Add article view
* avandu.el: Add Tiny Tiny RSS url to Commentary. (avandu-article-title): (avandu-article-author): New faces. (avandu-html2text-command): New user option. (avandu-article-button-map): Wrap a lambda around the call to `avandu-mark-article-read'. (avu-prop): New macro. (avandu--get-session-id): (avandu--get-status-id): (avandu--send-command): (avandu-logged-in-p): (avandu-new-articles-count): (avandu-tt-rss-api-level): (avandu-tt-rss-version): (avandu-overview): Use `avu-prop'. (avandu--insert-article-title): Show the article screen when activating an article button instead of showing its URL. (avandu-categories): (avandu-feeds): (avandu-headlines): No more need to call `cdr' and `assq' on the result of `avandu--send-command'. (avandu-get-article): New function. (avandu-mark-article-read): Just send a command to the server, don't do anything with the UI. (avandu-ui-mark-article-read): New function. Split off from `avandu-mark-article-read'. (avandu-article-mode): New major mode. (avandu-view-article): New function.
This commit is contained in:
parent
93461d87cd
commit
6d056c6a63
1 changed files with 134 additions and 58 deletions
156
avandu.el
156
avandu.el
|
@ -23,8 +23,9 @@
|
|||
|
||||
;;; Commentary:
|
||||
|
||||
;; Avandu is an emacs mode that connects to a Tiny Tiny RSS instance
|
||||
;; and allows you to read the feeds it has gathered locally.
|
||||
;; Avandu is an emacs mode that connects to a Tiny Tiny RSS
|
||||
;; (http://tt-rss.org) instance and allows you to read the feeds it
|
||||
;; has gathered.
|
||||
|
||||
;; The simplest way to install it is to use package.el:
|
||||
|
||||
|
@ -105,6 +106,21 @@
|
|||
"Face for unread article titles in avandu overview."
|
||||
:group 'avandu)
|
||||
|
||||
(defface avandu-article-title
|
||||
'((((class color)
|
||||
(background dark))
|
||||
(:foreground "orange3" :weight bold :family "sans"))
|
||||
(((class color)
|
||||
(background light))
|
||||
(:foreground "red4" :weight bold :family "sans")))
|
||||
"Face for titles in avandu article view."
|
||||
:group 'avandu)
|
||||
|
||||
(defface avandu-article-author
|
||||
'((t (:inherit shadow :slant italic :height 0.9)))
|
||||
"Face for the author's name in avandu article view."
|
||||
:group 'avandu)
|
||||
|
||||
;; User options
|
||||
(defcustom avandu-tt-rss-api-url nil
|
||||
"URL of your Tiny Tiny RSS instance. For example:
|
||||
|
@ -117,6 +133,11 @@
|
|||
:group 'avandu
|
||||
:type 'string)
|
||||
|
||||
(defcustom avandu-html2text-command nil
|
||||
"Shell command to call to change HTML to plain text."
|
||||
:group 'avandu
|
||||
:type 'string)
|
||||
|
||||
;; Variables
|
||||
(defvar avandu--session-id nil
|
||||
"*internal* Session id for avandu.")
|
||||
|
@ -125,7 +146,12 @@
|
|||
(let ((map (make-sparse-keymap)))
|
||||
(set-keymap-parent map button-map)
|
||||
(define-key map "o" 'avandu-browse-article)
|
||||
(define-key map "r" 'avandu-mark-article-read)
|
||||
(define-key map "r" #'(lambda ()
|
||||
(interactive)
|
||||
(let ((button (button-at (point))))
|
||||
(avandu-mark-article-read
|
||||
(button-get button 'article-id))
|
||||
(avandu-ui-mark-article-read button))))
|
||||
map)
|
||||
"Keymap for articles in `avandu-overview-mode'.")
|
||||
|
||||
|
@ -182,6 +208,10 @@
|
|||
`(or ,var (setq ,var (,(if passwdp 'read-passwd 'read-string)
|
||||
,prompt))))
|
||||
|
||||
(defmacro avu-prop (element property)
|
||||
"Get PROPERTY from ELEMENT."
|
||||
`(cdr (assq (quote ,property) ,element)))
|
||||
|
||||
;; Internal
|
||||
(defun avandu--check-login ()
|
||||
"Check to see if we're (still) logged in, try to login
|
||||
|
@ -249,11 +279,11 @@ with `auth-source-search' and then by asking the user."
|
|||
|
||||
(defun avandu--get-session-id (results)
|
||||
"Get the session id from RESULTS."
|
||||
(cdr (assq 'session_id (assq 'content results))))
|
||||
(avu-prop (assq 'content results) session_id))
|
||||
|
||||
(defun avandu--get-status-id (results)
|
||||
"Get the status id from RESULTS."
|
||||
(cdr (assq 'status results)))
|
||||
(avu-prop results status))
|
||||
|
||||
(defun avandu--insert-article-excerpt (excerpt)
|
||||
"Insert the excerpt of an article."
|
||||
|
@ -280,7 +310,8 @@ the article-id and link properties, respectively."
|
|||
'link link
|
||||
'keymap avandu-article-button-map
|
||||
'action #'(lambda (button)
|
||||
(message "%s" (button-get button 'link))))
|
||||
(avandu-view-article (button-get button 'article-id))))
|
||||
|
||||
(fill-region pos (point))
|
||||
(insert-char ?\n 1)))
|
||||
|
||||
|
@ -337,15 +368,14 @@ returned json."
|
|||
(search-forward "\n\n")
|
||||
(setq result (json-read)))
|
||||
(kill-buffer buffer)
|
||||
result))
|
||||
(avu-prop result content)))
|
||||
|
||||
(defun avandu-categories (&optional unread)
|
||||
"Get the created categories. If UNREAD is non-nil only get
|
||||
categories with feeds with unread articles in them."
|
||||
(cdr (assq 'content
|
||||
(avandu--send-command
|
||||
`((op . "getCategories")
|
||||
,@(when unread `((unread_only . ,unread))))))))
|
||||
,@(when unread `((unread_only . ,unread))))))
|
||||
|
||||
(defun avandu-feeds (&optional category unread limit offset)
|
||||
"Get the subscribed feeds. If CATEGORY has been specified show
|
||||
|
@ -359,13 +389,12 @@ There are a number of special category IDs:
|
|||
-2 -- Labels
|
||||
-3 -- All feeds, excluding virtual feeds (e.g. Labels and such)
|
||||
-4 -- All feeds, including virtual feeds"
|
||||
(cdr (assq 'content
|
||||
(avandu--send-command
|
||||
`((op . "getFeeds")
|
||||
,@(when category `((cat_id . ,category)))
|
||||
,@(when unread `((unread_only . ,unread)))
|
||||
,@(when limit `((limit . ,limit)))
|
||||
,@(when offset `((offset . ,offset))))))))
|
||||
,@(when offset `((offset . ,offset))))))
|
||||
|
||||
(defun avandu-headlines (feed-id &rest plist)
|
||||
"Get a list of headlines from Tiny Tiny RSS from the feed
|
||||
|
@ -411,7 +440,6 @@ There are some special feed IDs:
|
|||
(view-mode (plist-get plist :view-mode))
|
||||
(include-attachments (plist-get plist :include-attachments))
|
||||
(since-id (plist-get plist :since-id)))
|
||||
(cdr (assq 'content
|
||||
(avandu--send-command
|
||||
`((op . "getHeadlines")
|
||||
(feed_id . ,feed-id)
|
||||
|
@ -421,8 +449,9 @@ There are some special feed IDs:
|
|||
,@(when show-excerpt `((show_excerpt . ,show-excerpt)))
|
||||
,@(when show-content `((show_content . ,show-content)))
|
||||
,@(when view-mode `((view_mode . ,view-mode)))
|
||||
,@(when include-attachments `((include_attachments . ,include-attachments)))
|
||||
,@(when since-id `((since_id . ,since-id)))))))))
|
||||
,@(when include-attachments `((include_attachments
|
||||
. ,include-attachments)))
|
||||
,@(when since-id `((since_id . ,since-id)))))))
|
||||
|
||||
(defun avandu-update-article (article-ids mode field &optional data)
|
||||
"Update the status of FIELD to MODE for the articles identified
|
||||
|
@ -449,6 +478,13 @@ When updating FIELD 3 DATA functions as the note's contents."
|
|||
(field . ,field)
|
||||
,@(when data `((data . ,data))))))
|
||||
|
||||
(defun avandu-get-article (article-ids)
|
||||
"Get one or more articles from Tiny Tiny RSS with ARTICLE-IDS,
|
||||
if you're using version 1.5.0 or higher this can also be a
|
||||
comma-separated list of ids."
|
||||
(avandu--send-command `((op . "getArticle")
|
||||
(article_id . ,article-ids))))
|
||||
|
||||
;; Commands
|
||||
(defun avandu-browse-article ()
|
||||
"Browse the current button's article url."
|
||||
|
@ -456,7 +492,8 @@ When updating FIELD 3 DATA functions as the note's contents."
|
|||
(let ((button (button-at (point)))
|
||||
(message-truncate-lines t))
|
||||
(browse-url (button-get button 'link))
|
||||
(avandu-mark-article-read button)
|
||||
(avandu-mark-article-read (button-get button 'article-id))
|
||||
(avandu-ui-mark-article-read button)
|
||||
(message "Opened: %s" (button-label button))))
|
||||
|
||||
(defun avandu-feed-catchup ()
|
||||
|
@ -475,7 +512,7 @@ When updating FIELD 3 DATA functions as the note's contents."
|
|||
"Send a request to tt-rss to see if we're (still) logged
|
||||
in. This function returns t if we are, or nil if we're not."
|
||||
(let* ((response (avandu--send-command '((op . "isLoggedIn"))))
|
||||
(result (cdr (assq 'status (assq 'content response)))))
|
||||
(result (avu-prop response status)))
|
||||
(if (eq result :json-false)
|
||||
nil
|
||||
result)))
|
||||
|
@ -505,7 +542,7 @@ otherwise."
|
|||
(avandu--send-command '((op . "logout")))
|
||||
(avandu--clear-data))
|
||||
|
||||
(defun avandu-mark-article-read (&optional button)
|
||||
(defun avandu-mark-article-read (id)
|
||||
"Send a request to tt-rss to mark an article as read.
|
||||
|
||||
BUTTON, if given, should be a button widget, as created by
|
||||
|
@ -513,13 +550,18 @@ BUTTON, if given, should be a button widget, as created by
|
|||
nil, it will be assumed that `point' is currently within the
|
||||
bounds of a button."
|
||||
(interactive)
|
||||
(let* ((button (or button (button-at (point))))
|
||||
(id (button-get button 'article-id))
|
||||
(message-truncate-lines t))
|
||||
(avandu-update-article id 0 2)
|
||||
(let* ((message-truncate-lines t))
|
||||
(avandu-update-article id 0 2)))
|
||||
|
||||
(defun avandu-ui-mark-article-read (&optional button)
|
||||
"Try to change the state of BUTTON to a read article button, if
|
||||
BUTTON is nil, try to use a button at `point'."
|
||||
(let ((button (or button (button-at (point)))))
|
||||
(if button
|
||||
(progn
|
||||
(button-put button 'face 'avandu-overview-read-article)
|
||||
(message "Marked article as read: %s" (button-label button)))
|
||||
(avandu-next-article))
|
||||
(error "No button found."))))
|
||||
|
||||
(defun avandu-new-articles-count ()
|
||||
"Send a request to tt-rss for the total number of unread
|
||||
|
@ -527,7 +569,7 @@ feeds."
|
|||
(interactive)
|
||||
(avandu--check-login)
|
||||
(let* ((result (avandu--send-command '((op . "getUnread"))))
|
||||
(count (cdr (assq 'unread (assq 'content result)))))
|
||||
(count (avu-prop result unread)))
|
||||
|
||||
(when (called-interactively-p 'any)
|
||||
(message "There are %s unread articles" count))
|
||||
|
@ -557,10 +599,8 @@ feeds."
|
|||
(defun avandu-tt-rss-api-level ()
|
||||
"Get the API level of your Tiny Tiny RSS instance."
|
||||
(interactive)
|
||||
(let ((level (cdr (assq 'level
|
||||
(assq 'content
|
||||
(avandu--send-command
|
||||
'((op . "getApiLevel"))))))))
|
||||
(let ((level (avu-prop (avandu--send-command '((op . "getApiLevel")))
|
||||
level)))
|
||||
(when (called-interactively-p 'any)
|
||||
(message "API Level: %d" level))
|
||||
|
||||
|
@ -569,10 +609,8 @@ feeds."
|
|||
(defun avandu-tt-rss-version ()
|
||||
"Get the version of your Tiny Tiny RSS instance."
|
||||
(interactive)
|
||||
(let ((version (cdr (assq 'version
|
||||
(assq 'content
|
||||
(avandu--send-command
|
||||
'((op . "getVersion"))))))))
|
||||
(let ((version (avu-prop (avandu--send-command '((op . "getVersion")))
|
||||
version)))
|
||||
(when (called-interactively-p 'any)
|
||||
(message "Tiny Tiny RSS Version: %s" version))
|
||||
|
||||
|
@ -595,6 +633,15 @@ doesn't sort the list, so you'll have to set that up in tt-rss.
|
|||
avandu-overview-mode-name
|
||||
(avandu-new-articles-count))))
|
||||
|
||||
(define-derived-mode avandu-article-mode special-mode
|
||||
"Avandu:Article"
|
||||
"Major mode for the avandu article screen.
|
||||
|
||||
This screen shows the contents of an article.
|
||||
|
||||
\\{avandu-overview-map}
|
||||
\\<avandu-overview-map>")
|
||||
|
||||
;;;###autoload
|
||||
(defun avandu-overview ()
|
||||
"Request the headlines of unread articles and list them grouped
|
||||
|
@ -610,22 +657,51 @@ by feed."
|
|||
(goto-char (point-min))
|
||||
(mapc #'(lambda (elt)
|
||||
(unless (equal feed-id (assq 'feed_id elt))
|
||||
(avandu--insert-feed-title
|
||||
(cdr (assq 'feed_id elt))
|
||||
(cdr (assq 'feed_title elt))))
|
||||
(avandu--insert-feed-title (avu-prop elt feed_id)
|
||||
(avu-prop elt feed_title)))
|
||||
(setq feed-id (assq 'feed_id elt))
|
||||
(avandu--insert-article-title
|
||||
(cdr (assq 'id elt))
|
||||
(cdr (assq 'link elt))
|
||||
(cdr (assq 'title elt)))
|
||||
(avandu--insert-article-excerpt
|
||||
(cdr (assq 'excerpt elt))))
|
||||
(avandu--insert-article-title (avu-prop elt id)
|
||||
(avu-prop elt link)
|
||||
(avu-prop elt title))
|
||||
(avandu--insert-article-excerpt (avu-prop elt excerpt)))
|
||||
result)
|
||||
(setq buffer-read-only t)
|
||||
(goto-char (point-min))
|
||||
(avandu-overview-mode))
|
||||
(switch-to-buffer buffer)))
|
||||
|
||||
(defun avandu-view-article (id)
|
||||
"Show a single article in a new buffer."
|
||||
(interactive "nArticle id: ")
|
||||
(let* ((data (avandu-get-article id))
|
||||
(buffer (get-buffer-create "*avandu-article*"))
|
||||
(inhibit-read-only t))
|
||||
(with-current-buffer buffer
|
||||
(erase-buffer)
|
||||
(mapc #'(lambda (item)
|
||||
(insert
|
||||
(propertize (avu-prop item title)
|
||||
'face 'avandu-article-title))
|
||||
(newline)
|
||||
(insert
|
||||
(propertize (concat "by: " (avu-prop item author))
|
||||
'face 'avandu-article-author))
|
||||
(newline)(newline)
|
||||
(let ((pos (point)))
|
||||
(insert (avu-prop item content))
|
||||
|
||||
(when avandu-html2text-command
|
||||
(shell-command-on-region
|
||||
pos (point) avandu-html2text-command buffer t)))
|
||||
(newline)(newline))
|
||||
data)
|
||||
(setq buffer-read-only t)
|
||||
(goto-char (point-min))
|
||||
(avandu-article-mode))
|
||||
(avandu-mark-article-read id)
|
||||
(avandu-ui-mark-article-read)
|
||||
(switch-to-buffer buffer)))
|
||||
|
||||
(provide 'avandu)
|
||||
|
||||
;;; avandu.el ends here
|
||||
|
|
Loading…
Reference in a new issue