branch: elpa/mastodon
commit 3eebd4d41b41f8ac948de9948b524fcce1a11ab1
Merge: 317c218eab 319e1d11ed
Author: marty hiatt <[email protected]>
Commit: marty hiatt <[email protected]>
Merge branch 'quote-post' into develop
---
lisp/mastodon-notifications.el | 21 +++-
lisp/mastodon-profile.el | 18 ++++
lisp/mastodon-tl.el | 179 ++++++++++++++++++++++++----------
lisp/mastodon-toot.el | 213 +++++++++++++++++++++++++++++++----------
lisp/mastodon.el | 14 ++-
mastodon-index.org | 8 +-
6 files changed, 345 insertions(+), 108 deletions(-)
diff --git a/lisp/mastodon-notifications.el b/lisp/mastodon-notifications.el
index 58fbf009f3..f5c706d8c3 100644
--- a/lisp/mastodon-notifications.el
+++ b/lisp/mastodon-notifications.el
@@ -481,7 +481,7 @@ TYPE is notification type, used for non-group notifs."
"\n"
;; display quoted post:
(when (alist-get 'quote toot)
- (mastodon-tl--insert-quoted (alist-get 'quote toot)))
+ (mastodon-tl--insert-quoted (alist-get 'quote toot) toot))
;; actual byline:
(if (member type '("severed_relationships" "moderation_warning"))
(propertize
@@ -862,6 +862,25 @@ Uses `alert.el'."
;; we nil this in `mastodon-notifications-get':
(setq mastodon-notifications-notify-shown t))))
+;;; REVOKE QUOTED STATUS
+;; POST /api/v1/statuses/:id/quotes/:quoting_status_id/revoke.
+(defun mastodon-notifications-revoke-post-quote ()
+ "Revoke the quote of a post from a quote notification."
+ (interactive)
+ ;; SCOPE required: check quote notif type, which will have user post in
+ ;; the "status" attribute:
+ (let* ((notif (mastodon-tl--property 'item-json :no-move))
+ (id (map-nested-elt notif '(quote quoted_status id)))
+ (quote-id (alist-get 'id notif))
+ (url (mastodon-http--api (format "statuses/%s/quotes/%s/revoke"
+ id quote-id))))
+ (when (y-or-n-p "Revoke quote of post at point?")
+ (let ((resp (mastodon-http--post url)))
+ (mastodon-http--triage
+ resp
+ (lambda (resp)
+ (message "Quote of post revoked!")))))))
+
;;; NOTIFICATION REQUESTS / FILTERING / POLICY
(defvar mastodon-notifications--requests-map
diff --git a/lisp/mastodon-profile.el b/lisp/mastodon-profile.el
index f95908dc0b..a1fe54a800 100644
--- a/lisp/mastodon-profile.el
+++ b/lisp/mastodon-profile.el
@@ -571,6 +571,24 @@ The endpoint only holds a few preferences. For others, see
"\n\n"))
(goto-char (point-min)))))
+(defvar mastodon-profiles-quote-policy-types
+ '(public followers nobody))
+
+(defun mastodon-profile-set-quote-policy ()
+ "Prompt for a quote policy and set it in the user's preferences."
+ (interactive)
+ (let* ((prefs
+ ;; this fetches from a local var, we want to fetch from server,
+ ;; else we need this var to update when we change this setting:
+ ;; (mastodon-profile--get-source-values))
+ ;; so we fetch from preferences instead:
+ (mastodon-http--get-json (mastodon-http--api "preferences")))
+ (current (alist-get 'posting:default:quote_policy prefs))
+ (choice (completing-read
+ (format "Set default quote policy [current: %s]: " current)
+ mastodon-profiles-quote-policy-types nil :match)))
+ (mastodon-profile--update-preference "quote_policy" choice 'source)))
+
;;; PROFILE VIEW DETAILS
diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el
index 6b6b064440..14043e683c 100644
--- a/lisp/mastodon-tl.el
+++ b/lisp/mastodon-tl.el
@@ -97,12 +97,14 @@
(autoload 'mastodon-notifications--empty-group-json-p "mastodon-notifications")
(autoload 'mastodon-search--print-tags "mastodon-search")
(autoload 'mastodon-profile-show-user "mastodon-profile")
+(autoload 'mastodon-toot--own-toot-p "mastodon-toot")
(defvar mastodon-toot--visibility)
(defvar mastodon-toot-mode)
(defvar mastodon-active-user)
(defvar mastodon-images-in-notifs)
(defvar mastodon-group-notifications)
+(defvar mastodon-profiles-quote-policy-types)
(when (require 'mpv nil :no-error)
(declare-function mpv-start "mpv"))
@@ -807,8 +809,8 @@ The result is added as an attachments property to
author-byline."
(defun mastodon-tl--top-byline (toot)
"Format a boost or reply top (action) byline for TOOT.
If it is a self-reply, return 'continued thread'.
-If it is a non-self-reply, return 'in reply to $username'.
-If it is a boost, return '$username boosted'."
+If it is a non-self-reply, return \\='in reply to $username'.
+If it is a boost, return \\='$username boosted'."
(let ((reblog (alist-get 'reblog toot))
(reply-acc-id (alist-get 'in_reply_to_account_id toot)))
(cond
@@ -859,6 +861,7 @@ LETTER is a string, F for favourited, B for boosted, or K
for bookmarked."
(propertize letter 'face 'mastodon-boost-fave-face
;; emojify breaks this for ๐:
'help-echo (format "You have %s this status."
+ ;; FIXME: this is often nil
help-string)))))
(defun mastodon-tl--image-trans-check ()
@@ -1292,9 +1295,13 @@ LINK-TYPE is the type of link to produce."
Used for hitting RET on a given link."
(interactive "d")
(let ((link-type (get-text-property pos 'mastodon-tab-stop))
- (cont-thread (mastodon-tl--property 'continued-thread :nomove)))
+ (cont-thread (mastodon-tl--property 'continued-thread :nomove))
+ (quote-toot (mastodon-tl--property 'quote-url :nomove)))
(cond (cont-thread
(mastodon-tl-continued-thread-load))
+ (quote-toot
+ (let ((url (mastodon-tl--property 'quote-url :nomove)))
+ (mastodon-url-lookup url)))
((eq link-type 'content-warning)
(mastodon-tl--toggle-spoiler-text pos))
((eq link-type 'hashtag)
@@ -1426,23 +1433,28 @@ FILTER is a string to use as a filter warning spoiler
instead."
cw
(propertize
(mastodon-tl--content toot)
- 'invisible
- (or filter ;; filters = invis
- (let ((cust mastodon-tl--expand-content-warnings))
- (if (not (eq 'server cust))
- (not cust) ;; opp to setting
- ;; respect server setting:
- ;; If something goes wrong reading prefs,
- ;; just return t so CWs fold by default.
- (condition-case nil
- (if (eq :json-false
- (mastodon-profile--get-preferences-pref
- 'reading:expand:spoilers))
- t
- nil)
- (error t)))))
+ 'invisible (mastodon-tl--spoiler-invisible-maybe filter)
'mastodon-content-warning-body t))))
+(defun mastodon-tl--spoiler-invisible-maybe (&optional filter)
+ "Set the invisible property for a post with a spoiler.
+Also used to set invisibility for quoted posts.
+We respect `mastodon-tl--expand-content-warnings'.
+If it is server, we check the user's preference.
+FILTER means we go invisible."
+ (or filter ;; filters = invis
+ (let ((cust mastodon-tl--expand-content-warnings))
+ (if (not (eq 'server cust))
+ (not cust) ;; opp to setting
+ ;; respect server setting:
+ ;; If something goes wrong reading prefs,
+ ;; just return t so CWs fold by default.
+ (condition-case nil
+ (eq :json-false
+ (mastodon-profile--get-preferences-pref
+ 'reading:expand:spoilers))
+ (error t))))))
+
;;; MEDIA
@@ -1870,9 +1882,18 @@ in which case play first video or gif from current toot."
"Retrieve text content from TOOT.
Runs `mastodon-tl--render-text' and fetches poll or media."
(let* ((content (mastodon-tl--field 'content toot))
+ (quote-p (mastodon-tl--field 'quote toot))
+ (rendered (mastodon-tl--render-text content toot))
+ (stripped-maybe (if (not quote-p)
+ rendered
+ (with-temp-buffer ;; strip quoted toot URL:
+ (insert rendered)
+ (goto-char (point-min))
+ (kill-line 2)
+ (buffer-string))))
(poll-p (mastodon-tl--field 'poll toot))
(media-p (mastodon-tl--field 'media_attachments toot)))
- (concat (mastodon-tl--render-text content toot)
+ (concat stripped-maybe
(when poll-p
(mastodon-tl--format-poll
(mastodon-tl--field 'poll toot))) ;; toot or reblog
@@ -1912,26 +1933,89 @@ Runs `mastodon-tl--render-text' and fetches poll or
media."
(goto-char (prop-match-end prop)))))
list))
-(defun mastodon-tl--insert-quoted (data)
- "Propertize quoted status DATA for insertion."
- (let ((bar (concat " " (mastodon-tl--symbol 'reply-bar)))
- (content (map-nested-elt data '(quoted_status content)))
- ;; quote symbol hack:
- (quotemark (propertize "โ" 'face
- '(t :inherit success :weight bold
- :height 1.8))))
- (propertize
- (concat quotemark "\n"
- ;; author byline without horiz bar and toot stats:
- (mastodon-tl--byline-author
- (alist-get 'quoted_status data) nil :domain :base)
- "\n"
- ;; quoted text:
- (mastodon-tl--render-text content
- (alist-get 'quoted_status data)))
- 'line-prefix bar
- 'wrap-prefix bar
- 'mastodon-quote data)))
+(defvar mastodon-tl--quote-states
+ '(pending accepted rejected revoked deleted
+ unauthorized blocked_account blocked_domain muted_account)
+ "A list of possible values for a quote state attribute.
+See https://docs.joinmastodon.org/entities/Quote/#state for details.")
+
+(defun mastodon-tl--insert-quoted (data toot)
+ "Propertize quoted status DATA for insertion.
+TOOT is the data for the quoting toot."
+ (let* ((bar (concat " " (mastodon-tl--symbol 'reply-bar)))
+ (state (alist-get 'state data))
+ ;; CW status of quoting toot:
+ (cw (not (string-empty-p
+ (mastodon-tl--field 'spoiler_text toot))))
+ ;; quote symbol hack:
+ (quotemark (propertize "โ" 'face
+ '( :inherit success :weight bold
+ :height 1.8)))
+ (quoted (alist-get 'quoted_status data)))
+ (let-alist quoted
+ (let ((filters (when .filtered
+ (mastodon-tl--current-filters .filtered))))
+ ;; TODO: tailor non-disply of quote based on quote 'state'
+ ;; `mastodon-tl--quote-states':
+ (when (or (string= "pending" state)
+ (string= "accepted" state))
+ (propertize
+ (if-let* ((match (or (assoc "warn" filters)
+ (assoc "hide" filters))))
+ ;; FIXME: "warn" should result in CW, but it should be
+ ;; a CW independent of post CW:
+ (concat "\n\n" quotemark "\n"
+ "Quote hidden due to one of your filters")
+ (concat
+ "\n" quotemark "\n"
+ ;; author byline without horiz bar/stats:
+ (if (string= state "pending")
+ "[quote pending]"
+ (concat
+ (mastodon-tl--byline-author quoted nil :domain :base)
+ "\n"
+ (propertize ;; buttonize quoted toot body
+ ;; quoted text:
+ (mastodon-tl--content quoted)
+ 'button t
+ 'keymap mastodon-tl--link-keymap
+ 'help-echo "Load quoted toot"
+ 'mouse-face '(:inherit (highlight link) :underline nil))))))
+ 'line-prefix bar
+ 'wrap-prefix bar
+ 'quote-url .url
+ 'mastodon-content-warning-body (when cw t)
+ ;; TODO: respect filtering of quoted toot:
+ 'invisible (when cw (mastodon-tl--spoiler-invisible-maybe))
+ 'mastodon-quote data))))))
+
+;; PUT /api/v1/statuses/:id/interaction_policy
+(defun mastodon-tl--change-post-quote-policy ()
+ "Change the quote policy of the toot at point.
+Toot must be on you own."
+ (interactive)
+ ;; SCOPE: with own toot:
+ (let ((toot (mastodon-tl--property 'item-json :no-move)))
+ (if (not (mastodon-toot--own-toot-p toot))
+ (user-error "You can only set quote policy for your own posts")
+ (let* ((id (mastodon-tl--property 'item-id))
+ (current (car (map-nested-elt toot
+ '(quote_approval automatic))))
+ (choice (completing-read (format "Set quote policy [%s]: "
current)
+ mastodon-profiles-quote-policy-types
+ nil :match))
+ (url (mastodon-http--api (format "statuses/%s/interaction_policy"
+ id)))
+ (resp (mastodon-http--put
+ url `(("quote_approval_policy" . ,choice)))))
+ (mastodon-http--triage
+ resp
+ (lambda (resp)
+ (let* ((json (with-current-buffer resp
+ (mastodon-http--process-json)))
+ (set (or (car (map-nested-elt json '(quote_approval
automatic)))
+ "nobody"))) ;; nil on the server = nobody
+ (message "Quote policy for post updated to: %s!" set))))))))
(defun mastodon-tl--insert-status
(toot body &optional detailed-p thread domain unfolded no-byline
@@ -1953,9 +2037,8 @@ CW-EXPANDED means treat content warnings as unfolded."
(toot-foldable
(and mastodon-tl--fold-toots-at-length
(length> body mastodon-tl--fold-toots-at-length)))
- (cw-p (not
- (string-empty-p
- (alist-get 'spoiler_text toot))))
+ (cw-p (not (string-empty-p
+ (alist-get 'spoiler_text toot))))
(body-tags (mastodon-tl--body-tags body))
(quote (mastodon-tl--field 'quote toot)))
(insert
@@ -1981,11 +2064,8 @@ CW-EXPANDED means treat content warnings as unfolded."
'wrap-prefix bar)
body)
;; insert quote maybe:
- (when (and quote
- (not (string= "deleted" (alist-get 'state quote))))
- (concat "\n\n"
- (mastodon-tl--insert-quoted quote)
- ))))
+ (when quote
+ (mastodon-tl--insert-quoted quote toot))))
(if (and toot-foldable unfolded cw-expanded)
(mastodon-tl--read-more-or-less
"LESS" cw-p (not cw-expanded))
@@ -2036,12 +2116,15 @@ title, and context."
(defun mastodon-tl--filters-context ()
"Return a string of the current buffer's filter context.
Returns a member of `mastodon-views--filter-types'."
+ ;; FIXME: filters for bookmarks? = home?
(let ((buf (mastodon-tl--get-buffer-type)))
(cond ((or (eq buf 'local) (eq buf 'federated))
"public")
((mastodon-tl--profile-buffer-p)
"profile")
- ((eq buf 'list-timeline)
+ (
+ ;; (eq buf 'list-timeline)
+ (or (eq buf 'list-timeline) (eq buf 'bookmarks))
"home") ;; lists are "home" filter
(t ;; thread, notifs, home:
(symbol-name buf)))))
diff --git a/lisp/mastodon-toot.el b/lisp/mastodon-toot.el
index 68163d3736..9a9a7870cb 100644
--- a/lisp/mastodon-toot.el
+++ b/lisp/mastodon-toot.el
@@ -241,6 +241,9 @@ Takes its form from `window-configuration-to-register'.")
(defvar mastodon-toot-current-toot-text nil
"The text of the toot being composed.")
+(defvar-local mastodon-toot-quote-policy nil
+ "The quote policy for the current toot.")
+
(persist-defvar mastodon-toot-draft-toots-list nil
"A list of toots that have been saved as drafts.
For the moment we just put all composed toots in here, as we want
@@ -331,8 +334,9 @@ property, and call BODY-FUN on them."
(define-key map (kbd "C-c C-v") #'mastodon-toot-change-visibility)
(define-key map (kbd "C-c C-e") #'mastodon-toot-insert-emoji)
(define-key map (kbd "C-c C-a") #'mastodon-toot-attach-media)
- (define-key map (kbd "C-c !") #'mastodon-toot-clear-all-attachments)
+ (define-key map (kbd "C-c !") #'mastodon-toot-clear-all-attachments)
(define-key map (kbd "C-c C-p") #'mastodon-toot-create-poll)
+ (define-key map (kbd "C-c C-u") #'mastodon-toot-set-quote-policy)
(define-key map (kbd "C-c C-o") #'mastodon-toot-clear-poll)
(define-key map (kbd "C-c C-l") #'mastodon-toot-set-toot-language)
(define-key map (kbd "C-c C-s") #'mastodon-toot-schedule-toot)
@@ -734,7 +738,8 @@ MEDIA is the media_attachments data for a status from the
server."
media))
(defun mastodon-toot--set-toot-properties
- (reply-id visibility cw lang &optional scheduled scheduled-id media poll)
+ (reply-id visibility cw lang
+ &optional scheduled scheduled-id media poll quote-id)
"Set the toot properties for the current redrafted or edited toot.
REPLY-ID, VISIBILITY, CW, SCHEDULED, and LANG are the properties to set.
MEDIA is the media_attachments data for a status from the server."
@@ -751,6 +756,7 @@ MEDIA is the media_attachments data for a status from the
server."
(mastodon-toot--set-toot-media-attachments media))
(when poll
(mastodon-toot--server-poll-to-local poll))
+ (setq mastodon-toot-quote-id quote-id)
(mastodon-toot--refresh-attachments-display)
(mastodon-toot--update-status-fields)))
@@ -932,7 +938,9 @@ instance to edit a toot."
("sensitive" . ,(when mastodon-toot--content-nsfw
(symbol-name t)))
("spoiler_text" . ,mastodon-toot--content-warning)
- ("language" . ,mastodon-toot--language))
+ ("quote_approval_policy" . ,mastodon-toot-quote-policy)
+ ("language" . ,mastodon-toot--language)
+ ("quoted_status_id" . ,mastodon-toot-quote-id))
;; Pleroma instances can't handle null-valued
;; scheduled_at args, so only add if non-nil
(when scheduled `(("scheduled_at" . ,scheduled)))))
@@ -1006,17 +1014,19 @@ instance to edit a toot."
(user-error "You can only edit your own toots")
(let* ((source (mastodon-toot--get-toot-source id))
(content (alist-get 'text source))
- (source-cw (alist-get 'spoiler_text source)))
+ (source-cw (alist-get 'spoiler_text source))
+ (quote (alist-get 'quote toot)))
(let-alist toot
(when (y-or-n-p "Edit this toot? ")
(mastodon-toot--compose-buffer nil .in_reply_to_id nil
content :edit)
(goto-char (point-max))
;; adopt reply-to-id, visibility, CW, language, and media:
- (mastodon-toot--set-toot-properties .in_reply_to_id .visibility
- source-cw .language nil nil
- ;; maintain media order:
- (reverse .media_attachments)
.poll)
+ (mastodon-toot--set-toot-properties
+ .in_reply_to_id .visibility source-cw .language nil nil
+ ;; maintain media order:
+ (reverse .media_attachments) .poll
+ (map-nested-elt quote '(quoted_status id)))
(setq mastodon-toot--edit-item-id id))))))))
(defun mastodon-toot--get-toot-source (id)
@@ -1296,6 +1306,20 @@ Return its two letter ISO 639 1 code."
(message "Language set to %s" choice)
(mastodon-toot--update-status-fields)))
+(defun mastodon-toot-set-quote-policy ()
+ "Set quote policy for the current toot."
+ (interactive)
+ (let* ((default (alist-get 'posting:default:quote_policy
+ (mastodon-http--get-json
+ (mastodon-http--api "preferences"))))
+ (choice (completing-read
+ (format "Quote policy for this toot [default: %s]"
+ default)
+ mastodon-profiles-quote-policy-types)))
+ (setq mastodon-toot-quote-policy choice)
+ (message "Quote policy for this toot: " choice)
+ (mastodon-toot--update-status-fields)))
+
;;; ATTACHMENTS
@@ -1320,31 +1344,35 @@ If that fails, return 4 as a fallback"
(alist-get 'max_media_attachments config))
4)) ; mastodon default as fallback
-(defun mastodon-toot-attach-media (file description)
- "Prompt for an attachment FILE with DESCRIPTION.
+(defun mastodon-toot-attach-media ()
+ "Prompt for an attachment file.
A preview is displayed in the new toot buffer, and the file
is uploaded asynchronously using `mastodon-toot--upload-attached-media'.
File is actually attached to the toot upon posting."
- (interactive "fFilename: \nsDescription: ")
- (let ((max-attachments (mastodon-toot--get-instance-max-attachments)))
- (when (>= (length mastodon-toot--media-attachments)
- max-attachments)
- ;; warn + pop the oldest one:
- (when (y-or-n-p
- (format "Maximum attachments (%s) reached: remove first one?"
- max-attachments))
- (pop mastodon-toot--media-attachments)))
- (if (file-directory-p file)
- (user-error "Looks like you chose a directory not a file")
- (setq mastodon-toot--media-attachments
- (nconc mastodon-toot--media-attachments
- `(((:contents . ,(mastodon-http--read-file-as-string file))
- (:description . ,description)
- (:filename . ,file)))))
- (mastodon-toot--refresh-attachments-display)
- ;; upload only most recent attachment:
- (mastodon-toot--upload-attached-media
- (car (last mastodon-toot--media-attachments))))))
+ (interactive)
+ (if mastodon-toot-quote-id
+ (user-error "You can't add attachments to quote toots.")
+ (let ((file (read-file-name "Filename: "))
+ (description (read-string "Description: "))
+ (max-attachments (mastodon-toot--get-instance-max-attachments)))
+ (when (>= (length mastodon-toot--media-attachments)
+ max-attachments)
+ ;; warn + pop the oldest one:
+ (when (y-or-n-p
+ (format "Maximum attachments (%s) reached: remove first one?"
+ max-attachments))
+ (pop mastodon-toot--media-attachments)))
+ (if (file-directory-p file)
+ (user-error "Looks like you chose a directory not a file")
+ (setq mastodon-toot--media-attachments
+ (nconc mastodon-toot--media-attachments
+ `(((:contents . ,(mastodon-http--read-file-as-string
file))
+ (:description . ,description)
+ (:filename . ,file)))))
+ (mastodon-toot--refresh-attachments-display)
+ ;; upload only most recent attachment:
+ (mastodon-toot--upload-attached-media
+ (car (last mastodon-toot--media-attachments)))))))
(defun mastodon-toot--attachment-descriptions ()
"Return a list of image descriptions for current attachments."
@@ -1444,9 +1472,11 @@ MAX is the maximum number set by their instance."
(defun mastodon-toot-create-poll ()
"Prompt for new poll options and return as a list."
(interactive)
- (if mastodon-toot-poll-use-transient
- (call-interactively #'mastodon-create-poll)
- (mastodon-toot--read-poll)))
+ (cond (mastodon-toot-quote-id
+ (user-error "You can't add a poll to a quote toot."))
+ (mastodon-toot-poll-use-transient
+ (call-interactively #'mastodon-create-poll))
+ (t (mastodon-toot--read-poll))))
(defun mastodon-toot--read-poll ()
"Read poll options."
@@ -1548,6 +1578,50 @@ If TRANSIENT, we are called from a transient, so nil
`( :options ,options :expiry-readable ,expiry-human
:expiry ,expiry-str :multi ,multiple))))))
+;;; QUOTE
+
+(defvar mastodon-toot-quote-approval-user
+ '("automatic" "manual" "denied" "unknown"))
+
+(defvar mastodon-toot-quote-approval-auto-or-manual
+ '("public" "followers" "following" "unsupported_policy"))
+
+(defvar-local mastodon-toot-quote-id nil)
+
+(defun mastodon-toot-quote ()
+ "Compose a toot quoting the toot at point."
+ (interactive)
+ ;; boosted toot or toot:
+ (let* ((quote-id (mastodon-tl--property 'base-item-id))
+ (json (mastodon-tl--property 'item-json))
+ (policy (alist-get 'quote_approval json))
+ (user-policy (alist-get 'current_user policy))
+ ;; Respect visibility when quoting a toot:
+ ;; According to web UI settings (preferences/posting defaults):
+
+ ;; - quoting an unlisted ("quiet public") post, means quoting post
also unlisted
+ ;; "When people quote you, their post will also be hidden from
trending timelines."
+
+ ;; - private ("followers only") means no quoting allowed
+ ;; "Followers-only posts authored on Mastodon can't be quoted by
others."
+ ;; BUT: we don't need to enforce this, as user-policy will be "denied"
+ ;; if toot is "private"
+
+ ;; "Quoting a private post will restrict the quoting postโs
+ ;; visibility to private or direct (if the given visibility is
+ ;; public or unlisted, private will be used instead)."
+
+ ;;
<https://docs.joinmastodon.org/methods/statuses/#form-data-parameters>
+
+ ;; for now all we do is hand on quoted toot's visibility:
+ (visibility (mastodon-tl--field 'visibility json)))
+ (if (string= user-policy "denied")
+ (user-error "You don't have permission to quote this toot.")
+ (when (or (not (string= user-policy "unknown"))
+ (y-or-n-p "Quote permission unknown. Proceed?"))
+ (mastodon-toot--compose-buffer nil nil nil nil nil
+ quote-id json visibility)))))
+
;;; SCHEDULE
@@ -1690,22 +1764,23 @@ LONGEST is the length of the longest binding."
(mastodon-toot--formatted-kbinds-pairs formatted longest-kbind)
nil))))
-(defun mastodon-toot--format-reply-in-compose (reply-text)
+(defun mastodon-toot--format-reply-in-compose (reply-text &optional quote-text)
"Format a REPLY-TEXT for display in compose buffer docs."
- (let* ((rendered (mastodon-tl--render-text reply-text))
+ (let* ((rendered (mastodon-tl--render-text (or quote-text reply-text)))
(no-props (substring-no-properties rendered))
;; FIXME: this replaces \n at end of every post, so we have to trim:
(no-newlines (string-trim
(replace-regexp-in-string "[\n]+" " " no-props)))
- (reply-to (concat " Reply to: \"" no-newlines "\""))
- (crop (truncate-string-to-width reply-to
- mastodon-toot-orig-in-reply-length)))
+ (prompt (concat (if quote-text " Quoting: \"" " Reply to: \"")
+ no-newlines "\""))
+ (crop (truncate-string-to-width
+ prompt mastodon-toot-orig-in-reply-length)))
(if (> (length no-newlines)
(length crop)) ; we cropped:
(concat crop "\n")
- (concat reply-to "\n"))))
+ (concat prompt "\n"))))
-(defun mastodon-toot--display-docs-and-status-fields (&optional reply-text)
+(defun mastodon-toot--display-docs-and-status-fields (&optional reply-text
quote-text)
"Insert propertized text with documentation about `mastodon-toot-mode'.
Also includes and the status fields which will get updated based
on the status of NSFW, content warning flags, media attachments, etc.
@@ -1739,15 +1814,23 @@ REPLY-TEXT is the text of the toot being replied to."
" "
(propertize "NSFW"
'toot-post-nsfw-flag t)
+ " "
+ (propertize "Quoting"
+ 'toot-quote-policy t)
"\n"
" Attachments: "
(propertize "None "
'toot-attachments t)
"\n"
- (when reply-text
- (propertize
- (mastodon-toot--format-reply-in-compose reply-text)
- 'toot-reply t))
+ (cond (reply-text
+ (propertize
+ (mastodon-toot--format-reply-in-compose reply-text)
+ 'toot-reply t))
+ (quote-text
+ (let ((text (mastodon-toot--format-reply-in-compose
+ nil quote-text)))
+ (propertize text
+ 'toot-quote text))))
divider)
'face 'mastodon-toot-docs-face
'read-only "Edit your message below."
@@ -1823,8 +1906,16 @@ REPLY-REGION is a string to be injected into the buffer."
(point-min)))
(poll-region (mastodon-tl--find-property-range 'toot-post-poll-flag
(point-min)))
+ (quote-pol-region (mastodon-tl--find-property-range
'toot-quote-policy
+ (point-min)))
(toot-string (buffer-substring-no-properties (cdr header-region)
- (point-max))))
+ (point-max)))
+ (toot-quote (mastodon-tl--find-property-range 'toot-quote
+ (point-min)))
+ (quote-text (when toot-quote
+ (save-excursion
+ (goto-char (car toot-quote))
+ (mastodon-tl--property 'toot-quote :nomove)))))
(mastodon-toot--apply-fields-props
count-region
(format "%s/%s chars"
@@ -1869,7 +1960,15 @@ REPLY-REGION is a string to be injected into the buffer."
(not (string= "" mastodon-toot--content-warning)))
(format "CW: %s" mastodon-toot--content-warning)
" ") ;; hold the blank space
- 'mastodon-cw-face))))
+ 'mastodon-cw-face)
+ (mastodon-toot--apply-fields-props
+ quote-pol-region
+ (if mastodon-toot-quote-policy
+ (format "Quoting: %s" mastodon-toot-quote-policy)
+ "")
+ 'mastodon-cw-face)
+ (mastodon-toot--apply-fields-props
+ toot-quote quote-text 'mastodon-cw-face))))
(defun mastodon-toot--apply-fields-props (region display &optional face
help-echo)
"Apply DISPLAY props FACE and HELP-ECHO to REGION, a cons of beg and end."
@@ -1999,7 +2098,8 @@ Added to `after-change-functions'."
;;; COMPOSE BUFFER FUNCTION
(defun mastodon-toot--compose-buffer
- (&optional reply-to-user reply-to-id reply-json initial-text edit)
+ (&optional reply-to-user reply-to-id reply-json initial-text edit
+ quote-id quote-json visibility)
"Create a new buffer to capture text for a new toot.
If REPLY-TO-USER is provided, inject their handle into the message.
If REPLY-TO-ID is provided, set the `mastodon-toot--reply-to-id' var.
@@ -2027,9 +2127,13 @@ EDIT means we are editing an existing toot, not
composing a new one."
(switch-to-buffer-other-window buffer)
(text-mode)
(mastodon-toot-mode t)
+ ;; set quote id:
+ (when quote-id
+ (setq mastodon-toot-quote-id quote-id))
;; set visibility:
(setq mastodon-toot--visibility
- (or (plist-get mastodon-profile-account-settings 'privacy)
+ (or visibility ;; quoting a toot
+ (plist-get mastodon-profile-account-settings 'privacy)
;; use toot visibility setting from the server:
(mastodon-profile--get-source-value 'privacy)
"public")) ; fallback
@@ -2040,11 +2144,14 @@ EDIT means we are editing an existing toot, not
composing a new one."
(setq mastodon-toot--language
(mastodon-profile--get-preferences-pref 'posting:default:language))
;; display original toot:
- (if mastodon-toot-display-orig-in-reply-buffer
- (progn
- (mastodon-toot--display-docs-and-status-fields reply-text)
- (mastodon-toot--fill-reply-in-compose))
- (mastodon-toot--display-docs-and-status-fields))
+ (cond (quote-id
+ (mastodon-toot--display-docs-and-status-fields
+ nil (mastodon-tl--field 'content quote-json))
+ (mastodon-toot--fill-reply-in-compose))
+ (mastodon-toot-display-orig-in-reply-buffer
+ (mastodon-toot--display-docs-and-status-fields reply-text)
+ (mastodon-toot--fill-reply-in-compose))
+ (t (mastodon-toot--display-docs-and-status-fields)))
;; `reply-to-user' (alone) is also used by `mastodon-tl-dm-user', so
;; perhaps we should not always call --setup-as-reply, or make its
;; workings conditional on reply-to-id. currently it only checks for
diff --git a/lisp/mastodon.el b/lisp/mastodon.el
index 42fac384c4..10bcbba54f 100644
--- a/lisp/mastodon.el
+++ b/lisp/mastodon.el
@@ -258,10 +258,11 @@ Also nil `mastodon-auth--token-alist'."
(define-key map (kbd "l") #'recenter-top-bottom)
;; navigation between timelines
(define-key map (kbd "#") #'mastodon-tl-get-tag-timeline)
- (define-key map (kbd "\"") #'mastodon-tl-list-followed-tags)
- (define-key map (kbd "C-\"") #'mastodon-tl-jump-to-followed-tag)
+ (define-key map (kbd "C-#") #'mastodon-tl-list-followed-tags)
+ ;; (define-key map (kbd "\"") #'mastodon-tl-list-followed-tags)
+ (define-key map (kbd "C-\"") #'mastodon-tl-jump-to-followed-tag)
(define-key map (kbd "'") #'mastodon-tl-followed-tags-timeline)
- (define-key map (kbd "C-'") #'mastodon-tl-tag-group-timeline)
+ (define-key map (kbd "C-'") #'mastodon-tl-tag-group-timeline)
(define-key map (kbd "A") #'mastodon-profile-get-toot-author)
(define-key map (kbd "F") #'mastodon-tl-get-federated-timeline)
(define-key map (kbd "H") #'mastodon-tl-get-home-timeline)
@@ -283,6 +284,7 @@ Also nil `mastodon-auth--token-alist'."
(define-key map (kbd "f") #'mastodon-toot-toggle-favourite)
(define-key map (kbd "k") #'mastodon-toot-toggle-bookmark)
(define-key map (kbd "r") #'mastodon-toot-reply)
+ (define-key map (kbd "\"") #'mastodon-toot-quote)
(define-key map (kbd "C") #'mastodon-toot-copy-toot-url)
(define-key map (kbd "o") #'mastodon-toot-browse-toot-url)
(define-key map (kbd "v") #'mastodon-tl-poll-vote)
@@ -443,13 +445,15 @@ FORCE means to fetch from the server in any case and
update
(alist-get 'version (mastodon-instance-data)))
;;;###autoload
-(defun mastodon-toot (&optional user reply-to-id reply-json)
+(defun mastodon-toot (&optional user reply-to-id reply-json
+ quote-id quote-json visibility)
"Update instance with new toot. Content is captured in a new buffer.
If USER is non-nil, insert after @ symbol to begin new toot.
If REPLY-TO-ID is non-nil, attach new toot to a conversation.
If REPLY-JSON is the json of the toot being replied to."
(interactive)
- (mastodon-toot--compose-buffer user reply-to-id reply-json))
+ (mastodon-toot--compose-buffer user reply-to-id reply-json
+ nil nil quote-id quote-json visibility))
;;;###autoload
(defun mastodon-notifications-get (&optional type buffer-name max-id)
diff --git a/mastodon-index.org b/mastodon-index.org
index 8d41afdcde..1f5fca03b3 100644
--- a/mastodon-index.org
+++ b/mastodon-index.org
@@ -98,6 +98,7 @@
| | mastodon-notifications-request-accept | Accept a
notification request for a user. |
| | mastodon-notifications-request-reject | Reject a
notification request for a user. |
| C-S-n | mastodon-notifications-requests | Open a new
buffer displaying the user's notification requests. |
+| | mastodon-notifications-revoke-post-quote | Revoke the
quote of a post from a quote notification. |
| | mastodon-profile-account-bot-toggle | Toggle the
bot status of your account. |
| | mastodon-profile-account-discoverable-toggle | Toggle the
discoverable status of your account. |
| | mastodon-profile-account-locked-toggle | Toggle the
locked status of your account. |
@@ -121,6 +122,7 @@
| | mastodon-profile-remove-from-followers-at-point | Prompt for a
user in the item at point and remove from followers. |
| | mastodon-profile-remove-from-followers-list | Select a
user from your followers and remove from followers. |
| | mastodon-profile-remove-user-from-followers | Remove a
user from your followers. |
+| | mastodon-profile-set-quote-policy | Prompt for a
quote policy and set it in the user's preferences. |
| | mastodon-profile-show-familiar-followers | Show a list
of familiar followers. |
| P | mastodon-profile-show-user | Query for
USER-HANDLE from current status and show that user's profile. |
| | mastodon-profile-update-display-name | Update
display name for your account. |
@@ -142,10 +144,12 @@
| | mastodon-search-trending-statuses | Display a
list of statuses trending on your instance. |
| | mastodon-search-trending-tags | Display a
list of tags trending on your instance. |
| / | mastodon-switch-to-buffer | Switch to a
live mastodon buffer. |
+| | mastodon-tl--change-post-quote-policy | Change the
quote policy of the toot at point. |
| | mastodon-tl-announcements | Display
announcements from your instance. |
| | mastodon-tl-block-domain | Read a
domain and block it. |
| B | mastodon-tl-block-user | Query for
USER-HANDLE from current status and block that user. |
| <mouse-2> | mastodon-tl-click-image-or-video | Click to
play video with `mpv.el'. |
+| | mastodon-tl-continued-thread-load | Load thread
based on prop item-id. |
| C | mastodon-tl-copy-image-caption | Copy the
caption of the image at point. |
| | mastodon-tl-disable-notify-user-posts | Query for
USER-HANDLE and disable notifications when they post. |
| m | mastodon-tl-dm-user | Query for
USER-HANDLE from current status and compose a message to that user. |
@@ -207,7 +211,7 @@
| | mastodon-tl-view-single-toot | View toot at
point in a separate buffer. |
| | mastodon-tl-view-whole-thread | From a
thread view, view entire thread. |
| C-c m t, t | mastodon-toot | Update
instance with new toot. Content is captured in a new buffer. |
-| C-c C-a | mastodon-toot-attach-media | Prompt for
an attachment FILE with DESCRIPTION. |
+| C-c C-a | mastodon-toot-attach-media | Prompt for
an attachment file. |
| o | mastodon-toot-browse-toot-url | Browse URL
of toot at point. |
| C-c C-k | mastodon-toot-cancel | Kill
new-toot buffer/window. Does not POST content. |
| C-c C-v | mastodon-toot-change-visibility | Change the
current visibility to the next valid value. |
@@ -230,12 +234,14 @@
| | mastodon-toot-mode | Minor mode
for composing toots. |
| | mastodon-toot-open-draft-toot | Prompt for a
draft and compose a toot with it. |
| i | mastodon-toot-pin-toot-toggle | Pin or unpin
user's toot at point. |
+| " | mastodon-toot-quote | Compose a
toot quoting the toot at point. |
| r | mastodon-toot-reply | Reply to
toot at `point'. |
| | mastodon-toot-save-draft | Save the
current compose toot text as a draft. |
| C-c C-s | mastodon-toot-schedule-toot | Read a date
(+ time) in the minibuffer and schedule the current toot. |
| C-c C-c | mastodon-toot-send | POST
contents of new-toot buffer to fediverse instance and kill buffer. |
| C-c C-w | mastodon-toot-set-content-warning | Set a
content warning for the current toot. |
| | mastodon-toot-set-default-visibility | Set the
default visibility for toots on the server. |
+| C-c C-u | mastodon-toot-set-quote-policy | Set quote
policy for the current toot. |
| C-c C-l | mastodon-toot-set-toot-language | Prompt for a
language and set `mastodon-toot--language'. |
| k | mastodon-toot-toggle-bookmark | Bookmark or
unbookmark toot at point. |
| b | mastodon-toot-toggle-boost |
Boost/unboost toot at `point'. |