aboutsummaryrefslogtreecommitdiff
path: root/sx-interaction.el
diff options
context:
space:
mode:
Diffstat (limited to 'sx-interaction.el')
-rw-r--r--sx-interaction.el169
1 files changed, 141 insertions, 28 deletions
diff --git a/sx-interaction.el b/sx-interaction.el
index b67e0df..89050c3 100644
--- a/sx-interaction.el
+++ b/sx-interaction.el
@@ -19,38 +19,85 @@
;;; Commentary:
+;; This file holds a series of functions for performing arbitrary
+;; interactions with arbitrary objects (objects here always mean the
+;; alist of a question, answer, or comment). All commands take at
+;; least a DATA argument corresponding to the object which, when
+;; called interactively, is always derived from the context at point
+;; (usually using the `sx--data-here' function).
+;;
+;; Interactions represented here involve voting, commenting, asking,
+;; answering, editing.
+;;
+;; These are commands are meant to be available throughout the
+;; interface. So it didn't make sense to put them in a specific
+;; module. They also rely on a lot of dependencies, so they couldn't
+;; be put in sx.el.
+
;;; Code:
+(eval-when-compile
+ '(require 'cl-lib))
(require 'sx)
(require 'sx-question)
(require 'sx-question-mode)
(require 'sx-question-list)
+(require 'sx-compose)
+(require 'sx-tab)
;;; Using data in buffer
-(defun sx--data-here (&optional noerror)
- "Get data for the question or other object under point.
-If NOERROR is non-nil, don't throw an error on failure.
-
-This looks at the text property `sx--data-here'. If it's not set,
-it looks at a few other reasonable variables. If those fail too,
-it throws an error."
- (or (get-text-property (point) 'sx--data-here)
- (and (derived-mode-p 'sx-question-list-mode)
- (tabulated-list-get-id))
- (and (derived-mode-p 'sx-question-mode)
- sx-question-mode--data)
+(defun sx--data-here (&optional type noerror)
+ "Get the alist regarding object under point of type TYPE.
+Looks at the text property `sx--data-here'. If it's not set, it
+looks at a few other reasonable variables. If those fail too, it
+throws an error.
+
+TYPE is a symbol restricting the type of object desired. Possible
+values are 'question, 'answer, 'comment, or nil (for any type).
+
+If no object of the requested type could be returned, an error is
+thrown unless NOERROR is non-nil."
+ (or (let ((data (get-char-property (point) 'sx--data-here)))
+ (if (null type) data
+ (sx-assoc-let type
+ ;; Is data of the right type?
+ (cl-case type
+ (question (when .title data))
+ (answer (when .answer_id data))
+ (comment (when .comment_id data))))))
+ ;; The following two only ever return questions.
+ (when (or (null type) (eq type 'question))
+ ;; @TODO: `sx-question-list-mode' may one day display answers.
+ ;; Ideally, it would use the `sx--data-here' (so no special
+ ;; handling would be necessary.
+ (or (and (derived-mode-p 'sx-question-list-mode)
+ (tabulated-list-get-id))
+ (and (derived-mode-p 'sx-question-mode)
+ sx-question-mode--data)))
+ ;; Nothing was found
(and (null noerror)
- (error "No question data found here"))))
+ (error "No %s found here" (or type "data")))))
-(defun sx--maybe-update-display ()
- "Refresh the question list if we're inside it."
- (cond
- ((derived-mode-p 'sx-question-list-mode)
- (sx-question-list-refresh 'redisplay 'no-update))
- ((derived-mode-p 'sx-question-mode)
- (sx-question-mode-refresh 'no-update))))
+(defun sx--error-if-unread (data)
+ "Throw a user-error if DATA is an unread question.
+If it's not a question, or if it is read, return DATA."
+ ;; If we found a question, we may need to check if it's read.
+ (if (and (assoc 'title data)
+ (null (sx-question--read-p data)))
+ (user-error "Question not yet read. View it before acting on it")
+ data))
+
+(defun sx--maybe-update-display (&optional buffer)
+ "Refresh whatever is displayed in BUFFER or the current buffer.
+If BUFFER is not live, nothing is done."
+ (setq buffer (or buffer (current-buffer)))
+ (when (buffer-live-p buffer)
+ (cond ((derived-mode-p 'sx-question-list-mode)
+ (sx-question-list-refresh 'redisplay 'no-update))
+ ((derived-mode-p 'sx-question-mode)
+ (sx-question-mode-refresh 'no-update)))))
(defun sx--copy-data (from to)
"Copy all fields of alist FORM onto TO.
@@ -75,7 +122,7 @@ If DATA is a question, also mark it as read."
(let ((link
(when (stringp .link)
(funcall (if copy-as-kill #'kill-new #'browse-url)
- .link))))
+ .link))))
(when (and (called-interactively-p 'any) copy-as-kill)
(message "Copied: %S" link)))
(when (and .title (not copy-as-kill))
@@ -110,7 +157,7 @@ If WINDOW nil, the window is decided by
"Apply or remove upvote from DATA.
DATA can be a question, answer, or comment. Interactively, it is
guessed from context at point."
- (interactive (list (sx--data-here)))
+ (interactive (list (sx--error-if-unread (sx--data-here))))
(sx-assoc-let data
(sx-set-vote data "upvote" (null (eq .upvoted t)))))
@@ -118,7 +165,7 @@ guessed from context at point."
"Apply or remove downvote from DATA.
DATA can be a question or an answer. Interactively, it is guessed
from context at point."
- (interactive (list (sx--data-here)))
+ (interactive (list (sx--error-if-unread (sx--data-here))))
(sx-assoc-let data
(sx-set-vote data "downvote" (null (eq .downvoted t)))))
@@ -157,7 +204,7 @@ it is guessed from context at point.
If DATA is a comment, the comment is posted as a reply to it.
TEXT is a string. Interactively, it is read from the minibufer."
- (interactive (list (sx--data-here) 'query))
+ (interactive (list (sx--error-if-unread (sx--data-here)) 'query))
;; When clicking the "Add a Comment" button, first arg is a marker.
(when (markerp data)
(setq data (sx--data-here))
@@ -222,13 +269,79 @@ OBJECT can be a question or an answer."
(setcdr
com-cell
(apply #'vector
- (append
- (cl-map 'list #'identity
- (cdr com-cell))
- (list comment)))))
+ (append
+ (cl-map 'list #'identity
+ (cdr com-cell))
+ (list comment)))))
;; No previous comments, add it manually.
(setcdr object (cons (car object) (cdr object)))
(setcar object `(comments . [,comment])))))
+
+;;; Editing
+(defun sx-edit (data)
+ "Start editing an answer or question given by DATA.
+DATA is an answer or question alist. Interactively, it is guessed
+from context at point."
+ (interactive (list (sx--data-here)))
+ ;; If we ever make an "Edit" button, first arg is a marker.
+ (when (markerp data) (setq data (sx--data-here)))
+ (sx-assoc-let data
+ (when .comment_id (user-error "Editing comments is not supported yet"))
+ (let ((buffer (current-buffer)))
+ (pop-to-buffer
+ (sx-compose-create
+ .site data nil
+ ;; After send functions
+ (list (lambda (_ res)
+ (sx--copy-data (elt res 0) data)
+ (sx--maybe-update-display buffer))))))))
+
+
+;;; Asking
+(defun sx-ask (site)
+ "Start composing a question for SITE.
+SITE is a string, indicating where the question will be posted."
+ (interactive (list (sx-tab--interactive-site-prompt)))
+ (let ((buffer (current-buffer)))
+ (pop-to-buffer
+ (sx-compose-create
+ site nil nil
+ ;; After send functions
+ (list (lambda (_ res) (sx--maybe-update-display buffer)))))))
+
+
+;;; Answering
+(defun sx-answer (data)
+ "Start composing an answer for question given by DATA.
+DATA is a question alist. Interactively, it is guessed from
+context at point. "
+ ;; If the user tries to answer a question that's not viewed, he
+ ;; probaby hit the button by accident.
+ (interactive
+ (list (sx--error-if-unread (sx--data-here 'question))))
+ ;; When clicking the "Write an Answer" button, first arg is a marker.
+ (when (markerp data) (setq data (sx--data-here)))
+ (let ((buffer (current-buffer)))
+ (sx-assoc-let data
+ (pop-to-buffer
+ (sx-compose-create
+ .site .question_id nil
+ ;; After send functions
+ (list (lambda (_ res)
+ (sx--add-answer-to-question-object
+ (elt res 0) sx-question-mode--data)
+ (sx--maybe-update-display buffer))))))))
+
+(defun sx--add-answer-to-question-object (answer question)
+ "Add alist ANSWER to alist QUESTION in the right place."
+ (let ((cell (assoc 'answers question)))
+ (if cell
+ (setcdr cell (apply #'vector
+ (append (cdr cell) (list answer))))
+ ;; No previous comments, add it manually.
+ (setcdr question (cons (car question) (cdr question)))
+ (setcar question `(answers . [,answer])))))
+
(provide 'sx-interaction)
;;; sx-interaction.el ends here