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. |