229 lines
7.4 KiB
EmacsLisp
229 lines
7.4 KiB
EmacsLisp
|
|
||
|
;;; Code:
|
||
|
|
||
|
(require 'json)
|
||
|
(require 'plz)
|
||
|
(require 'message)
|
||
|
|
||
|
(defgroup discoursel nil
|
||
|
"Discourse TODO")
|
||
|
|
||
|
(defcustom discoursel-instance "https://discourse.ubuntu.com"
|
||
|
"The Discourse instance."
|
||
|
:group 'discoursel
|
||
|
:type 'string)
|
||
|
|
||
|
(defvar discoursel-buffer-list nil
|
||
|
"List of discoursel buffers.")
|
||
|
|
||
|
(defface discoursel-topic-face
|
||
|
'((((class color) (background light)) (:foreground "#000"))
|
||
|
(((class color) (background dark)) (:foreground "#fff")))
|
||
|
"Face used for topics."
|
||
|
:group 'discoursel)
|
||
|
|
||
|
(defface discoursel-username-face
|
||
|
'((((class color) (background light)) (:foreground "#aa0"))
|
||
|
(((class color) (background dark)) (:foreground "#ff0")))
|
||
|
"Face used for usernames."
|
||
|
:group 'discoursel)
|
||
|
|
||
|
(defface discoursel-code-face
|
||
|
'((((class color) (background light)) (:background "#cdcecc" :extend t))
|
||
|
(((class color) (background dark)) (:background "#17211c" :extend t)))
|
||
|
"Face used for code blocks."
|
||
|
:group 'discoursel)
|
||
|
|
||
|
(defun discoursel--parse-pre (tag)
|
||
|
(insert
|
||
|
(with-temp-buffer
|
||
|
(let ((shr-folding-mode 'none))
|
||
|
(shr-ensure-newline)
|
||
|
(shr-generic tag)
|
||
|
(shr-ensure-newline))
|
||
|
(add-text-properties (point-min) (point-max)
|
||
|
'(pre-tag t face discoursel-code-face))
|
||
|
(buffer-string))))
|
||
|
|
||
|
(defun discoursel--get (endpoint)
|
||
|
(condition-case plzerror
|
||
|
(plz 'get (concat discoursel-instance "/" endpoint ".json")
|
||
|
:as #'json-read)
|
||
|
(plz-error
|
||
|
(error "Could not query endpoint '%s': '%s'"
|
||
|
endpoint (plz-error-message plzerror)))))
|
||
|
|
||
|
(defun discoursel--latest-topics ()
|
||
|
(let* ((r (discoursel--get "latest"))
|
||
|
(topics (alist-get 'topics (alist-get 'topic_list r)))
|
||
|
(users (alist-get 'users r)))
|
||
|
(with-current-buffer (discoursel-get-create-buffer)
|
||
|
(cl-map 'vector
|
||
|
(lambda (topic)
|
||
|
(let* ((userid (alist-get 'user_id (aref (alist-get 'posters topic) 0)))
|
||
|
(username (seq-find
|
||
|
(lambda (v)
|
||
|
(= (alist-get 'id v) userid))
|
||
|
users))
|
||
|
(inhibit-read-only t))
|
||
|
(insert
|
||
|
(propertize (format "%s (by %s)\n"
|
||
|
(propertize (alist-get 'title topic)
|
||
|
'face 'discoursel-topic-face)
|
||
|
(propertize (alist-get 'name username)
|
||
|
'face 'discoursel-username-face))
|
||
|
'discoursel-topic-slug (alist-get 'slug topic)
|
||
|
'discoursel-topic-id (alist-get 'id topic)))))
|
||
|
topics))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun discoursel-update ()
|
||
|
(interactive)
|
||
|
(with-current-buffer (discoursel-get-create-buffer)
|
||
|
(let ((inhibit-read-only t))
|
||
|
(discoursel--latest-topics))))
|
||
|
|
||
|
(defun discoursel--open-topic (&optional id)
|
||
|
(interactive)
|
||
|
(when (and (not (eq major-mode 'discoursel-mode))
|
||
|
(not id))
|
||
|
(error "You must be in a discoursel buffer"))
|
||
|
(let* ((topic-id (number-to-string
|
||
|
(or id (get-text-property (point) 'discoursel-topic-id))))
|
||
|
(r (discoursel--get (concat "t/" topic-id)))
|
||
|
(posts (alist-get 'posts (alist-get 'post_stream r)))
|
||
|
(first-post (aref posts 0))
|
||
|
(topic-slug (get-text-property (point) 'discoursel-topic-slug))
|
||
|
(topic-title (alist-get 'title r))
|
||
|
(topic-date (alist-get 'created_at r))
|
||
|
(topic-date-string
|
||
|
(format-time-string "%c"
|
||
|
(encode-time
|
||
|
(parse-time-string topic-date))))
|
||
|
(userid (alist-get 'user_id r))
|
||
|
(participants (alist-get 'participants (alist-get 'details r)))
|
||
|
(topic-author-alist (seq-find (lambda (p)
|
||
|
(= (alist-get 'id p) userid))
|
||
|
participants))
|
||
|
(topic-author (alist-get 'username topic-author-alist))
|
||
|
(buf (discoursel-get-create-buffer topic-slug)))
|
||
|
(switch-to-buffer buf)
|
||
|
(with-current-buffer buf
|
||
|
(discoursel-topic-mode)
|
||
|
(let ((inhibit-read-only t))
|
||
|
(erase-buffer)
|
||
|
(save-excursion
|
||
|
(insert
|
||
|
(with-temp-buffer
|
||
|
(insert (format (propertize "Title: %s\n" 'face 'message-header-name)
|
||
|
(propertize topic-title 'face 'message-header-subject)))
|
||
|
(insert (format (propertize "Author: %s\n" 'face 'message-header-name)
|
||
|
(propertize topic-author 'face 'message-header-to)))
|
||
|
(insert (format (propertize "Date: %s\n" 'face 'message-header-name)
|
||
|
(propertize topic-date-string 'face 'message-header-other)))
|
||
|
(insert "\n")
|
||
|
(let ((p (point)))
|
||
|
(insert (alist-get 'cooked first-post))
|
||
|
(let ((shr-use-fonts nil)
|
||
|
(shr-external-rendering-functions
|
||
|
'((pre . discoursel--parse-pre))))
|
||
|
(shr-render-region p (point-max))))
|
||
|
(buffer-substring (point-min) (point-max)))))))))
|
||
|
|
||
|
(defun discoursel-open-topic (id)
|
||
|
(interactive "nTopic ID: ")
|
||
|
(discoursel--open-topic id))
|
||
|
|
||
|
(defun discoursel-get-create-buffer (&optional name)
|
||
|
(let* ((bufname (concat "*discoursel"
|
||
|
(when name (concat "-" name))
|
||
|
"*"))
|
||
|
(buf (get-buffer-create bufname)))
|
||
|
(prog1 buf
|
||
|
(unless (memq buf discoursel-buffer-list)
|
||
|
(add-to-list 'discoursel-buffer-list buf)))))
|
||
|
|
||
|
(defun discoursel-kill-buffer (&optional buf)
|
||
|
(interactive)
|
||
|
(let* ((tmpb (or buf (current-buffer)))
|
||
|
(b (get-buffer tmpb)))
|
||
|
(unless b
|
||
|
(error "Invalid buffer %s" b))
|
||
|
(with-current-buffer b
|
||
|
(unless (derived-mode-p 'discoursel-mode)
|
||
|
(error "Cannot kill buffer %s: not a discoursel buffer" (buffer-name))))
|
||
|
(kill-buffer b)
|
||
|
(setq discoursel-buffer-list (remove b discoursel-buffer-list))))
|
||
|
|
||
|
(defun discoursel-exit ()
|
||
|
(interactive)
|
||
|
(dolist (buf discoursel-buffer-list)
|
||
|
(kill-buffer buf))
|
||
|
(setq discoursel-buffer-list nil))
|
||
|
|
||
|
(defvar discoursel-mode-map
|
||
|
(let ((map (make-sparse-keymap)))
|
||
|
(prog1 map
|
||
|
(suppress-keymap map)
|
||
|
(define-key map "n" #'next-line)
|
||
|
(define-key map "p" #'previous-line)
|
||
|
(define-key map (kbd "RET") #'discoursel--open-topic)
|
||
|
(define-key map "j" #'discoursel-open-topic)
|
||
|
(define-key map "q" #'discoursel-exit)
|
||
|
(define-key map "g" #'discoursel-update)))
|
||
|
"Keymap for `discoursel-mode'.")
|
||
|
|
||
|
(defvar discoursel-topic-mode-map
|
||
|
(let ((map (make-sparse-keymap)))
|
||
|
(prog1 map
|
||
|
(suppress-keymap map)
|
||
|
(define-key map "n" #'discoursel-topic-next-post)
|
||
|
(define-key map "p" #'discoursel-topic-previous-post)
|
||
|
(define-key map "q" #'discoursel-kill-buffer)))
|
||
|
"Keymap for `discoursel-topic-mode'.")
|
||
|
|
||
|
(define-derived-mode discoursel-mode special-mode "Discourse client"
|
||
|
"Major mode for interacting with Discourse instances.
|
||
|
\\{discoursel-mode-map}"
|
||
|
(use-local-map discoursel-mode-map))
|
||
|
|
||
|
(define-derived-mode discoursel-topic-mode discoursel-mode "Discoursel topic"
|
||
|
"Major mode for interacting with Discourse topics.
|
||
|
\\{discoursel-topic-mode-map}"
|
||
|
(use-local-map discoursel-topic-mode-map))
|
||
|
|
||
|
(defun discoursel ()
|
||
|
"Enter discoursel."
|
||
|
(interactive)
|
||
|
(switch-to-buffer (discoursel-get-create-buffer))
|
||
|
(unless (eq major-mode 'discoursel-mode)
|
||
|
(discoursel-mode))
|
||
|
(discoursel-update))
|
||
|
|
||
|
|
||
|
;; TODO
|
||
|
;; (with-current-buffer (get-buffer-create "test-discourse")
|
||
|
;; (insert (plz 'get "https://discourse.ubuntu.com/raw/39765"))
|
||
|
;; (markdown-mode)
|
||
|
;; (setq buffer-read-only t))
|
||
|
|
||
|
;; (with-current-buffer (get-buffer-create "test-discourse4")
|
||
|
;; (let* ((ttt (plz 'get "https://discourse.ubuntu.com/latest.json" :as #'json-read))
|
||
|
;; (topics (alist-get 'topics (alist-get 'topic_list ttt)))
|
||
|
;; (users (alist-get 'users ttt)))
|
||
|
;; (cl-map 'vector
|
||
|
;; (lambda (topic)
|
||
|
;; (let* ((uid (alist-get 'user_id (aref (alist-get 'posters topic) 0)))
|
||
|
;; (user (seq-find (lambda (v) (= (alist-get 'id v) uid))
|
||
|
;; users)))
|
||
|
;; (insert
|
||
|
;; (propertize (format "%s [%s]\n" (alist-get 'title topic) (alist-get 'name user))
|
||
|
;; 'discoursel-slug (alist-get 'slug topic)))))
|
||
|
;; topics)))
|
||
|
|
||
|
|
||
|
|
||
|
(provide 'discoursel)
|
||
|
|
||
|
;;; discoursel.el ends here
|