;;; 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