diff --git a/TODO.org b/TODO.org index 442b381..7e98681 100644 --- a/TODO.org +++ b/TODO.org @@ -1,17 +1,25 @@ * TODO ** Features -- [ ] search -- [ ] upvote/downvote posts -- [ ] upvote/downvote comments +- [X] upvote/downvote posts +- [X] upvote/downvote comments +- [ ] upvote/downvote current post in dank-mode-comments +- [ ] browse url of current post in dank-mode-comments - [ ] reply comments +- [ ] mouse interaction + - [ ] open post comments + - [ ] open post link + - [ ] upvote/downvote post + - [ ] upvote/downvote comment + - [ ] fold comment +- [ ] search ** Enhancements ** Bug fixes - [ ] Switching subreddits in dank-mode-comments mode is not available * Ideas -- Use plstore.el for oauth tokens -- Use web-server.el to do the oauth dance +- [X] Use plstore.el for oauth tokens +- [X] Use web-server.el to do the oauth dance http://eschulte.github.io/emacs-web-server * Known bugs diff --git a/lisp/dank-mode-backend.el b/lisp/dank-mode-backend.el index 70d5469..7a4719f 100644 --- a/lisp/dank-mode-backend.el +++ b/lisp/dank-mode-backend.el @@ -8,7 +8,7 @@ ;;; Commentary: -;; This file defines functions for making requests to Reddit. +;; This file defines wrapper functions for making requests to Reddit. ;;; Code: @@ -151,6 +151,13 @@ REQUEST-PARAMS is plist of request parameters that Reddit's 'subreddits' API tak (resp (dank-mode-backend-request "GET" url params))) (plist-get (plist-get resp :data) :children))) +(defun dank-mode-backend-vote (thing-id direction) + "Cast a vote on THING-ID with DIRECTION (1, 0 or -1)." + (let* ((url "/api/vote") + (resp (dank-mode-backend-request "POST" url `((id . ,thing-id) (dir . ,direction))))) + resp)) + + (provide 'dank-mode-backend) ;;; dank-mode-backend.el ends here diff --git a/lisp/dank-mode-comment.el b/lisp/dank-mode-comment.el index ffce7d0..0288ba0 100644 --- a/lisp/dank-mode-comment.el +++ b/lisp/dank-mode-comment.el @@ -16,11 +16,22 @@ (require 'dank-mode-backend) (require 'dank-mode-utils) (require 'dank-mode-post) +(require 'dank-mode-faces) + +(defcustom dank-mode-comment-upvote-symbol "🡅" + "Symbol to use for upvotes. Change this if the emoji doesn't work correctly." + :type 'string + :group 'dank-mode) + +(defcustom dank-mode-comment-downvote-symbol "🡇" + "Symbol to use for downvotes. Change this if the emoji doesn't work correctly." + :type 'string + :group 'dank-mode) (cl-defstruct dank-mode-comment name id body edited text age date author subreddit score author_flair gilded replies depth parent_id post_author_p - type more_count children_ids) + type more_count children_ids likes) (defcustom dank-mode-comments-body-fill-width 120 "Fill width for rendering the comment body." @@ -28,7 +39,7 @@ :group 'dank-mode) (defvar dank-mode-comment-metadata-template - "- ${author} (${score} points | ${age})${edited} ") + "- ${author} (${vote}${score} points | ${age})${edited} ") (defvar dank-mode-comment-more-template "+ [${count} more comments]") @@ -72,7 +83,8 @@ list of children, also parsed." :author_flair (plist-get comment :author_flair_text) :gilded (plist-get comment :gilded) :parent_id (plist-get comment :parent_id) - :post_author_p (string-equal (plist-get comment :author) post-author)))) + :post_author_p (string-equal (plist-get comment :author) post-author) + :likes (plist-get comment :likes)))) (if (stringp replies) parsed-comment `(,parsed-comment . (,(mapcar (lambda (c) (dank-mode-comment-parse c post-author)) children)))))))) @@ -85,11 +97,18 @@ formatting/indentation will depend on its position." (let* ((author-face (if (dank-mode-comment-post_author_p comment) 'dank-mode-faces-comment-author-op 'dank-mode-faces-comment-author)) (author (concat "/u/" (dank-mode-comment-author comment))) (score (number-to-string (dank-mode-comment-score comment))) + (likes (dank-mode-comment-likes comment)) + (vote (format "%s" (cond ((eq :false likes) dank-mode-comment-downvote-symbol) + (likes dank-mode-comment-upvote-symbol) ; likes is t if upvoted + (t "")))) + (vote-face (format "%s" (cond ((eq :false likes) 'dank-mode-faces-downvote) + (likes 'dank-mode-faces-upvote) ; likes is t if upvoted + (t 'dank-mode-faces-comment-metadata)))) (age (dank-mode-comment-age comment)) (edited (or nil "")) (gilded (number-to-string (dank-mode-comment-gilded comment))) (depth (dank-mode-comment-depth comment)) - (format-context `(author (,author . ,author-face) age ,age score ,score edited ,edited gilded ,gilded depth ,depth)) + (format-context `(author (,author . ,author-face) age ,age vote (,vote . ,vote-face) score (,score . ,vote-face) edited ,edited gilded ,gilded depth ,depth)) (formatted-metadata (dank-mode-utils-format-plist dank-mode-comment-metadata-template format-context 'dank-mode-faces-comment-metadata)) (formatted-metadata (concat (make-string (* 2 depth) ?\s) formatted-metadata))) formatted-metadata)) diff --git a/lisp/dank-mode-comments.el b/lisp/dank-mode-comments.el index a0caa49..12b190e 100644 --- a/lisp/dank-mode-comments.el +++ b/lisp/dank-mode-comments.el @@ -59,6 +59,8 @@ (define-key map (kbd "C-x l o") 'dank-mode-comments-browse-post-link) (define-key map (kbd "C-x l o") 'dank-mode-comments-browse-post-comments) (define-key map (kbd "TAB") 'dank-mode-comments-toggle-comment-tree-fold-at-point) + (define-key map (kbd "C-x v u") (lambda (point) (interactive "d") (dank-mode-comments-vote-comment-at-point point 1))) + (define-key map (kbd "C-x v d") (lambda (point) (interactive "d") (dank-mode-comments-vote-comment-at-point point -1))) (define-key map (kbd "C-x q") 'dank-mode-comments-kill-current-buffer) map)) @@ -438,5 +440,28 @@ If EWW is non-nil, browse in eww instead of the browser." (let ((browse-url-browser-function (if eww 'eww-browse-url 'browse-url-default-browser))) (browse-url (concat (dank-mode-utils-reddit-url) dank-mode-comments-current-permalink)))) +(defun dank-mode-comments-vote-comment-at-point (pos direction) + "Vote the comment at POS with DIRECTION. +Sets the comment's `likes' field appropriately." + (interactive "d") + (let* ((ewoc-node (ewoc-locate dank-mode-comments-current-ewoc pos)) + (comment-data (dank-mode-utils-ewoc-data dank-mode-comments-current-ewoc pos)) + (comment-id (dank-mode-comment-id comment-data)) + (comment-current-likes (dank-mode-comment-likes comment-data)) + (comment-current-score (dank-mode-comment-score comment-data)) + ; negate direction (set to 0) if comment is already voted towards the same direction + (dir (cond ((and (eq :false comment-current-likes) (= -1 direction)) 0) + ((and comment-current-likes (= 1 direction)) 0) + (t direction))) + (new-likes (cond ((= -1 dir) :false) + ((= 1 dir) t) + ((= 0 dir) nil) + (t nil)))) + (dank-mode-backend-vote (concat "t1_" comment-id) dir) + (setf (dank-mode-comment-likes comment-data) new-likes) + (setf (dank-mode-comment-score comment-data) (+ comment-current-score dir)) + (ewoc-set-data ewoc-node comment-data) + (ewoc-invalidate dank-mode-comments-current-ewoc ewoc-node))) + (provide 'dank-mode-comments) ;;; dank-mode-comments.el ends here diff --git a/lisp/dank-mode-faces.el b/lisp/dank-mode-faces.el index 916fa75..3c786ea 100644 --- a/lisp/dank-mode-faces.el +++ b/lisp/dank-mode-faces.el @@ -22,15 +22,20 @@ :group 'dank-mode-faces) (defface dank-mode-faces-upvote - `((t :foreground ,dank-mode-faces-color-upvote)) + `((t :foreground ,dank-mode-faces-color-upvote :weight bold)) "Face for upvotes." :group 'dank-mode-faces) (defface dank-mode-faces-downvote - `((t :foreground ,dank-mode-faces-color-downvote)) + `((t :foreground ,dank-mode-faces-color-downvote :weight bold)) "Face for downvotes." :group 'dank-mode-faces) +(defface dank-mode-faces-score + `((t :foreground ,dank-mode-faces-color-upvote)) + "Face for score points." + :group 'dank-mode-faces) + (defface dank-mode-faces-post-title '((t :inherit default :weight bold)) "Face for post titles." diff --git a/lisp/dank-mode-oauth.el b/lisp/dank-mode-oauth.el index ca8dbf1..d887c34 100644 --- a/lisp/dank-mode-oauth.el +++ b/lisp/dank-mode-oauth.el @@ -64,7 +64,7 @@ to be refreshed." (defvar dank-mode-oauth-redirect-server nil) (defvar dank-mode-oauth-auth-url "https://www.reddit.com/api/v1/authorize") (defvar dank-mode-oauth-token-url "https://www.reddit.com/api/v1/access_token") -(defvar dank-mode-oauth-scope "identity history mysubreddits read wikiread") +(defvar dank-mode-oauth-scope "identity history mysubreddits read wikiread vote edit save submit subscribe report") (defvar dank-mode-oauth-redirect-url (concat "http://localhost:" (number-to-string dank-mode-oauth-redirect-port) dank-mode-oauth-redirect-path)) (defun dank-mode-oauth--random-string (n) diff --git a/lisp/dank-mode-post.el b/lisp/dank-mode-post.el index 7c097b1..bc7fc6c 100644 --- a/lisp/dank-mode-post.el +++ b/lisp/dank-mode-post.el @@ -16,11 +16,21 @@ (require 'dank-mode-utils) (require 'dank-mode-faces) +(defcustom dank-mode-post-upvote-symbol "🡅" + "Symbol to use for upvotes. Change this if the emoji doesn't work correctly." + :type 'string + :group 'dank-mode) + +(defcustom dank-mode-post-downvote-symbol "🡇" + "Symbol to use for downvotes. Change this if the emoji doesn't work correctly." + :type 'string + :group 'dank-mode) + (cl-defstruct dank-mode-post "Struct for a Reddit post." name id title link permalink text age date author subreddit score num_comments domain post_type nsfw spoiler link_flair author_flair - gilded stickied locked) + gilded stickied locked likes) (cl-defstruct dank-subreddit "Struct for a subreddit." @@ -29,7 +39,7 @@ ;; Rendering (defvar dank-mode-post-template - "${title}\n | ${score} points | ${num_comments} comments | ${link_flair}${nsfw}${spoiler}${post_type}${domain}\n | ${subreddit} submitted by ${author}${author_flair} ${age}") + "${title}\n | ${vote}${score} points | ${num_comments} comments | ${link_flair}${nsfw}${spoiler}${post_type}${domain}\n | ${subreddit} submitted by ${author}${author_flair} ${age}") (defun dank-mode-post-format (post) "Format POST as string using `dank-mode-post-template'. @@ -42,6 +52,13 @@ Also applies font-lock properties." "")) (subreddit (concat "/r/" (dank-mode-post-subreddit post))) (score (format "%s" (dank-mode-post-score post))) + (likes (dank-mode-post-likes post)) + (vote (format "%s" (cond ((eq :false likes) dank-mode-post-downvote-symbol) + (likes dank-mode-post-upvote-symbol) ; likes is t if upvoted + (t "")))) + (vote-face (format "%s" (cond ((eq :false likes) 'dank-mode-faces-downvote) + (likes 'dank-mode-faces-upvote) ; likes is t if upvoted + (t 'dank-mode-faces-upvote)))) (num_comments (format "%s" (dank-mode-post-num_comments post))) (nsfw (if (dank-mode-post-nsfw post) "NSFW " "")) (spoiler (if (dank-mode-post-spoiler post) "SPOILERS " "")) @@ -51,7 +68,7 @@ Also applies font-lock properties." (link_flair (if (dank-mode-post-link_flair post) (concat "[" (dank-mode-post-link_flair post) "] ") "")) (format-context `(title (,title . dank-mode-faces-post-title) age (,age . dank-mode-faces-age) author (,author . dank-mode-faces-post-author) author_flair (,author_flair . dank-mode-faces-flair) - subreddit (,subreddit . dank-mode-faces-subreddit) score (,score . dank-mode-faces-upvote) num_comments (,num_comments . dank-mode-faces-downvote) + subreddit (,subreddit . dank-mode-faces-subreddit) vote (,vote . ,vote-face) score (,score . dank-mode-faces-upvote) num_comments (,num_comments . dank-mode-faces-downvote) nsfw (,nsfw . dank-mode-faces-nsfw) spoiler (,spoiler . dank-mode-faces-nsfw) domain (,domain . dank-mode-faces-site-domain) post_type (,post_type . dank-mode-faces-post-type) link_flair (,link_flair . dank-mode-faces-flair))) (formatted-post (dank-mode-utils-format-plist dank-mode-post-template format-context))) @@ -67,6 +84,7 @@ Also applies font-lock properties." :text (plist-get post :selftext) :age (dank-mode-utils-timestamp-ago (plist-get post :created_utc)) :date (plist-get post :created_utc) + :likes (plist-get post :likes) :score (plist-get post :score) :author (plist-get post :author) :subreddit (plist-get post :subreddit) diff --git a/lisp/dank-mode-posts.el b/lisp/dank-mode-posts.el index 4eeebce..726a6db 100644 --- a/lisp/dank-mode-posts.el +++ b/lisp/dank-mode-posts.el @@ -73,6 +73,8 @@ When nil, defaults to the frontpage." (define-key map (kbd "C-x l l") (lambda (point) (interactive "d") (dank-mode-posts-browse-post-link-at-point point t))) (define-key map (kbd "C-x l b") 'dank-mode-posts-browse-post-link-at-point) (define-key map (kbd "C-x l o") 'dank-mode-posts-browse-post-comments-at-point) + (define-key map (kbd "C-x v u") (lambda (point) (interactive "d") (dank-mode-posts-vote-post-at-point point 1))) + (define-key map (kbd "C-x v d") (lambda (point) (interactive "d") (dank-mode-posts-vote-post-at-point point -1))) (define-key map (kbd "C-x q") 'kill-current-buffer) map)) @@ -164,17 +166,6 @@ REFRESH-EWOC creates a new ewoc." "Populate the EWOC with POSTS." (mapc (lambda (p) (ewoc-enter-last ewoc p)) posts)) -(defun dank-mode-posts-append-post-to-buffer (buf post-index post) - "Append POST into BUF. -POST-INDEX is the number (\"position\") of the post." - (when (buffer-live-p buf) - (with-current-buffer buf - (let* ((inhibit-read-only t) - (formatted-post (concat (dank-mode-post-format post post-index) "\n"))) - (save-excursion - (goto-char (point-max)) - (insert formatted-post)))))) - (defun dank-mode-posts-render-error (err) "Render contents of ERR into the current buffer." (let ((inhibit-read-only t)) @@ -338,6 +329,33 @@ If EWW is non-nil, browse in eww instead of the browser." (browse-url-browser-function (if eww 'eww-browse-url 'browse-url-default-browser))) (browse-url (concat (dank-mode-utils-reddit-url) post-permalink)))) +(defun dank-mode-posts-vote-post-at-point (pos direction) + "Vote the post at POS with DIRECTION. +Sets the post's `likes' field appropriately." + (interactive "d") + (let* ((ewoc-node (ewoc-locate dank-mode-posts-current-ewoc pos)) + (post-data (dank-mode-utils-ewoc-data dank-mode-posts-current-ewoc pos)) + (post-id (dank-mode-post-id post-data)) + (post-current-likes (dank-mode-post-likes post-data)) + (post-current-score (dank-mode-post-score post-data)) + ; negate direction (set to 0) if post is already voted towards the same direction + (dir (cond ((and (eq :false post-current-likes) (= -1 direction)) 0) + ((and post-current-likes (= 1 direction)) 0) + (t direction))) + (new-likes (cond ((= -1 dir) :false) + ((= 1 dir) t) + ((= 0 dir) nil) + (t nil)))) + (dank-mode-backend-vote (concat "t3_" post-id) dir) + (setf (dank-mode-post-likes post-data) new-likes) + (setf (dank-mode-post-score post-data) (+ post-current-score dir)) + (ewoc-set-data ewoc-node post-data) + (ewoc-invalidate dank-mode-posts-current-ewoc ewoc-node))) + +(defun dank-mode-posts--print-ewoc-data-at-point (pos) + (interactive "d") + (message "%s" (dank-mode-utils-ewoc-data dank-mode-posts-current-ewoc pos))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; interaction functions ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;