aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Tom Willemsen2012-08-22 12:44:50 +0200
committerGravatar Tom Willemsen2012-08-22 12:44:50 +0200
commit6d056c6a63be14e976d0bca160cae2de9f2c191d (patch)
tree5af56def403b6797038c848541a4280ec78c484e
parent93461d87cd66df2a4b31f0ae12593235e8867adb (diff)
downloadavandu-6d056c6a63be14e976d0bca160cae2de9f2c191d.tar.gz
avandu-6d056c6a63be14e976d0bca160cae2de9f2c191d.zip
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.
-rw-r--r--avandu.el192
1 files changed, 134 insertions, 58 deletions
diff --git a/avandu.el b/avandu.el
index 5c952c8..1bde92d 100644
--- a/avandu.el
+++ b/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))))))))
+ (avandu--send-command
+ `((op . "getCategories")
+ ,@(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))))))))
+ (avandu--send-command
+ `((op . "getFeeds")
+ ,@(when category `((cat_id . ,category)))
+ ,@(when unread `((unread_only . ,unread)))
+ ,@(when limit `((limit . ,limit)))
+ ,@(when offset `((offset . ,offset))))))
(defun avandu-headlines (feed-id &rest plist)
"Get a list of headlines from Tiny Tiny RSS from the feed
@@ -411,18 +440,18 @@ 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)
- ,@(when limit `((limit . ,limit)))
- ,@(when skip `((skip . ,skip)))
- ,@(when is-cat `((is_cat . ,is-cat)))
- ,@(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)))))))))
+ (avandu--send-command
+ `((op . "getHeadlines")
+ (feed_id . ,feed-id)
+ ,@(when limit `((limit . ,limit)))
+ ,@(when skip `((skip . ,skip)))
+ ,@(when is-cat `((is_cat . ,is-cat)))
+ ,@(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)))))))
(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)
- (button-put button 'face 'avandu-overview-read-article)
- (message "Marked article as read: %s" (button-label button)))
- (avandu-next-article))
+ (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)
+ (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