aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.org4
-rw-r--r--lisp/mastodon-notifications.el19
-rw-r--r--lisp/mastodon-tl.el38
-rw-r--r--lisp/mastodon-toot.el103
-rw-r--r--lisp/mastodon.el10
5 files changed, 155 insertions, 19 deletions
diff --git a/README.org b/README.org
index bfb1641..c6df5ed 100644
--- a/README.org
+++ b/README.org
@@ -150,10 +150,12 @@ take place if your =mastodon-token-file= does not contain =:client_id= and
| =v= | Vote on poll at point |
| =C= | copy url of toot at point |
| =C-RET= | play video/gif at point (requires =mpv=) |
+| =e= | edit your toot at point |
+| =E= | view edits of toot at point |
| =i= | (un)pin your toot at point |
| =d= | delete your toot at point, and reload current timeline |
| =D= | delete and redraft toot at point, preserving reply/CW/visibility |
-| (=S-C=) =W=, =M=, =B= | (un)follow, (un)mute, (un)block author of toot at point |
+| (=S-C-=) =W=, =M=, =B= | (un)follow, (un)mute, (un)block author of toot at point |
|---------------+-----------------------------------------------------------------------|
| | Notifications view |
| =a=, =j= | accept/reject follow request |
diff --git a/lisp/mastodon-notifications.el b/lisp/mastodon-notifications.el
index 127a9e2..c4570ea 100644
--- a/lisp/mastodon-notifications.el
+++ b/lisp/mastodon-notifications.el
@@ -61,7 +61,8 @@
("mention" . mastodon-notifications--mention)
("poll" . mastodon-notifications--poll)
("follow_request" . mastodon-notifications--follow-request)
- ("status" . mastodon-notifications--status))
+ ("status" . mastodon-notifications--status)
+ ("update" . mastodon-notifications--edit))
"Alist of notification types and their corresponding function.")
(defvar mastodon-notifications--response-alist
@@ -71,7 +72,8 @@
("Mentioned" . "you")
("Posted a poll" . "that has now ended")
("Requested to follow" . "you")
- ("Posted" . "a post"))
+ ("Posted" . "a post")
+ ("Edited" . "a post"))
"Alist of subjects for notification types.")
(defvar mastodon-notifications--map
@@ -172,6 +174,10 @@ Status notifications are given when
"Format for a `poll' NOTE."
(mastodon-notifications--format-note note 'poll))
+(defun mastodon-notifications--edit (note)
+ "Format for an `edit' NOTE."
+ (mastodon-notifications--format-note note 'edit))
+
(defun mastodon-notifications--format-note (note type)
"Format for a NOTE of TYPE."
(let ((id (alist-get 'id note))
@@ -196,7 +202,7 @@ Status notifications are given when
"Congratulations, you have a new follower!"
(format "You have a follow request from... %s"
follower))
- 'face 'default)
+ 'face 'default)
(mastodon-tl--clean-tabs-and-nl
(if (mastodon-tl--has-spoiler status)
(mastodon-tl--spoiler status)
@@ -223,7 +229,9 @@ Status notifications are given when
((equal type 'status)
"Posted")
((equal type 'poll)
- "Posted a poll"))))
+ "Posted a poll")
+ ((equal type 'edit)
+ "Edited"))))
id
(when (or (equal type 'favourite)
(equal type 'boost))
@@ -314,9 +322,6 @@ Status notifications are created when you call
(defun mastodon-notifications--filter-types-list (type)
"Return a list of notification types with TYPE removed."
(let ((types
- ;; the docs don't mention "status" as an options
- ;; but we do need to exclude it, so keep it in the list here
- ;;(remove "status"
(mapcar #'car mastodon-notifications--types-alist)))
(remove type types)))
diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el
index 56001db..34048e7 100644
--- a/lisp/mastodon-tl.el
+++ b/lisp/mastodon-tl.el
@@ -604,7 +604,9 @@ this just means displaying toot client."
"K"))
(visibility (mastodon-tl--field 'visibility toot))
(account (alist-get 'account toot))
- (avatar-url (alist-get 'avatar account)))
+ (avatar-url (alist-get 'avatar account))
+ (edited-time (alist-get 'edited_at toot))
+ (edited-parsed (when edited-time (date-to-time edited-time))))
(concat
;; Boosted/favourited markers are not technically part of the byline, so
;; we don't propertize them with 'byline t', as per the rest. This
@@ -632,6 +634,7 @@ this just means displaying toot client."
;; we propertize help-echo format faves for author name
;; in `mastodon-tl--byline-author'
(funcall author-byline toot)
+ ;; visibility:
(cond ((equal visibility "direct")
(if (fontp (char-displayable-p #10r9993))
" ✉"
@@ -640,6 +643,7 @@ this just means displaying toot client."
(if (fontp (char-displayable-p #10r128274))
" 🔒"
" [followers]")))
+ ;; action:
(funcall action-byline toot)
" "
;; TODO: Once we have a view for toot (responses etc.) make
@@ -665,12 +669,44 @@ this just means displaying toot client."
'shr-url app-url
'help-echo app-url
'keymap mastodon-tl--shr-map-replacement)))))
+ (when edited-time
+ (concat
+ (if (fontp (char-displayable-p #10r128274))
+ " ✍ "
+ " [edited] ")
+ (propertize
+ (format-time-string mastodon-toot-timestamp-format
+ edited-parsed)
+ 'face 'font-lock-comment-face
+ 'timestamp edited-parsed
+ 'display (if mastodon-tl--enable-relative-timestamps
+ (mastodon-tl--relative-time-description edited-parsed)
+ edited-parsed))))
(propertize "\n ------------\n" 'face 'default))
'favourited-p faved
'boosted-p boosted
'bookmarked-p bookmarked
+ 'edited edited-time
+ 'edit-history (when edited-time
+ (mastodon-toot--get-toot-edits (alist-get 'id toot)))
'byline t))))
+(defun mastodon-tl--format-edit-timestamp (timestamp)
+ "Convert edit TIMESTAMP into a descriptive string."
+ (let ((parsed (ts-human-duration
+ (ts-diff (ts-now) (ts-parse timestamp)))))
+ (cond ((> (plist-get parsed :days) 0)
+ (format "%s days ago" (plist-get parsed :days) (plist-get parsed :hours)))
+ ((> (plist-get parsed :hours) 0)
+ (format "%s hours ago" (plist-get parsed :hours) (plist-get parsed :minutes)))
+ ((> (plist-get parsed :minutes) 0)
+ (format "%s minutes ago" (plist-get parsed :minutes)))
+ (t ;; we failed to guess:
+ (format "%s days, %s hours, %s minutes ago"
+ (plist-get parsed :days)
+ (plist-get parsed :hours)
+ (plist-get parsed :minutes))))))
+
(defun mastodon-tl--format-faved-or-boosted-byline (letter)
"Format the byline marker for a boosted or favourited status.
LETTER is a string, F for favourited, B for boosted, or K for bookmarked."
diff --git a/lisp/mastodon-toot.el b/lisp/mastodon-toot.el
index f7fea75..5a735dc 100644
--- a/lisp/mastodon-toot.el
+++ b/lisp/mastodon-toot.el
@@ -81,6 +81,7 @@
(autoload 'mastodon-profile--fetch-server-account-settings-maybe "mastodon-profile")
(autoload 'mastodon-http--build-array-args-alist "mastodon-http")
(autoload 'mastodon-tl--get-endpoint "mastodon-tl")
+(autoload 'mastodon-http--put "mastodon-http")
;; for mastodon-toot--translate-toot-text
(autoload 'mastodon-tl--content "mastodon-tl")
@@ -171,6 +172,8 @@ change the setting on the server, see
(defvar-local mastodon-toot--reply-to-id nil
"Buffer-local variable to hold the id of the toot being replied to.")
+(defvar-local mastodon-toot--edit-toot-id nil
+ "The id of the toot being edited.")
(defvar-local mastodon-toot-previous-window-config nil
"A list of window configuration prior to composing a toot.
@@ -188,7 +191,7 @@ For the moment we just put all composed toots in here, as we want
to also capture toots that are 'sent' but that don't successfully
send.")
-(defvar mastodon-handle-regex
+(defvar mastodon-toot-handle-regex
(concat
;; preceding space or bol [boundary doesn't work with @]
"\\([\n\t ]\\|^\\)"
@@ -637,13 +640,22 @@ to `emojify-user-emojis', and the emoji data is updated."
(defun mastodon-toot--send ()
"POST contents of new-toot buffer to Mastodon instance and kill buffer.
If media items have been attached and uploaded with
-`mastodon-toot--attach-media', they are attached to the toot."
+`mastodon-toot--attach-media', they are attached to the toot.
+If `mastodon-toot--edit-toot-id' is non-nil, PUT contents to
+instance to edit a toot."
(interactive)
- (let* ((toot (mastodon-toot--remove-docs))
- (endpoint (mastodon-http--api "statuses"))
+ (let* ((edit-p (if mastodon-toot--edit-toot-id t nil))
+ (toot (mastodon-toot--remove-docs))
+ (endpoint
+ (if edit-p
+ ;; we are sending an edit:
+ (mastodon-http--api (format "statuses/%s"
+ mastodon-toot--edit-toot-id))
+ (mastodon-http--api "statuses")))
(spoiler (when (and (not (mastodon-toot--empty-p))
mastodon-toot--content-warning)
- (read-string "Warning: " mastodon-toot--content-warning-from-reply-or-redraft)))
+ (read-string "Warning: "
+ mastodon-toot--content-warning-from-reply-or-redraft)))
(args-no-media `(("status" . ,toot)
("in_reply_to_id" . ,mastodon-toot--reply-to-id)
("visibility" . ,mastodon-toot--visibility)
@@ -676,13 +688,86 @@ If media items have been attached and uploaded with
((mastodon-toot--empty-p)
(message "Empty toot. Cowardly refusing to post this."))
(t
- (let ((response (mastodon-http--post endpoint args)))
+ (let ((response (if edit-p
+ ;; we are sending an edit:
+ (mastodon-http--put endpoint args)
+ (mastodon-http--post endpoint args))))
(mastodon-http--triage response
(lambda ()
(mastodon-toot--kill)
(message "Toot toot!")
(mastodon-toot--restore-previous-window-config prev-window-config))))))))
+;; EDITING TOOTS:
+
+(defun mastodon-toot--edit-toot-at-point ()
+ "Edit the user's toot at point."
+ (interactive)
+ (let ((toot (or (mastodon-tl--property 'base-toot); fave/boost notifs
+ (mastodon-tl--property 'toot-json))))
+ (if (not (mastodon-toot--own-toot-p toot))
+ (message "You can only edit your own toots.")
+ (let* ((id (mastodon-tl--as-string (mastodon-tl--toot-id toot)))
+ (source (mastodon-toot--get-toot-source id))
+ (content (alist-get 'text source))
+ (source-cw (alist-get 'spoiler_text source))
+ (toot-visibility (alist-get 'visibility toot))
+ (reply-id (alist-get 'in_reply_to_id toot)))
+ (when (y-or-n-p "Edit this toot? ")
+ (mastodon-toot--compose-buffer)
+ (goto-char (point-max))
+ (insert content)
+ ;; adopt reply-to-id, visibility and CW:
+ (when reply-id
+ (setq mastodon-toot--reply-to-id reply-id))
+ (setq mastodon-toot--visibility toot-visibility)
+ (mastodon-toot--set-cw source-cw)
+ (mastodon-toot--update-status-fields)
+ (setq mastodon-toot--edit-toot-id id))))))
+
+(defun mastodon-toot--get-toot-source (id)
+ "Fetch the source JSON of toot with ID."
+ (let ((url (mastodon-http--api (format "/statuses/%s/source" id))))
+ (mastodon-http--get-json url :silent)))
+
+(defun mastodon-toot--get-toot-edits (id)
+ "Return the edit history of toot with ID."
+ (let* ((url (mastodon-http--api (format "statuses/%s/history" id))))
+ (mastodon-http--get-json url)))
+
+(defun mastodon-toot--view-toot-edits ()
+ "View editing history of the toot at point in a popup buffer."
+ (interactive)
+ (let ((history (mastodon-tl--property 'edit-history)))
+ (with-current-buffer (get-buffer-create "*mastodon-toot-edits*")
+ (let ((inhibit-read-only t))
+ (special-mode)
+ (erase-buffer)
+ (let ((count 1))
+ (mapc (lambda (x)
+ (insert (propertize (if (= count 1)
+ (format "%s [original]:\n" count)
+ (format "%s:\n" count))
+ 'face 'font-lock-comment-face)
+ (mastodon-toot--insert-toot-iter x)
+ "\n")
+ (cl-incf count))
+ history))
+ (switch-to-buffer-other-window (current-buffer))
+ (setq-local header-line-format
+ (propertize
+ (format "Edits to toot by %s:"
+ (alist-get 'username
+ (alist-get 'account (car history))))
+ 'face font-lock-comment-face))))))
+
+(defun mastodon-toot--insert-toot-iter (it)
+ "Insert iteration IT of toot."
+ (let ((content (alist-get 'content it))
+ (account (alist-get 'account it)))
+ ;; TODO: handle polls, media
+ (mastodon-tl--render-text content)))
+
(defun mastodon-toot--restore-previous-window-config (config)
"Restore the window CONFIG after killing the toot compose buffer.
Buffer-local variable `mastodon-toot-previous-window-config' holds the config."
@@ -788,7 +873,7 @@ meta fields respectively."
(if (string= str-prefix "@")
(save-match-data
(save-excursion
- (re-search-backward mastodon-handle-regex nil :no-error)
+ (re-search-backward mastodon-toot-handle-regex nil :no-error)
(if (match-string-no-properties 2)
;; match full handle inc. domain (see the regex for subexp 2)
(buffer-substring-no-properties (match-beginning 2) (match-end 2))
@@ -1225,7 +1310,7 @@ REPLY-JSON is the full JSON of the toot being replied to."
'face 'mastodon-cw-face)))))
(defun mastodon-toot--count-toot-chars (toot-string)
- "Count the characters in the current toot.
+ "Count the characters in TOOT-STRING.
URLs always = 23, and domain names of handles are not counted.
This is how mastodon does it."
(with-temp-buffer
@@ -1310,7 +1395,7 @@ Added to `after-change-functions'."
'success
(cdr header-region))
(mastodon-toot--propertize-item
- mastodon-handle-regex
+ mastodon-toot-handle-regex
'mastodon-display-name-face
(cdr header-region)))))
diff --git a/lisp/mastodon.el b/lisp/mastodon.el
index 57d5bd4..5be168c 100644
--- a/lisp/mastodon.el
+++ b/lisp/mastodon.el
@@ -92,6 +92,10 @@
(autoload 'mastodon-toot--translate-toot-text "mastodon-toot"))
(autoload 'mastodon-search--trending-tags "mastodon-search")
(autoload 'mastodon-profile--fetch-server-account-settings "mastodon-profile")
+(autoload 'mastodon-notifications--get-mentions "mastodon-notifications")
+(autoload 'mastodon-tl--view-lists "mastodon-tl")
+(autoload 'mastodon-toot--edit-toot-at-point "mastodon-toot")
+(autoload 'mastodon-toot--view-toot-history "mastodon-tl")
(defgroup mastodon nil
"Interface with Mastodon."
@@ -195,6 +199,8 @@ Use. e.g. \"%c\" for your locale's date and time format."
(define-key map (kbd "G") #'mastodon-tl--get-follow-suggestions)
(define-key map (kbd "X") #'mastodon-tl--view-lists)
(define-key map (kbd "@") #'mastodon-notifications--get-mentions)
+ (define-key map (kbd "e") #'mastodon-toot--edit-toot-at-point)
+ (define-key map (kbd "E") #'mastodon-toot--view-toot-edits)
(when (require 'lingva nil :no-error)
(define-key map (kbd "s") #'mastodon-toot--translate-toot-text))
map)
@@ -248,7 +254,9 @@ Use. e.g. \"%c\" for your locale's date and time format."
(if buffer
(switch-to-buffer buffer)
(mastodon-tl--get-home-timeline)
- (message "Loading Mastodon account %s on %s..." (mastodon-auth--user-acct) mastodon-instance-url))))
+ (message "Loading Mastodon account %s on %s..."
+ (mastodon-auth--user-acct)
+ mastodon-instance-url))))
;;;###autoload
(defun mastodon-toot (&optional user reply-to-id reply-json)