branch: elpa/mastodon
commit 1dfe4368488d2b0539b7626b0c105343ca92322e
Merge: e2443f1cd4 38f814c3e5
Author: marty hiatt <martianhia...@disroot.org>
Commit: marty hiatt <martianhia...@disroot.org>

    Merge branch 'develop'
---
 README.org                     |   4 ++
 lisp/mastodon-auth.el          |   2 +-
 lisp/mastodon-http.el          |   7 +-
 lisp/mastodon-media.el         |   4 +-
 lisp/mastodon-notifications.el | 146 +++++++++++++++++++++++++++++++++++++----
 lisp/mastodon-profile.el       |  43 ++++++------
 lisp/mastodon-search.el        |  15 ++---
 lisp/mastodon-tl.el            | 111 ++++++++++++++++++-------------
 lisp/mastodon-toot.el          |  13 ++--
 lisp/mastodon-transient.el     |  79 +++++++++++++++++++---
 lisp/mastodon-views.el         |  10 ++-
 lisp/mastodon.el               |  10 ++-
 mastodon-index.org             |   9 ++-
 13 files changed, 342 insertions(+), 111 deletions(-)

diff --git a/README.org b/README.org
index 5096729c05..3daa3b8ad1 100644
--- a/README.org
+++ b/README.org
@@ -418,6 +418,10 @@ layout for =mastodon.el=.
 
 The repo is at [[https://github.com/rougier/mastodon-alt][mastodon-alt]].
 
+**** Org links, archive search
+
+[[https://codeberg.org/chrmoe/toot-suite][toot-suite]] implements an org link 
type for fediverse posts, and also provides a way to browse an offline archive 
of your account.
+
 **** Mastodon hydra
 
 A user made a hydra for handling basic =mastodon.el= commands. It's
diff --git a/lisp/mastodon-auth.el b/lisp/mastodon-auth.el
index 01639fb2bd..6e90b5348f 100644
--- a/lisp/mastodon-auth.el
+++ b/lisp/mastodon-auth.el
@@ -32,7 +32,7 @@
 (require 'plstore)
 (require 'auth-source)
 (require 'json)
-(eval-when-compile (require 'subr-x)) ; for if-let
+(eval-when-compile (require 'subr-x)) ; for if-let*
 
 (autoload 'mastodon-client "mastodon-client")
 (autoload 'mastodon-client--active-user "mastodon-client")
diff --git a/lisp/mastodon-http.el b/lisp/mastodon-http.el
index 47b96fed12..2c4c49d1bb 100644
--- a/lisp/mastodon-http.el
+++ b/lisp/mastodon-http.el
@@ -379,7 +379,12 @@ item uploaded, and `mastodon-toot--update-status-fields' 
is run."
                 (when data
                   (let* ((id (alist-get 'id data)))
                     ;; update ids:
-                    (push id mastodon-toot--media-attachment-ids)
+                    (if (not mastodon-toot--media-attachment-ids)
+                        ;; add first id:
+                        (push id mastodon-toot--media-attachment-ids)
+                      ;; add new id to end of list to preserve order:
+                      (push id (cdr
+                                (last mastodon-toot--media-attachment-ids))))
                     ;; pleroma, PUT the description:
                     ;; this is how the mangane akkoma web client does it
                     ;; and it seems easier than the other options!
diff --git a/lisp/mastodon-media.el b/lisp/mastodon-media.el
index cfb20de028..09692c311b 100644
--- a/lisp/mastodon-media.el
+++ b/lisp/mastodon-media.el
@@ -338,7 +338,7 @@ image-data prop so it can be toggled."
   "Callback function processing the `url-retrieve' response for URL.
 URL is a full-sized image URL attached to a timeline image.
 STATUS-PLIST is a plist of status events as per `url-retrieve'."
-  (if-let (error-response (plist-get status-plist :error))
+  (if-let* ((error-response (plist-get status-plist :error)))
       (user-error "error in loading image: %S" error-response)
     (when mastodon-media--enable-image-caching
       (unless (url-is-cached url) ;; cache if not already cached
@@ -467,7 +467,7 @@ START and END are the beginning and end of the media item 
to overlay."
      (propertize ""
                  'help-echo "Video"
                  'face
-                 '((:height 3.5 :inherit font-lock-comment-face))))))
+                 '((:height 3.5 :inherit mastodon-toot-docs-face))))))
 ;; (cl-pushnew ov mastodon-media--overlays)))
 
 (defun mastodon-media--get-avatar-rendering (avatar-url)
diff --git a/lisp/mastodon-notifications.el b/lisp/mastodon-notifications.el
index 5790757c00..5730a95e08 100644
--- a/lisp/mastodon-notifications.el
+++ b/lisp/mastodon-notifications.el
@@ -68,6 +68,9 @@
 (autoload 'mastodon-tl--buffer-type-eq "mastodon-tl")
 (autoload 'mastodon-tl--buffer-property "mastodon-tl")
 (autoload 'mastodon-http--patch "mastodon-http")
+(autoload 'mastodon-views--minor-view "mastodon-views")
+(autoload 'mastodon-tl--goto-first-item "mastodon-tl")
+(autoload 'mastodon-tl--init-sync "mastodon-tl")
 
 ;; notifications defcustoms moved into mastodon.el
 ;; as some need to be available without loading this file
@@ -85,6 +88,7 @@
 (defvar mastodon-profile-note-in-foll-reqs-max-length)
 (defvar mastodon-group-notifications)
 (defvar mastodon-notifications-grouped-names-count)
+(defvar mastodon-tl--link-keymap)
 
 ;;; VARIABLES
 
@@ -298,8 +302,7 @@ Can be called in notifications view or in follow-requests 
view."
                 str))))
          (status (mastodon-tl--field 'status note))
          (follower (alist-get 'account note))
-         (follower-name (or (alist-get 'display_name follower)
-                            (alist-get 'username follower)))
+         (follower-name (mastodon-notifications--follower-name follower))
          (filtered (mastodon-tl--field 'filtered status))
          (filters (when filtered
                     (mastodon-tl--current-filters filtered))))
@@ -336,8 +339,7 @@ ACCOUNTS is data of the accounts that have reacted to the 
notification."
                     str))))
              (follower (when (member type '(follow follow_request))
                          (car accounts)))
-             (follower-name (or (alist-get 'display_name follower)
-                                (alist-get 'username follower)))
+             (follower-name (mastodon-notifications--follower-name follower))
              (filtered (mastodon-tl--field 'filtered status))
              (filters (when filtered
                         (mastodon-tl--current-filters filtered))))
@@ -358,6 +360,12 @@ ACCOUNTS is data of the accounts that have reacted to the 
notification."
              status)
            folded group accounts))))))
 
+(defun mastodon-notifications--follower-name (follower)
+  "Return display_name or username of FOLLOWER."
+  (if (not (string= "" (alist-get 'display_name follower)))
+      (alist-get 'display_name follower)
+    (alist-get 'username follower)))
+
 (defun mastodon-notifications--comment-note-text (str)
   "Add comment face to all text in STR with `shr-text' face only."
   (with-temp-buffer
@@ -367,7 +375,7 @@ ACCOUNTS is data of the accounts that have reacted to the 
notification."
       (while (setq prop (text-property-search-forward 'face 'shr-text t))
         (add-text-properties (prop-match-beginning prop)
                              (prop-match-end prop)
-                             '(face (font-lock-comment-face shr-text)))))
+                             '(face (mastodon-toot-docs-face shr-text)))))
     (buffer-string)))
 
 (defun mastodon-notifications--body-arg
@@ -377,7 +385,7 @@ The string returned is passed to 
`mastodon-notifications--insert-note'.
 TYPE is a symbol, a member of `mastodon-notifiations--types'.
 FILTERS STATUS PROFILE-NOTE FOLLOWER-NAME GROUP NOTE."
   (let ((body
-         (if-let ((match (assoc "warn" filters)))
+         (if-let* ((match (assoc "warn" filters)))
              (mastodon-tl--spoiler status (cadr match))
            (mastodon-tl--clean-tabs-and-nl
             (cond ((mastodon-tl--has-spoiler status)
@@ -796,23 +804,139 @@ Status notifications are created when you call
          (resp (mastodon-http--get-json url)))
     (alist-get 'count resp)))
 
-(defvar mastodon-notifications--policy-vals
+;;; NOTIFICATION REQUESTS / FILTERING / POLICY
+
+(defvar mastodon-notifications--requests-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map mastodon-mode-map)
+    (define-key map (kbd "j") #'mastodon-notifications--request-reject)
+    (define-key map (kbd "a") #'mastodon-notifications--request-accept)
+    (define-key map (kbd "g") #'mastodon-notifications--requests)
+    map)
+  "Keymap for viewing follow requests.")
+
+;; FIXME: these are only for grouped notifs, else the fields are JSON bools
+(defvar mastodon-notifications-policy-vals
   '("accept" "filter" "drop"))
 
 (defun mastodon-notifications--get-policy ()
   "Return the notification filtering policy."
   (interactive)
-  (let ((url
-         (mastodon-notifications--api "notifications/policy")))
+  (let ((url (mastodon-notifications--api "notifications/policy")))
     (mastodon-http--get-json url)))
 
+(defun mastodon-notifications--pending-p ()
+  "Non-nil if there are any pending requests or notifications."
+  (let* ((json (mastodon-notifications--get-policy))
+         (summary (alist-get 'summary json)))
+    (or (not (= 0 (alist-get 'pending_requests_count summary)))
+        (not (= 0 (alist-get 'pending_notifications_count summary))))))
+
 (defun mastodon-notifications--update-policy (&optional params)
   "Update notifications filtering policy.
 PARAMS is an alist of parameters."
   ;; 
https://docs.joinmastodon.org/methods/notifications/#update-the-filtering-policy-for-notifications
-  (let ((url
-         (mastodon-notifications--api "notifications/policy")))
+  (let ((url (mastodon-notifications--api "notifications/policy")))
     (mastodon-http--patch url params)))
 
+(defun mastodon-notifications--get-requests (&optional params)
+  "Get a list of notification requests data from the server.
+PARAMS is an alist of parameters."
+  ;; NB: link header pagination
+  (let ((url (mastodon-notifications--api "notifications/requests")))
+    (mastodon-http--get-json url params)))
+
+(defun mastodon-notifications--request-accept (&optional reject)
+  "Accept a notification request for a user.
+This will merge any filtered notifications from them into the main
+notifications and accept any future notification from them.
+REJECT means reject notifications instead."
+  ;; POST /api/v1/notifications/requests/:id/accept
+  (interactive)
+  (let* ((id (mastodon-tl--property 'item-id))
+         (user (mastodon-tl--property 'notif-req-user))
+         (url (mastodon-http--api
+               (format "notifications/requests/%s/%s"
+                       id (if reject "dismiss" "accept"))))
+         (resp (mastodon-http--post url)))
+    (mastodon-http--triage
+     resp
+     (lambda (_resp)
+       (message "%s notifications from %s"
+                (if reject "Not accepting" "Accepting") user)))))
+
+(defun mastodon-notifications--request-reject ()
+  "Reject a notification request for a user.
+Rejecting a request means any notifications from them will continue to
+be filtered."
+  (interactive)
+  (mastodon-notifications--request-accept :reject))
+
+(defun mastodon-notifications--requests ()
+  "Open a new buffer displaying the user's notification requests."
+  ;; calqued off `mastodon-views--view-follow-requests'
+  (interactive)
+  (mastodon-tl--init-sync
+   "notification-requests"
+   "notifications/requests"
+   'mastodon-views--insert-notification-requests
+   nil
+   '(("limit" . "40")) ; server max is 80
+   :headers
+   "notification requests"
+   "a/j - accept/reject request at point\n\
+ n/p - go to next/prev request")
+  (mastodon-tl--goto-first-item)
+  (with-current-buffer "*mastodon-notification-requests*"
+    (use-local-map mastodon-notifications--requests-map)))
+
+(defun mastodon-views--insert-notification-requests (json)
+  "Insert the user's current notification requests.
+JSON is the data returned by the server."
+  (mastodon-views--minor-view
+   "notification requests"
+   #'mastodon-notifications--insert-users
+   json))
+;; masto-notif-req))
+
+(defun mastodon-notifications--insert-users (json)
+  "Insert users list into the buffer.
+JSON is the data from the server."
+  ;; calqued off `mastodon-views--insert-users-propertized-note'
+  ;; and `mastodon-search--insert-users-propertized'
+  (mapc (lambda (req)
+          (insert
+           (concat
+            (mastodon-notifications--format-req-user req)
+            mastodon-tl--horiz-bar "\n\n")))
+        json))
+
+(defun mastodon-notifications--format-req-user (req &optional note)
+  "Format a notification request user, REQ.
+NOTE means to include a profile note."
+  ;; calqued off `mastodon-search--propertize-user'
+  (let-alist req
+    (propertize
+     (concat
+      (propertize .account.username
+                  'face 'mastodon-display-name-face
+                  'byline t
+                  'notif-req-user .account.username
+                  'item-type 'notif-req
+                  'item-id .id) ;; notif req id
+      " : \n : "
+      (propertize (concat "@" .account.acct)
+                  'face 'mastodon-handle-face
+                  'mouse-face 'highlight
+                 'mastodon-tab-stop 'user-handle
+                 'keymap mastodon-tl--link-keymap
+                  'mastodon-handle (concat "@" .account.acct)
+                 'help-echo (concat "Browse user profile of @" .account.acct))
+      " : \n"
+      (when note
+        (mastodon-tl--render-text .account.note .account))
+      "\n")
+     'item-json req)))
+
 (provide 'mastodon-notifications)
 ;;; mastodon-notifications.el ends here
diff --git a/lisp/mastodon-profile.el b/lisp/mastodon-profile.el
index 417a787294..8852806d91 100644
--- a/lisp/mastodon-profile.el
+++ b/lisp/mastodon-profile.el
@@ -178,7 +178,8 @@ MAX-ID is a flag to include the max_id pagination 
parameter."
   (map-keys mastodon-profile--account-view-alist))
 
 (defun mastodon-profile--account-view-cycle (&optional prefix)
-  "Cycle through profile view: toots, toot sans boosts, followers, and 
following."
+  "Cycle through profile view: toots, toot sans boosts, followers, and 
following.
+If a PREFIX argument is provided, prompt for a view type and load."
   (interactive "P")
   (if prefix
       (let* ((choice
@@ -335,7 +336,7 @@ If value is :json-false, return nil."
                                      'display nil)
                          "/500 characters")
                         'read-only t
-                        'face 'font-lock-comment-face
+                        'face 'mastodon-toot-docs-face
                         'note-header t)
             "\n")
     (make-local-variable 'after-change-functions)
@@ -729,25 +730,25 @@ MAX-ID is a flag to include the max_id pagination 
parameter."
                  ;; sharkey has no relationships endpoint, returns 500.
                  ;; or poss it has a different endpoint
                  ""
-                 (let* ((followsp (mastodon-profile--follows-p
-                                   (list .requested_by .following 
.followed_by)))
-                        (rels (mastodon-profile--relationships-get .id))
-                        (langs-filtered (if-let ((langs (alist-get 'languages 
rels)))
-                                            (concat " ("
-                                                    (mapconcat #'identity 
langs " ")
-                                                    ")")
-                                          "")))
-                   (if followsp
-                       (mastodon-tl--set-face
-                        (concat (when (eq .following t)
-                                  (format " | FOLLOWED BY YOU%s" 
langs-filtered))
-                                (when (eq .followed_by t)
-                                  " | FOLLOWS YOU")
-                                (when (eq .requested_by t)
-                                  " | REQUESTED TO FOLLOW YOU")
-                                "\n\n")
-                        'success)
-                     ""))))) ; for insert call
+               (let* ((followsp (mastodon-profile--follows-p
+                                 (list .requested_by .following .followed_by)))
+                      (rels (mastodon-profile--relationships-get .id))
+                      (langs-filtered (if-let* ((langs (alist-get 'languages 
rels)))
+                                          (concat " ("
+                                                  (mapconcat #'identity langs 
" ")
+                                                  ")")
+                                        "")))
+                 (if followsp
+                     (mastodon-tl--set-face
+                      (concat (when (eq .following t)
+                                (format " | FOLLOWED BY YOU%s" langs-filtered))
+                              (when (eq .followed_by t)
+                                " | FOLLOWS YOU")
+                              (when (eq .requested_by t)
+                                " | REQUESTED TO FOLLOW YOU")
+                              "\n\n")
+                      'success)
+                   ""))))) ; for insert call
           (mastodon-media--inline-images (point-min) (point))
           ;; widget items description
           (mastodon-widget--create
diff --git a/lisp/mastodon-search.el b/lisp/mastodon-search.el
index 5f1e98090d..bb6a388bb3 100644
--- a/lisp/mastodon-search.el
+++ b/lisp/mastodon-search.el
@@ -201,13 +201,12 @@ is used for pagination."
          (limit (or limit "40"))
          (offset (or offset "0"))
          (buffer (format "*mastodon-search-%s-%s*" type query))
-         (params (cl-remove nil
-                            `(("q" . ,query)
-                              ,(when type `("type" . ,type))
-                              ,(when limit `("limit" . ,limit))
-                              ,(when offset `("offset" . ,offset))
-                              ,(when following `("following" . ,following))
-                              ,(when account-id `("account_id" . 
,account-id)))))
+         (params `(("q" . ,query)
+                   ,@(when type `(("type" . ,type)))
+                   ,@(when limit `(("limit" . ,limit)))
+                   ,@(when offset `(("offset" . ,offset)))
+                   ,@(when following `(("following" . ,following)))
+                   ,@(when account-id `(("account_id" . ,account-id)))))
          (response (mastodon-http--get-json url params))
          (items (alist-get (intern type) response)))
     (with-mastodon-buffer buffer #'mastodon-mode nil
@@ -243,7 +242,7 @@ is used for pagination."
   (let ((thing (or thing "items")))
     (insert
      (propertize (format "Looks like search returned no %s." thing)
-                 'face 'font-lock-comment-face))))
+                 'face 'mastodon-toot-docs-face))))
 
 (defun mastodon-search--render-response (data type buffer params
                                               insert-fun update-fun)
diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el
index 781c27363b..6e755fb046 100644
--- a/lisp/mastodon-tl.el
+++ b/lisp/mastodon-tl.el
@@ -471,15 +471,13 @@ With a single PREFIX arg, hide-replies.
 With a double PREFIX arg, only show posts with media."
   (interactive "p")
   (let ((params
-         (cl-remove
-          nil
-          `(("limit" . ,mastodon-tl--timeline-posts-count)
-            ,(when (eq prefix 16)
-               '("only_media" . "true"))
-            ,(when local
-               '("local" . "true"))
-            ,(when max-id
-               `("max_id" . ,(mastodon-tl--buffer-property 'max-id)))))))
+         `(("limit" . ,mastodon-tl--timeline-posts-count)
+           ,@(when (eq prefix 16)
+               '(("only_media" . "true")))
+           ,@(when local
+               '(("local" . "true")))
+           ,@(when max-id
+               `(("max_id" . ,(mastodon-tl--buffer-property 'max-id)))))))
     (message "Loading federated timeline...")
     (mastodon-tl--init (if local "local" "federated")
                        "timelines/public" 'mastodon-tl--timeline nil
@@ -574,13 +572,11 @@ If TAG is a list, show a timeline for all tags.
 With a single PREFIX arg, only show posts with media.
 With a double PREFIX arg, limit results to your own instance."
   (let ((params
-         (cl-remove
-          nil
-          `(("limit" . ,mastodon-tl--timeline-posts-count)
-            ,(when (eq prefix 4)
-               '("only_media" . "true"))
-            ,(when (eq prefix 16)
-               '("local" . "true"))))))
+         `(("limit" . ,mastodon-tl--timeline-posts-count)
+           ,@(when (eq prefix 4)
+               '(("only_media" . "true")))
+           ,@(when (eq prefix 16)
+               '(("local" . "true"))))))
     (when (listp tag)
       (let ((list (mastodon-http--build-array-params-alist "any[]" (cdr tag))))
         (while list
@@ -897,10 +893,10 @@ TS is a timestamp from the server, if any."
                           'face 'mastodon-display-name-face
                           'follow-link t
                           'mouse-face 'highlight
-                         'mastodon-tab-stop 'shr-url
-                         'shr-url app-url
+                                 'mastodon-tab-stop 'shr-url
+                                 'shr-url app-url
                           'help-echo app-url
-                         'keymap mastodon-tl--shr-map-replacement)))))
+                                 'keymap mastodon-tl--shr-map-replacement)))))
        ;; edited:
        (when edited-time
          (concat
@@ -910,7 +906,7 @@ TS is a timestamp from the server, if any."
           (propertize
            (format-time-string mastodon-toot-timestamp-format
                                edited-parsed)
-           'face 'font-lock-comment-face
+           'face 'mastodon-toot-docs-face
            'timestamp edited-parsed
            'display (if mastodon-tl--enable-relative-timestamps
                         (mastodon-tl--relative-time-description edited-parsed)
@@ -1014,7 +1010,7 @@ links in the text. If TOOT is nil no parsing occurs."
 
 (defun mastodon-tl--format-card-author (data)
   "Render card author DATA."
-  (when-let ((account (alist-get 'account data))) ;.account
+  (when-let* ((account (alist-get 'account data))) ;.account
     (let-alist account ;.account
       ;; FIXME: replace with refactored handle render fun
       ;; in byline refactor branch:
@@ -1027,10 +1023,10 @@ links in the text. If TOOT is nil no parsing occurs."
        (propertize (concat "@" .acct)
                    'face 'mastodon-handle-face
                    'mouse-face 'highlight
-                  'mastodon-tab-stop 'user-handle
-                  'keymap mastodon-tl--link-keymap
+                          'mastodon-tab-stop 'user-handle
+                          'keymap mastodon-tl--link-keymap
                    'mastodon-handle (concat "@" .acct)
-                  'help-echo (concat "Browse user profile of @" .acct))))))
+                          'help-echo (concat "Browse user profile of @" 
.acct))))))
 
 (defun mastodon-tl--process-link (toot start end url)
   "Process link URL in TOOT as hashtag, userhandle, or normal link.
@@ -1474,13 +1470,13 @@ LENGTH is of the longest option, for formatting."
                      (.vote_count
                       (format "%s votes | " .vote_count))
                      (t ""))
-               'face 'font-lock-comment-face)
+               'face 'mastodon-toot-docs-face)
               (let ((str (if (eq .expired :json-false)
                              (if (eq .expires_at nil)
                                  ""
                                (mastodon-tl--format-poll-expiry .expires_at))
                            "Poll expired.")))
-                (propertize str 'face 'font-lock-comment-face))
+                (propertize str 'face 'mastodon-toot-docs-face))
               "\n"))))
 
 (defconst mastodon-tl--time-units
@@ -1653,7 +1649,7 @@ in which case play first video or gif from current toot."
 (defun mastodon-tl--copy-image-caption ()
   "Copy the caption of the image at point."
   (interactive)
-  (if-let ((desc (get-text-property (point) 'image-description)))
+  (if-let* ((desc (get-text-property (point) 'image-description)))
       (progn
         (kill-new desc)
         (message "Image caption copied."))
@@ -1811,7 +1807,7 @@ CW-EXPANDED means treat content warnings as unfolded."
          (filtered (mastodon-tl--field 'filtered toot))
          (filters (when filtered
                     (mastodon-tl--current-filters filtered)))
-         (spoiler-or-content (if-let ((match (assoc "warn" filters)))
+         (spoiler-or-content (if-let* ((match (assoc "warn" filters)))
                                  (mastodon-tl--spoiler toot (cadr match))
                                (if (mastodon-tl--has-spoiler toot)
                                    (mastodon-tl--spoiler toot)
@@ -1837,8 +1833,8 @@ NO-BYLINE means just insert toot body, used for folding."
                 (mastodon-tl--buffer-property 'hide-replies nil :no-error)
                 ;; loading a tl with a prefix arg:
                 (mastodon-tl--hide-replies-p current-prefix-arg))
-              (cl-remove-if-not #'mastodon-tl--is-reply toots)
-            toots))))
+                  (cl-remove-if-not #'mastodon-tl--is-reply toots)
+                toots))))
     (mapc (lambda (toot)
             (mastodon-tl--toot toot nil thread domain nil no-byline))
           toots)
@@ -1976,19 +1972,19 @@ To disable showing the stats, customize
                                'favourited-p (eq t .favourited)
                                'favourites-field t
                                'help-echo (format "%s favourites" 
.favourites_count)
-                               'face 'font-lock-comment-face)
-                   (propertize " | " 'face 'font-lock-comment-face)
+                               'face 'mastodon-toot-docs-face)
+                   (propertize " | " 'face 'mastodon-toot-docs-face)
                    (propertize boosts
                                'boosted-p (eq t .reblogged)
                                'boosts-field t
                                'help-echo (format "%s boosts" .reblogs_count)
-                               'face 'font-lock-comment-face)
-                   (propertize " | " 'face 'font-lock-comment-face)
+                               'face 'mastodon-toot-docs-face)
+                   (propertize " | " 'face 'mastodon-toot-docs-face)
                    (propertize replies
                                'replies-field t
                                'replies-count .replies_count
                                'help-echo (format "%s replies" .replies_count)
-                               'face 'font-lock-comment-face)))
+                               'face 'mastodon-toot-docs-face)))
            (right-spacing
             (propertize " "
                         'display
@@ -2101,6 +2097,8 @@ call this function after it is set or use something else."
            'mentions)
           ((mastodon-tl--endpoint-str-= "notifications")
            'notifications)
+          ((mastodon-tl--endpoint-str-= "notifications/requests")
+           'notification-requests)
           ;; threads:
           ((mastodon-tl--endpoint-str-= "context" :suffix)
            'thread)
@@ -2414,7 +2412,7 @@ programmatically and not crash into
                 (when unfolded-state
                   (plist-put mastodon-tl--buffer-spec
                              'thread-unfolded unfolded-state))
-                (when-let ((ancestors (alist-get 'ancestors context)))
+                (when-let* ((ancestors (alist-get 'ancestors context)))
                   (mastodon-tl--timeline ancestors :thread))
                 (goto-char (point-max))
                 (move-marker marker (point))
@@ -2425,7 +2423,7 @@ programmatically and not crash into
                 (when mastodon-tl--display-media-p
                   (mastodon-media--inline-images marker ;start-pos
                                                  (point)))
-                (when-let ((descendants (alist-get 'descendants context)))
+                (when-let* ((descendants (alist-get 'descendants context)))
                   (mastodon-tl--timeline descendants :thread))
                 ;; put point at the toot:
                 (goto-char (marker-position marker))
@@ -2877,6 +2875,28 @@ PREFIX is for `mastodon-tl--show-tag-timeline', which 
see."
                      tags)))
     (mastodon-tl--show-tag-timeline prefix selection)))
 
+(defcustom mastodon-tl--tags-groups nil
+  "A list containing lists of up to four tags each.
+You can load a tag timeline list with one of these by calling
+`mastodon-tl--tag-group-timeline'."
+  :group 'mastodon-tl
+  :type '(repeat (list string string string string)))
+
+(defun mastodon-tl--tag-group-timeline (&optional prefix)
+  "Load a timeline of a tag group from `mastodon-tl--tags-groups'.
+PREFIX is for `mastodon-tl--show-tag-timeline', which see."
+  (interactive "P")
+  (if (not mastodon-tl--tags-groups)
+      (user-error
+       "Set `mastodon-tl--tags-groups' to view tag group timelines")
+    (let* ((list-strs (mapcar (lambda (x)
+                                ;; cons of list-as-string and list:
+                                (cons (prin1-to-string x) x))
+                              mastodon-tl--tags-groups))
+           (choice (completing-read "Tag group: " list-strs))
+           (choice-list (cdr (assoc choice list-strs #'equal))))
+      (mastodon-tl--show-tag-timeline prefix choice-list))))
+
 
 ;;; REPORT TO MODERATORS
 
@@ -2904,13 +2924,12 @@ ACCOUNT and TOOT are the data to use."
   "Build the parameters alist based on user responses.
 ACCOUNT-ID, COMMENT, ITEM-ID, FORWARD-P, CAT, and RULES are all from
 `mastodon-tl--report-params', which see."
-  (let ((params (cl-remove
-                 nil
-                 `(("account_id" . ,account-id)
-                   ,(when comment `("comment" . ,comment))
-                   ,(when item-id `("status_ids[]" . ,item-id))
-                   ,(when forward-p `("forward" . ,forward-p))
-                   ,(when cat `("category" . ,cat))))))
+  (let ((params
+         `(("account_id" . ,account-id)
+           ,@(when comment `(("comment" . ,comment)))
+           ,@(when item-id `(("status_ids[]" . ,item-id)))
+           ,@(when forward-p `(("forward" . ,forward-p)))
+           ,@(when cat `(("category" . ,cat))))))
     (if (not rules)
         params
       (let ((alist
@@ -3072,7 +3091,7 @@ and profile pages when showing followers or accounts 
followed."
 (defun mastodon-tl--get-link-header-from-response (headers)
   "Get http Link header from list of http HEADERS."
   ;; pleroma uses "link", so case-insensitive match required:
-  (when-let ((link-headers (alist-get "Link" headers nil nil #'cl-equalp)))
+  (when-let* ((link-headers (alist-get "Link" headers nil nil #'cl-equalp)))
     (split-string link-headers ", ")))
 
 (defun mastodon-tl--more ()
@@ -3459,7 +3478,7 @@ ENDPOINT-VERSION is a string, format Vx, e.g. V2."
         (mastodon-search--insert-heading view-name))
       (when binding-str
         (insert (mastodon-tl--set-face (concat "[" binding-str "]\n\n")
-                                       'font-lock-comment-face)))
+                                       'mastodon-toot-docs-face)))
       (mastodon-tl--set-buffer-spec
        buffer endpoint update-function
        link-header params nil
diff --git a/lisp/mastodon-toot.el b/lisp/mastodon-toot.el
index b80dc468b8..6d9295f6d9 100644
--- a/lisp/mastodon-toot.el
+++ b/lisp/mastodon-toot.el
@@ -611,7 +611,7 @@ base toot."
 Uses `lingva.el'."
   (interactive)
   (if mastodon-tl--buffer-spec
-      (if-let ((toot (mastodon-tl--property 'item-json)))
+      (if-let* ((toot (mastodon-tl--property 'item-json)))
           (condition-case x
               (lingva-translate nil
                                 (mastodon-tl--content toot)
@@ -1006,7 +1006,8 @@ instance to edit a toot."
              ;; adopt reply-to-id, visibility, CW, language, and media:
              (mastodon-toot--set-toot-properties .in_reply_to_id .visibility
                                                  source-cw .language nil nil
-                                                 .media_attachments .poll)
+                                                 ;; maintain media order:
+                                                 (reverse .media_attachments) 
.poll)
              (setq mastodon-toot--edit-item-id id))))))))
 
 (defun mastodon-toot--get-toot-source (id)
@@ -1033,7 +1034,7 @@ instance to edit a toot."
                  do (insert (propertize (if (= count 1)
                                             (format "%s [original]:\n" count)
                                           (format "%s:\n" count))
-                                        'face 'font-lock-comment-face)
+                                        'face 'mastodon-toot-docs-face)
                             (mastodon-toot--insert-toot-iter x)
                             "\n"))
         (goto-char (point-min))
@@ -1042,7 +1043,7 @@ instance to edit a toot."
                      (format "Edits to toot by %s:"
                              (alist-get 'username
                                         (alist-get 'account (car history))))
-                     'face 'font-lock-comment-face))
+                     'face 'mastodon-toot-docs-face))
         (mastodon-tl--set-buffer-spec (buffer-name (current-buffer))
                                       (format "statuses/%s/history" id)
                                       nil)))))
@@ -1627,7 +1628,7 @@ e.g. `mastodon-toot--send' -> Send."
     (substitute-command-keys
      (format
       (concat (mastodon-toot--comment "    ")
-              "%s"
+              "%-10s"
               (mastodon-toot--comment " - %s"))
       key command))))
 
@@ -1979,7 +1980,7 @@ Added to `after-change-functions'."
     (save-match-data
       (let* ((fill-column 67))
         (goto-char (point-min))
-        (when-let ((prop (text-property-search-forward 'toot-reply)))
+        (when-let* ((prop (text-property-search-forward 'toot-reply)))
           (fill-region (prop-match-beginning prop)
                        (point)))))))
 
diff --git a/lisp/mastodon-transient.el b/lisp/mastodon-transient.el
index 3e8ba5f820..ccd6d91b33 100644
--- a/lisp/mastodon-transient.el
+++ b/lisp/mastodon-transient.el
@@ -45,6 +45,7 @@
 (autoload 'mastodon-toot--read-poll-expiry "mastodon-toot")
 (autoload 'mastodon-toot--poll-expiry-options-alist "mastodon-toot")
 (autoload 'mastodon-toot--clear-poll "mastodon-toot")
+(autoload 'mastodon-notifications--get-policy "mastodon-notifications")
 
 ;;; UTILS
 
@@ -131,7 +132,7 @@ the format fields.X.keyname."
          (resp (mastodon-http--patch url strs))) ;; :json fails
     (mastodon-http--triage
      resp
-     (lambda (_)
+     (lambda (_resp)
        (message "Settings updated!\n%s" (pp-to-string strs))))))
 
 (transient-define-prefix mastodon-user-settings ()
@@ -155,19 +156,17 @@ the format fields.X.keyname."
    ("d"  "discoverable" "discoverable" :alist-key discoverable :class tp-bool)
    ("c" "hide follower/following lists" "source.hide_collections"
     :alist-key source.hide_collections :class tp-bool)
-   ("i" "indexable" "source.indexable" :alist-key source.indexable :class 
tp-bool)
-   ]
+   ("i" "indexable" "source.indexable" :alist-key source.indexable :class 
tp-bool)]
   ["Tooting options"
    ("p" "default privacy" "source.privacy" :alist-key source.privacy
     :class tp-option
     :choices (lambda () mastodon-toot-visibility-settings-list))
    ("s" "mark sensitive" "source.sensitive" :alist-key source.sensitive :class 
tp-bool)
    ("g" "default language" "source.language" :alist-key source.language :class 
tp-option
-    :choices (lambda () mastodon-iso-639-regional))
-   ]
+    :choices (lambda () mastodon-iso-639-regional))]
   ["Update"
    ("C-c C-c" "Save settings" mastodon-user-settings-update)
-   ("C-c C-k" :info "Revert all changes")]
+   ("C-x C-k" :info "Revert all changes")]
   (interactive)
   (if (or (not (boundp 'mastodon-active-user))
           (not mastodon-active-user))
@@ -192,7 +191,7 @@ the format fields.X.keyname."
          (url (mastodon-http--api endpoint))
          (resp (mastodon-http--patch url arrays))) ; :json)))
     (mastodon-http--triage
-     resp (lambda (_) (message "Fields updated!")))))
+     resp (lambda (_resp) (message "Fields updated!")))))
 
 (defun mastodon-transient-fetch-fields ()
   "Fetch profile fields (metadata)."
@@ -217,7 +216,7 @@ the format fields.X.keyname."
     ("4 v" "" "fields.4.value" :alist-key fields.4.value :class 
mastodon-transient-field)]]
   ["Update"
    ("C-c C-c" "Save settings" mastodon-profile-fields-update)
-   ("C-c C-k" :info "Revert all changes")]
+   ("C-x C-k" :info "Revert all changes")]
   (interactive)
   (if (not mastodon-active-user)
       (user-error "User not set")
@@ -345,8 +344,72 @@ Do not add more than the server's maximum setting."
               (tp-bools-to-strs args)))
       (mastodon-toot--update-status-fields))))
 
+(defvar mastodon-notifications-policy-vals)
+(declare-function mastodon-notifications--get-policy "mastodon-notifications")
+(declare-function mastodon-notifications--update-policy 
"mastodon-notifications")
+
+(transient-define-prefix mastodon-notifications-policy ()
+  "A transient to set notifications policy options."
+  ;; https://docs.joinmastodon.org/methods/notifications/#get-policy
+  :value (lambda () (tp-return-data #'mastodon-notifications--get-policy))
+  ["Notification policy options"
+   ("f" "people you don't follow" "for_not_following"
+    :alist-key for_not_following :class mastodon-transient-policy)
+   ("F" "people not following you" "for_not_followers"
+    :alist-key for_not_followers :class mastodon-transient-policy)
+   ("n" "New accounts" "for_new_accounts"
+    :alist-key for_new_accounts :class mastodon-transient-policy)
+   ("p" "Unsolicited private mentions" "for_private_mentions"
+    :alist-key for_private_mentions :class mastodon-transient-policy)
+   ("l" "Moderated accounts" "for_limited_accounts"
+    :alist-key for_limited_accounts :class mastodon-transient-policy)
+   (:info "")
+   (:info "\"accept\" = receive notifications")
+   (:info "\"filter\" = mark as filtered")
+   (:info "\"drop\" = do not receive any notifications")]
+  ["Notification requests"
+   (:info #'mastodon-notifications-requests-count)
+   (:info #'mastodon-notifications-filtered-count)]
+  ["Update"
+   ("C-c C-c" "Save settings" mastodon-notifications-policy-update)
+   ("C-x C-k" :info "Revert all changes")])
+
+(defun mastodon-notifications-requests-count ()
+  "Format a string for pending requests."
+  (let ((val (oref transient--prefix value)))
+    (format "Pending requests: %d"
+            (or (map-nested-elt
+                 val
+                 '(summary pending_requests_count))
+                0))))
+
+(defun mastodon-notifications-filtered-count ()
+  "Format a string for pending notifications."
+  (let ((val (oref transient--prefix value)))
+    (format "Pending notifications: %d"
+            (or (map-nested-elt
+                 val
+                 '(summary pending_notifications_count))
+                0))))
+
+(transient-define-suffix mastodon-notifications-policy-update (args)
+  "Send updated notification policy settings."
+  :transient 'transient--do-exit
+  ;; TODO:
+  (interactive (list (transient-args 'mastodon-notifications-policy)))
+  (let* ((parsed (tp-parse-args-for-send args))
+         (resp (mastodon-notifications--update-policy parsed)))
+    (mastodon-http--triage
+     resp
+     (lambda (_resp)
+       (message "Settings updated!\n%s" (pp-to-string parsed))))))
+
 ;;; CLASSES
 
+(defclass mastodon-transient-policy (tp-cycle)
+  ((choices :initarg :choices :initform 'mastodon-notifications-policy-vals))
+  "An option class for mastodon notification policy options.")
+
 (defclass mastodon-transient-field (tp-option-str)
   ((always-read :initarg :always-read :initform t))
   "An infix option class for our options.
diff --git a/lisp/mastodon-views.el b/lisp/mastodon-views.el
index 8d356fb1b3..a36af10f74 100644
--- a/lisp/mastodon-views.el
+++ b/lisp/mastodon-views.el
@@ -171,7 +171,7 @@ provides the JSON data."
   (if (seq-empty-p data)
       (insert (propertize
                (format "Looks like you have no %s for now." view-name)
-               'face 'font-lock-comment-face
+               'face 'mastodon-toot-docs-face
                'byline t
                'item-type 'no-item ; for nav
                'item-id "0")) ; so point can move here when no item
@@ -232,7 +232,7 @@ a: add account to this list, r: remove account from this 
list"
        (propertize (format " [replies: %s, exclusive %s]"
                            .replies_policy
                            (when (eq t .exclusive) "true"))
-                   'face 'font-lock-comment-face)
+                   'face 'mastodon-toot-docs-face)
        (propertize "\n\n"
                    'list t
                    'keymap mastodon-views--list-name-keymap
@@ -516,7 +516,7 @@ JSON is the data returned by the server."
                          (mastodon-toot--iso-to-human .scheduled_at))
                  'byline t ; so we nav here
                  'item-type 'scheduled ; so we nav here
-                 'face 'font-lock-comment-face
+                 'face 'mastodon-toot-docs-face
                  'keymap mastodon-views--scheduled-map
                  'item-json toot
                  'id .id)
@@ -875,6 +875,10 @@ If INSTANCE is given, use that."
         ((string-suffix-p "profile/" (url-basepath url))
          (string-remove-suffix "/profile/"
                                (url-basepath url)))
+        ;; snac is https://instance.com/user
+        ((not (string-match-p "@" url))
+         ;; cull trailing slash:
+         (string-trim-right (url-basepath url) "/"))
         ;; mastodon is https://instance.com/@user
         (t
          (string-remove-suffix (concat "/@" username)
diff --git a/lisp/mastodon.el b/lisp/mastodon.el
index 5092decbd0..dcf8132d3f 100644
--- a/lisp/mastodon.el
+++ b/lisp/mastodon.el
@@ -6,7 +6,7 @@
 ;; Author: Johnson Denen <johnson.de...@gmail.com>
 ;;         Marty Hiatt <mouse...@disroot.org>
 ;; Maintainer: Marty Hiatt <mouse...@disroot.org>
-;; Version: 1.1.8
+;; Version: 1.1.9
 ;; Package-Requires: ((emacs "28.1") (request "0.3.0") (persist "0.4") (tp 
"0.6"))
 ;; Homepage: https://codeberg.org/martianh/mastodon.el
 
@@ -103,6 +103,9 @@
 (autoload 'mastodon-tl--scroll-up-command "mastodon-tl")
 (autoload 'special-mode "simple")
 (autoload 'mastodon-tl--thread-do "mastodon-tl")
+(autoload 'mastodon-notifications-policy "mastodon-notifications")
+(autoload 'mastodon-notifications--requests "mastodon-notifications")
+(autoload 'mastodon-tl--tag-group-timeline "mastodon-tl")
 
 (defvar mastodon-tl--highlight-current-toot)
 (defvar mastodon-notifications--map)
@@ -207,11 +210,13 @@ and X others...\"."
     (define-key map (kbd "#")      #'mastodon-tl--get-tag-timeline)
     (define-key map (kbd "\"")     #'mastodon-tl--list-followed-tags)
     (define-key map (kbd "'")      #'mastodon-tl--followed-tags-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)
     (define-key map (kbd "L")      #'mastodon-tl--get-local-timeline)
     (define-key map (kbd "N")      #'mastodon-notifications-get)
+    (define-key map (kbd "S-C-n")  #'mastodon-notifications--requests)
     (define-key map (kbd "@")      #'mastodon-notifications--get-mentions)
     (define-key map (kbd "P")      #'mastodon-profile--show-user)
     (define-key map (kbd "s")      #'mastodon-search--query)
@@ -264,6 +269,7 @@ and X others...\"."
     (define-key map (kbd "V")      #'mastodon-profile--view-favourites)
     (define-key map (kbd "K")      #'mastodon-profile--view-bookmarks)
     (define-key map (kbd ":")      #'mastodon-user-settings)
+    (define-key map (kbd "C-:")    #'mastodon-notifications-policy)
     ;; minor views
     (define-key map (kbd "R")      #'mastodon-views--view-follow-requests)
     (define-key map (kbd "S")      #'mastodon-views--view-scheduled-toots)
@@ -302,7 +308,7 @@ and X others...\"."
   "Face used for content warning.")
 
 (defface mastodon-toot-docs-face
-  `((t :inherit font-lock-comment-face))
+  `((t :inherit shadow))
   "Face used for documentation in toot compose buffer.
 If `mastodon-tl--enable-proportional-fonts' is changed,
 mastodon.el needs to be re-loaded for this to be correctly set.")
diff --git a/mastodon-index.org b/mastodon-index.org
index 7ef9f13b05..8ec6c5cbe9 100644
--- a/mastodon-index.org
+++ b/mastodon-index.org
@@ -71,7 +71,12 @@
 |                  | mastodon-notifications--get-single-notif          | 
Return a single notification JSON for v2 notifs.                               |
 |                  | mastodon-notifications--get-statuses              | 
Display status notifications in buffer.                                        |
 |                  | mastodon-notifications--get-type                  | Read 
a notification type and load its timeline.                                |
+|                  | 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.                 |
 | N                | mastodon-notifications-get                        | 
Display NOTIFICATIONS in buffer.                                               |
+| C-:              | mastodon-notifications-policy                     | A 
transient to set notifications policy options.                               |
+|                  | mastodon-notifications-policy-update              | Send 
updated notification policy settings.                                     |
 |                  | 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.                                      |
@@ -154,6 +159,7 @@
 | SPC              | mastodon-tl--scroll-up-command                    | Call 
`scroll-up-command', loading more toots if necessary.                     |
 |                  | mastodon-tl--single-toot                          | View 
toot at point in separate buffer.                                         |
 |                  | mastodon-tl--some-followed-tags-timeline          | 
Prompt for some tags, and open a timeline for them.                            |
+| C-'              | mastodon-tl--tag-group-timeline                   | Load 
a timeline of a tag group from `mastodon-tl--tags-groups'.                |
 | RET, T           | mastodon-tl--thread                               | Open 
thread buffer for toot at point.                                          |
 |                  | mastodon-tl--toggle-sensitive-image               | 
Toggle dislay of sensitive image at point.                                     |
 |                  | mastodon-tl--toggle-spoiler-in-thread             | 
Toggler content warning for all posts in current thread.                       |
@@ -280,9 +286,7 @@
 | mastodon-media--hide-sensitive-media               | Whether media marked as 
sensitive should be hidden.                           |
 | mastodon-media--preview-max-height                 | Max height of any media 
attachment preview to be shown in timelines.          |
 | mastodon-mode-hook                                 | Hook run when entering 
Mastodon mode.                                         |
-| mastodon-notifications-check-for-updates           | Whether to regularly 
check for new notifications.                             |
 | mastodon-notifications-grouped-names-count         | The number of 
notification authors to display.                                |
-| mastodon-notifications-updates-interval            | How often to check for 
new notifications, in seconds.                         |
 | mastodon-profile-mode-hook                         | Hook run after entering 
or leaving `mastodon-profile-mode'.                   |
 | mastodon-profile-note-in-foll-reqs                 | If non-nil, show a 
user's profile note in follow request notifications.       |
 | mastodon-profile-note-in-foll-reqs-max-length      | The max character 
length for user profile note in follow requests.            |
@@ -303,6 +307,7 @@
 | mastodon-tl--show-stats                            | Whether to show toot 
stats (faves, boosts, replies counts).                   |
 | mastodon-tl--symbols                               | A set of symbols (and 
fallback strings) to be used in timeline.               |
 | mastodon-tl--tag-timeline-tags                     | A list of up to four 
tags for use with `mastodon-tl--followed-tags-timeline'. |
+| mastodon-tl--tags-groups                           | A list containing lists 
of up to four tags each.                              |
 | mastodon-tl--timeline-posts-count                  | Number of posts to 
display when loading a timeline.                           |
 | mastodon-tl-position-after-update                  | Defines where `point' 
should be located after a timeline update.              |
 | mastodon-toot--attachment-height                   | Height of the attached 
images preview in the toot draft buffer.               |

Reply via email to