diff options
author | marty hiatt <martianhiatus@riseup.net> | 2024-08-04 09:50:18 +0200 |
---|---|---|
committer | marty hiatt <martianhiatus@riseup.net> | 2024-08-04 09:50:18 +0200 |
commit | 4ac5b57ae6c4e94439a44820d81df00785d420c4 (patch) | |
tree | 1202433689a7b5f00eb4c110d2ffb7712c8a0892 | |
parent | a191fb5f3fb118892845792fe34ab41d98ccdf53 (diff) | |
parent | da0e348bc7aaa48474da8cf0ee657fed3f5e485d (diff) |
Merge branch 'develop'
-rw-r--r-- | README.org | 50 | ||||
-rw-r--r-- | lisp/mastodon-media.el | 8 | ||||
-rw-r--r-- | lisp/mastodon-search.el | 4 | ||||
-rw-r--r-- | lisp/mastodon-tl.el | 265 | ||||
-rw-r--r-- | lisp/mastodon-toot.el | 426 | ||||
-rw-r--r-- | lisp/mastodon.el | 30 | ||||
-rw-r--r-- | mastodon-index.org | 15 | ||||
-rw-r--r-- | mastodon.info | 118 | ||||
-rw-r--r-- | mastodon.texi | 32 | ||||
-rw-r--r-- | screenshot-notifs+compose.png | bin | 0 -> 207025 bytes | |||
-rw-r--r-- | screenshot-tl.png | bin | 0 -> 254949 bytes |
11 files changed, 554 insertions, 394 deletions
@@ -65,8 +65,7 @@ it shouldn't be very hard to get it working. *** Emoji -=mastodon-mode= will enable [[https://github.com/iqbalansari/emacs-emojify][Emojify]] if it is loaded in your Emacs environment, so -there's no need to write your own hook anymore. =emojify-mode= is not required. +Since Emacs 28, it has builtin emoji support with =emoji.el=. If you prefer to use [[https://github.com/iqbalansari/emacs-emojify][Emojify]], =require= it and set =mastodon-use-emojify= to non-nil to display emoji in timelines and to use it when composing toots. =Emoji.el= is the better option, but for now only =emojify= supports downloading and using custom emoji from your instance. From personal experience, =emojify= also tends to result in less TOFU. *** Discover @@ -186,6 +185,7 @@ For a full list of commands and variables, see [[file:mastodon-index.org][mastod | =i= | (un)pin your toot at point | | =d= | delete your toot at point, and reload current timeline | | =D= | delete and redraft toot at point, preserving reply/CW/visibility | +| =!= | toggle folding of toot at point | | (=S-C-=) =W=, =M=, =B= | (un)follow, (un)mute, (un)block author of toot at point | |----------------+---------------------------------------------------------------------------------| | | *Profile view* | @@ -242,21 +242,21 @@ value of that hook is as follows: **** Keybindings -|----------+----------------------------------| -| Key | Action | -|----------+----------------------------------| -| =C-c C-c= | Send toot | -| =C-c C-k= | Cancel toot | -| =C-c C-w= | Add content warning | -| =C-c C-v= | Change toot visibility | -| =C-c C-n= | Add sensitive media/nsfw flag | -| =C-c C-a= | Upload attachment(s) | -| =C-c != | Remove all attachments | -| =C-c C-e= | Add emoji (if =emojify= installed) | -| =C-c C-p= | Create a poll | -| =C-c C-l= | Set toot language | -| =-C-c C-s= | Schedule toot | -|----------+----------------------------------| +|----------+-------------------------------| +| Key | Action | +|----------+-------------------------------| +| =C-c C-c= | Send toot | +| =C-c C-k= | Cancel toot | +| =C-c C-w= | Add content warning | +| =C-c C-v= | Change toot visibility | +| =C-c C-n= | Add sensitive media/nsfw flag | +| =C-c C-a= | Upload attachment(s) | +| =C-c != | Remove all attachments | +| =C-c C-e= | Insert emoji | +| =C-c C-p= | Create a poll | +| =C-c C-l= | Set toot language | +| =-C-c C-s= | Schedule toot | +|----------+-------------------------------| **** Autocompletion of mentions, tags and emoji @@ -265,7 +265,7 @@ Autocompletion of mentions, tags, and emojis is provided by =mastodon-toot--enable-completion= is enabled by default. To trigger completion, type a prefix followed by a few letters, =@= for -mentions, =#= for tags, and =:= for emoji. +mentions, =#= for tags, and =:= for emoji (for now this only works when using =emojify.el=). If you want to enable =company-mode= in the toot compose buffer, set =mastodon-toot--use-company-for-completion= to =t=. (=mastodon.el= used to run its @@ -415,7 +415,7 @@ to your translator function as its text argument. Here's what *** Bookmarks and =mastodon.el= -=mastodon.el= implements a basic bookmark record and handler. Currently, this means that you can bookmark a post item and later load it in thread view. This could be expanded to any item with an id, but probably not to things like timeline views. If you want to be able to bookmark something, open an issue and ask, it's trivial to expand the bookmarking code. +=mastodon.el= implements a basic bookmark record and handler. Currently, this means that you can bookmark a post item and later load it in thread view. This could be expanded to any item with an id, but probably not to things like timeline views. If you want to be able to bookmark something, open an issue and ask, as it's trivial to expand the bookmarking code. ** Dependencies @@ -424,7 +424,7 @@ Hard dependencies (should all install with =mastodon.el=): - =persist= for storing some settings across sessions Optional dependencies (install yourself, =mastodon.el= can use them): -- =emojify= for inserting and viewing emojis +- =emojify= to use custom emoji (else we use builtin =emoji.el=) - =mpv= and =mpv.el= for viewing videos and gifs - =lingva.el= for translating toots @@ -494,3 +494,13 @@ Some significant contributors are: - https://alexjgriffith.itch.io - https://github.com/hdurer - https://codeberg.org/Red_Starfish + +** screenshots + +Here's a (federated) timeline: + +[[file:screenshot-tl.png]] + +Here's a notifcations view plus a compose buffer: + +[[file:screenshot-notifs+compose.png]] diff --git a/lisp/mastodon-media.el b/lisp/mastodon-media.el index d14d283..9dc8517 100644 --- a/lisp/mastodon-media.el +++ b/lisp/mastodon-media.el @@ -497,8 +497,12 @@ TYPE is the attachment's type field on the server. CAPTION is the image caption if provided. SENSITIVE is a flag from the item's JSON data." (let* ((help-echo-base - "RET/i: load full image (prefix: copy URL), +/-: zoom,\ - r: rotate, o: save preview") + (substitute-command-keys + (concat "\\`RET'/\\`i': load full image (prefix: copy URL), \\`+'/\\`-': zoom,\ + \\`r': rotate, \\`o': save preview" + (if (not (eq sensitive :json-false)) + ", \\`S': toggle sensitive media" + "")))) (help-echo (if caption (concat help-echo-base "\n\"" caption "\"") diff --git a/lisp/mastodon-search.el b/lisp/mastodon-search.el index e69366e..f862f3c 100644 --- a/lisp/mastodon-search.el +++ b/lisp/mastodon-search.el @@ -128,14 +128,14 @@ Optionally add string TYPE after HEADING." (insert (mastodon-search--format-heading str type))) -(defun mastodon-search--format-heading (str &optional type) +(defun mastodon-search--format-heading (str &optional type no-newline) "Format STR as a heading. Optionally add string TYPE after HEADING." (mastodon-tl--set-face (concat "\n " mastodon-tl--horiz-bar "\n " (upcase str) " " (if type (upcase type) "") "\n" - " " mastodon-tl--horiz-bar "\n") + " " mastodon-tl--horiz-bar (unless no-newline "\n")) 'success)) (defvar mastodon-search-types diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el index 41ecd85..8c00418 100644 --- a/lisp/mastodon-tl.el +++ b/lisp/mastodon-tl.el @@ -89,6 +89,8 @@ (autoload 'mastodon-search--insert-heading "mastodon-search") (autoload 'mastodon-media--process-full-sized-image-response "mastodon-media") (autoload 'mastodon-search--trending-statuses "mastodon-search") +(autoload 'mastodon-search--format-heading "mastodon-search") +(autoload 'mastodon-toot--with-toot-item "mastodon-toot") (defvar mastodon-toot--visibility) (defvar mastodon-toot-mode) @@ -219,6 +221,13 @@ respects the user's `browse-url' settings." See `mastodon-tl--get-remote-local-timeline' for view remote local domains." :type '(repeat string)) + +(defcustom mastodon-tl--fold-toots-at-length 1200 + "Length, in characters, to fold a toot at. +Longer toots will be folded and the remainder replaced by a +\"read more\" button. If the value is nil, don't fold at all." + :type '(integer)) + ;;; VARIABLES @@ -239,9 +248,8 @@ If nil `(point-min)' is used instead.") "The timer that, when set will scan the buffer to update the timestamps.") (defvar mastodon-tl--horiz-bar - (if (char-displayable-p ?―) - (make-string 12 ?―) - (make-string 12 ?-))) + (make-string 12 + (if (char-displayable-p ?―) ?― ?-))) ;;; KEYMAPS @@ -339,14 +347,6 @@ than `pop-to-buffer'." (message "Looks like there's no item at point?") ,@body)) -(defmacro mastodon-tl--do-if-item-strict (&rest body) - "Execute BODY if we have a toot object at point. -Includes boosts, and notifications that display toots." - (declare (debug t)) - `(if (not (equal 'toot (mastodon-tl--property 'item-type :no-move))) - (message "Looks like there's no toot at point?") - ,@body)) - ;;; NAV @@ -404,14 +404,18 @@ Optionally start from POS." (current-buffer)))) (if npos (if (not - ;; (get-text-property npos 'item-id) ; toots, users, not tags (get-text-property npos 'item-type)) ; generic + ;; FIXME let's make refresh &optional and only call refresh/recur + ;; if non-nil: (mastodon-tl--goto-item-pos find-pos refresh npos) (goto-char npos) ;; force display of help-echo on moving to a toot byline: (mastodon-tl--message-help-echo)) - ;; FIXME: this doesn't work, as the funcall doesn't return if we - ;; run into an endless refresh loop + ;; FIXME: doesn't work, the funcall doesn't return if in an endless + ;; refresh loop. + ;; either let-bind `max-lisp-eval-depth' and try to error handle when it + ;; errors, or else set up a counter, and error when it gets to high + ;; (like >2 would already be too much) (condition-case nil (funcall refresh) (error "No more items"))))) @@ -984,6 +988,8 @@ the toot)." LINK-TYPE is the type of link to produce." (let ((help-text (cond ((eq link-type 'content-warning) "Toggle hidden text") + ((eq link-type 'read-more) + "Toggle full post") (t (error "Unknown link type %s" link-type))))) (propertize string @@ -1025,6 +1031,8 @@ Used for hitting RET on a given link." "Search for account returned nothing. Perform URL lookup?") (mastodon-url-lookup (get-text-property position 'shr-url)) (message "Unable to find account.")))))))) + ((eq link-type 'read-more) + (mastodon-tl--unfold-post)) (t (error "Unknown link type %s" link-type))))) @@ -1365,7 +1373,7 @@ displayed when the duration is smaller than a minute)." cell)) options-alist))) (if (null poll) - (message "No poll here.") + (user-error "No poll here") (list ;; var "option" = just the cdr, a cons of option number and desc (cdr (assoc (completing-read "Poll option to vote for: " @@ -1377,7 +1385,7 @@ displayed when the duration is smaller than a minute)." "If there is a poll at point, prompt user for OPTION to vote on it." (interactive (mastodon-tl--read-poll-option)) (if (null (mastodon-tl--field 'poll (mastodon-tl--property 'item-json))) - (message "No poll here.") + (user-error "No poll here") (let* ((toot (mastodon-tl--property 'item-json)) (poll (mastodon-tl--field 'poll toot)) (poll-id (alist-get 'id poll)) @@ -1486,11 +1494,11 @@ Runs `mastodon-tl--render-text' and fetches poll or media." "Return the id of the last toot inserted into the buffer." (let* ((prev-change (save-excursion - (previous-single-property-change (point) 'base-toot-id))) + (previous-single-property-change (point) 'base-item-id))) (prev-pos (when prev-change (1- prev-change)))) (when prev-pos - (get-text-property prev-pos 'base-toot-id)))) + (get-text-property prev-pos 'base-item-id)))) (defun mastodon-tl--after-reply-status (reply-to-id) "T if REPLY-TO-ID is equal to that of the last toot inserted in the bufer." @@ -1498,7 +1506,8 @@ Runs `mastodon-tl--render-text' and fetches poll or media." (string= reply-to-id prev-id))) (defun mastodon-tl--insert-status (toot body author-byline action-byline - &optional id base-toot detailed-p thread domain) + &optional id base-toot detailed-p + thread domain unfolded no-byline) "Display the content and byline of timeline element TOOT. BODY will form the section of the toot above the byline. AUTHOR-BYLINE is an optional function for adding the author @@ -1515,31 +1524,46 @@ JSON of the toot responded to. DETAILED-P means display more detailed info. For now this just means displaying toot client. THREAD means the status will be displayed in a thread view. -When DOMAIN, force inclusion of user's domain in their handle." +When DOMAIN, force inclusion of user's domain in their handle. +UNFOLDED is a boolean meaning whether to unfold or fold item if foldable. +NO-BYLINE means just insert toot body, used for folding." (let* ((start-pos (point)) (reply-to-id (alist-get 'in_reply_to_id toot)) (after-reply-status-p (when (and thread reply-to-id) (mastodon-tl--after-reply-status reply-to-id))) - (type (alist-get 'type toot))) - ;; body: + (type (alist-get 'type toot)) + (toot-foldable + (and mastodon-tl--fold-toots-at-length + (length> body mastodon-tl--fold-toots-at-length)))) (insert (propertize (concat - "\n" - (if (and after-reply-status-p thread) - (concat (mastodon-tl--symbol 'replied) - "\n") - "") - (if (and after-reply-status-p thread) - (let ((bar (mastodon-tl--symbol 'reply-bar))) - (propertize body - 'line-prefix bar - 'wrap-prefix bar)) - body) - " \n" + (propertize + (concat + "\n" + ;; relpy symbol (broken): + (if (and after-reply-status-p thread) + (concat (mastodon-tl--symbol 'replied) + "\n") + "") + ;; actual body: + (let ((bar (mastodon-tl--symbol 'reply-bar)) + (body (if (and toot-foldable (not unfolded)) + (mastodon-tl--fold-body body) + body))) + (if (and after-reply-status-p thread) + (propertize body + 'line-prefix bar + 'wrap-prefix bar) + body))) + 'toot-body t) ;; includes newlines etc. for folding ;; byline: - (mastodon-tl--byline toot author-byline action-byline detailed-p domain)) + "\n" + (if no-byline + "" + (mastodon-tl--byline toot author-byline action-byline + detailed-p domain))) 'item-type 'toot 'item-id (or id ; notification's own id (alist-get 'id toot)) ; toot id @@ -1551,11 +1575,87 @@ When DOMAIN, force inclusion of user's domain in their handle." 'item-json toot 'base-toot base-toot 'cursor-face 'mastodon-cursor-highlight-face - 'notification-type type) - "\n") + 'notification-type type + 'toot-foldable toot-foldable + 'toot-folded (and toot-foldable (not unfolded))) + (if no-byline "" "\n")) (when mastodon-tl--display-media-p (mastodon-media--inline-images start-pos (point))))) +(defun mastodon-tl--fold-body (body) + "Fold toot BODY if it is very long. +Folding decided by `mastodon-tl--fold-toots-at-length'." + (let* ((heading (mastodon-search--format-heading + (mastodon-tl--make-link "READ MORE" 'read-more) + nil :no-newline)) + (display (concat (substring body 0 + mastodon-tl--fold-toots-at-length) + heading))) + (propertize display + 'read-more body))) + +(defun mastodon-tl--unfold-post (&optional fold) + "Unfold the toot at point if it is folded (read-more). +FOLD means to fold it instead" + (interactive) + (let ((at-byline (mastodon-tl--property 'byline :no-move))) + (if (save-excursion + (when (not at-byline) + (mastodon-tl--goto-next-item)) + (not (mastodon-tl--property 'toot-foldable :no-move))) + (user-error "No foldable item at point?") + (let* ((inhibit-read-only t) + (body-range (mastodon-tl--find-property-range 'toot-body + (point) :backward)) + (toot (mastodon-tl--property 'item-json :no-move)) + ;; `replace-region-contents' is much too slow, our hack from + ;; fedi.el is much simpler and much faster: + (beg (car body-range)) + (end (cdr body-range)) + (last-point (point)) + (point-after-fold (> last-point + (+ beg mastodon-tl--fold-toots-at-length)))) + ;; save-excursion here useless actually: + + ;; FIXME: because point goes to top of item, the screen gets scrolled + ;; by insertion + (goto-char beg) + (delete-region beg end) + (delete-char 1) ;; prevent newlines accumulating + ;; insert toot body: + (mastodon-tl--toot toot nil nil nil + (not fold) ;; (if fold :folded :unfolded) + :no-byline) + ;; set toot-folded prop on entire toot (not just body): + (let ((toot-range ;; post fold action range: + (mastodon-tl--find-property-range 'item-json + (point) :backward))) + (add-text-properties (car toot-range) + (cdr toot-range) + `(toot-folded ,fold))) + ;; try to leave point somewhere sane: + (cond ((or at-byline + (and fold + point-after-fold)) ;; point was in area now folded + (ignore-errors (forward-line -1)) ;; in case we are btw + (mastodon-tl--goto-next-item)) ;; goto byline + (t + (goto-char last-point) + (when point-after-fold ;; point was in READ MORE heading: + (beginning-of-line)))) + (message (format "%s" (if fold "Fold" "Unfold"))))))) + +(defun mastodon-tl--fold-post () + "Fold post at point, if it is too long." + (interactive) + (mastodon-tl--unfold-post t)) + +(defun mastodon-tl--fold-post-toggle () + "Toggle the folding status of the toot at point." + (interactive) + (let* ((folded (mastodon-tl--property 'toot-folded :no-move))) + (mastodon-tl--unfold-post (not folded)))) + ;; from mastodon-alt.el: (defun mastodon-tl--toot-for-stats (&optional toot) "Return the TOOT on which we want to extract stats. @@ -1616,19 +1716,22 @@ To disable showing the stats, customize (and (null (mastodon-tl--field 'in_reply_to_id toot)) (not (mastodon-tl--field 'rebloged toot)))) -(defun mastodon-tl--toot (toot &optional detailed-p thread domain) +(defun mastodon-tl--toot (toot &optional detailed-p thread domain + unfolded no-byline) "Format TOOT and insert it into the buffer. DETAILED-P means display more detailed info. For now this just means displaying toot client. THREAD means the status will be displayed in a thread view. -When DOMAIN, force inclusion of user's domain in their handle." +When DOMAIN, force inclusion of user's domain in their handle. +UNFOLDED is a boolean meaning whether to unfold or fold item if foldable. +NO-BYLINE means just insert toot body, used for folding." (mastodon-tl--insert-status toot (mastodon-tl--clean-tabs-and-nl (if (mastodon-tl--has-spoiler toot) (mastodon-tl--spoiler toot) (mastodon-tl--content toot))) 'mastodon-tl--byline-author 'mastodon-tl--byline-boosted - nil nil detailed-p thread domain)) + nil nil detailed-p thread domain unfolded no-byline)) (defun mastodon-tl--timeline (toots &optional thread domain) "Display each toot in TOOTS. @@ -1961,7 +2064,7 @@ ID is that of the toot to view." #'mastodon-tl--update-toot) (mastodon-tl--toot toot :detailed-p) (goto-char (point-min)) - (mastodon-tl--goto-next-item))))) + (mastodon-tl--goto-next-item :no-refresh))))) (defun mastodon-tl--update-toot (json) "Call `mastodon-tl--single-toot' on id found in JSON." @@ -1980,45 +2083,49 @@ view all branches of a thread." (let ((id (mastodon-tl--property 'base-item-id))) (mastodon-tl--thread id)))) -(defun mastodon-tl--thread (&optional id) - "Open thread buffer for toot at point or with ID." +(defun mastodon-tl--thread (&optional thread-id) + "Open thread buffer for toot at point or with THREAD-ID." (interactive) - (let* ((id (or id (mastodon-tl--property 'base-item-id :no-move))) - (type (mastodon-tl--field 'type (mastodon-tl--property 'item-json :no-move)))) - (if (or (string= type "follow_request") - (string= type "follow")) ; no can thread these - (user-error "No thread") - (let* ((endpoint (format "statuses/%s/context" id)) - (url (mastodon-http--api endpoint)) - (buffer (format "*mastodon-thread-%s*" id)) - (toot (mastodon-http--get-json ; refetch in case we just faved/boosted: - (mastodon-http--api (concat "statuses/" id)) - nil :silent)) - (context (mastodon-http--get-json url nil :silent))) - (if (equal (caar toot) 'error) - (user-error "Error: %s" (cdar toot)) - (when (member (alist-get 'type toot) '("reblog" "favourite")) - (setq toot (alist-get 'status toot))) - (if (> (+ (length (alist-get 'ancestors context)) - (length (alist-get 'descendants context))) - 0) - ;; if we have a thread: - (with-mastodon-buffer buffer #'mastodon-mode nil - (let ((marker (make-marker))) - (mastodon-tl--set-buffer-spec buffer endpoint - #'mastodon-tl--thread) - (mastodon-tl--timeline (alist-get 'ancestors context) :thread) - (goto-char (point-max)) - (move-marker marker (point)) - ;; print re-fetched toot: - (mastodon-tl--toot toot :detailed-p :thread) - (mastodon-tl--timeline (alist-get 'descendants context) - :thread) - ;; put point at the toot: - (goto-char (marker-position marker)) - (mastodon-tl--goto-next-item))) - ;; else just print the lone toot: - (mastodon-tl--single-toot id))))))) + (mastodon-toot--with-toot-item + ;; this function's var must not be id as the above macro binds id and even + ;; if we provide the arg (e.g. url-lookup), the macro definition overrides + ;; it, making the optional arg unusable! + (let* ((id (or thread-id (mastodon-tl--property 'base-item-id :no-move))) + (type (mastodon-tl--field 'type (mastodon-tl--property 'item-json :no-move)))) + (if (or (string= type "follow_request") + (string= type "follow")) ; no can thread these + (user-error "No thread") + (let* ((endpoint (format "statuses/%s/context" id)) + (url (mastodon-http--api endpoint)) + (buffer (format "*mastodon-thread-%s*" id)) + (toot (mastodon-http--get-json ; refetch in case we just faved/boosted: + (mastodon-http--api (concat "statuses/" id)) + nil :silent)) + (context (mastodon-http--get-json url nil :silent))) + (if (equal (caar toot) 'error) + (user-error "Error: %s" (cdar toot)) + (when (member (alist-get 'type toot) '("reblog" "favourite")) + (setq toot (alist-get 'status toot))) + (if (> (+ (length (alist-get 'ancestors context)) + (length (alist-get 'descendants context))) + 0) + ;; if we have a thread: + (with-mastodon-buffer buffer #'mastodon-mode nil + (let ((marker (make-marker))) + (mastodon-tl--set-buffer-spec buffer endpoint + #'mastodon-tl--thread) + (mastodon-tl--timeline (alist-get 'ancestors context) :thread) + (goto-char (point-max)) + (move-marker marker (point)) + ;; print re-fetched toot: + (mastodon-tl--toot toot :detailed-p :thread) + (mastodon-tl--timeline (alist-get 'descendants context) + :thread) + ;; put point at the toot: + (goto-char (marker-position marker)) + (mastodon-tl--goto-next-item))) + ;; else just print the lone toot: + (mastodon-tl--single-toot id)))))))) (defun mastodon-tl--mute-thread () "Mute the thread displayed in the current buffer. diff --git a/lisp/mastodon-toot.el b/lisp/mastodon-toot.el index 23de8b7..7497429 100644 --- a/lisp/mastodon-toot.el +++ b/lisp/mastodon-toot.el @@ -31,6 +31,8 @@ ;;; Code: (eval-when-compile (require 'subr-x)) + +(defvar mastodon-use-emojify) (require 'emojify nil :noerror) (declare-function emojify-insert-emoji "emojify") (declare-function emojify-set-emoji-data "emojify") @@ -76,7 +78,6 @@ (autoload 'mastodon-tl--as-string "mastodon-tl") (autoload 'mastodon-tl--buffer-type-eq "mastodon-tl") (autoload 'mastodon-tl--clean-tabs-and-nl "mastodon-tl") -(autoload 'mastodon-tl--do-if-item-strict "mastodon-tl") (autoload 'mastodon-tl--field "mastodon-tl") (autoload 'mastodon-tl--find-property-range "mastodon-tl") (autoload 'mastodon-tl--find-property-range "mastodon-tl") @@ -159,11 +160,6 @@ If the original toot visibility is different we use the more restricted one." "Whether to enable your instance's custom emoji by default." :type 'boolean) -(defcustom mastodon-toot--emojify-in-compose-buffer t - "Whether to enable `emojify-mode' in the compose buffer. -We only attempt to enable it if its bound." - :type 'boolean) - (defcustom mastodon-toot--proportional-fonts-compose nil "Nonnil to enable using proportional fonts in the compose buffer. By default fixed width fonts are used." @@ -171,10 +167,7 @@ By default fixed width fonts are used." width fonts")) (defvar-local mastodon-toot--content-warning nil - "A flag whether the toot should be marked with a content warning.") - -(defvar-local mastodon-toot--content-warning-from-reply-or-redraft nil - "The content warning of the toot being replied to.") + "The content warning of the current toot.") (defvar-local mastodon-toot--content-nsfw nil "A flag indicating whether the toot should be marked as NSFW.") @@ -267,17 +260,47 @@ send.") "\\>")) ; boundary end +;;; UTILS + +(defun mastodon-toot--base-toot-or-item-json () + "Return the JSON data of either base-toot or item-json property. +The former is for boost or favourite notifications, returning +data about the item boosted or favourited." + (or (mastodon-tl--property 'base-toot :no-move) ; fave/boost notifs + (mastodon-tl--property 'item-json))) + + +;;; MACRO + +(defmacro mastodon-toot--with-toot-item (&rest body) + "Execute BODY if we have a toot object at point. +Includes boosts, and notifications that display toots. +This macro makes the local variable ID available." + (declare (debug t)) + `(if (not (equal 'toot (mastodon-tl--property 'item-type :no-move))) + (user-error "Looks like there's no toot at point?") + (mastodon-tl--with-toot-helper + (lambda (id) + ,@body)))) + +(defun mastodon-tl--with-toot-helper (body-fun) + "Helper function for `mastodon-tl--with-toot-item'. +Extract any common variables needed, such as base-item-id +property, and call BODY-FUN on them." + (let ((id (mastodon-tl--property 'base-item-id))) + (funcall body-fun id))) + + ;;; MODE MAP (defvar mastodon-toot-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-c C-c") #'mastodon-toot--send) (define-key map (kbd "C-c C-k") #'mastodon-toot--cancel) - (define-key map (kbd "C-c C-w") #'mastodon-toot--toggle-warning) + (define-key map (kbd "C-c C-w") #'mastodon-toot--set-content-warning) (define-key map (kbd "C-c C-n") #'mastodon-toot--toggle-nsfw) (define-key map (kbd "C-c C-v") #'mastodon-toot--change-visibility) - (when (require 'emojify nil :noerror) - (define-key map (kbd "C-c C-e") #'mastodon-toot--insert-emoji)) + (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 C-p") #'mastodon-toot--create-poll) @@ -318,11 +341,12 @@ NO-TOOT means we are not calling from a toot buffer." (with-current-buffer "*new toot*" (mastodon-toot--update-status-fields))))) -(defun mastodon-toot--action-success (marker byline-region remove) +(defun mastodon-toot--action-success (marker byline-region remove &optional json) "Insert/remove the text MARKER with `success' face in byline. BYLINE-REGION is a cons of start and end pos of the byline to be modified. -Remove MARKER if REMOVE is non-nil, otherwise add it." +Remove MARKER if REMOVE is non-nil, otherwise add it. +JSON is added to the string as its item-json." (let ((inhibit-read-only t) (bol (car byline-region)) (eol (cdr byline-region)) @@ -333,7 +357,7 @@ Remove MARKER if REMOVE is non-nil, otherwise add it." (beginning-of-line) ;; The marker is not part of the byline (if (search-forward (format "(%s) " marker) eol t) (replace-match "") - (message "Oops: could not find marker '(%s)'" marker))) + (user-error "Oops: could not find marker '(%s)'" marker))) (unless remove (goto-char bol) (insert @@ -341,7 +365,8 @@ Remove MARKER if REMOVE is non-nil, otherwise add it." (format "(%s) " (propertize marker 'face 'success)) - 'cursor-face 'mastodon-cursor-highlight-face)))) + 'cursor-face 'mastodon-cursor-highlight-face + 'item-json json)))) ;; for (un)folding items (when at-byline-p ;; leave point after the marker: (unless remove @@ -352,7 +377,7 @@ Remove MARKER if REMOVE is non-nil, otherwise add it." (mastodon-tl--goto-next-item))))) (defun mastodon-toot--action (action callback) - "Take ACTION on toot at point, then execute CALLBACK. + "Take ACTION, a string, on toot at point, then execute CALLBACK. Makes a POST request to the server. Used for favouriting, boosting, or bookmarking toots." (let* ((id (mastodon-tl--property 'base-item-id)) @@ -361,69 +386,58 @@ boosting, or bookmarking toots." (response (mastodon-http--post url))) (mastodon-http--triage response callback))) -(defun mastodon-toot--toggle-boost-or-favourite (type) - "Toggle boost or favourite of toot at `point'. -TYPE is a symbol, either `favourite' or `boost.'" - (mastodon-tl--do-if-item-strict - (let ((n-type (mastodon-tl--property 'notification-type :no-move))) - (if (or (equal n-type "follow") - (equal n-type "follow_request")) - (user-error (format "Can't do action on %s notifications." n-type)) - (let* ((boost-p (equal type 'boost)) - ;; (has-id (mastodon-tl--property 'base-item-id)) - (byline-region ;(when has-id - (mastodon-tl--find-property-range 'byline (point))) - (id (when byline-region - (mastodon-tl--as-string (mastodon-tl--property 'base-item-id)))) - (boosted (when byline-region +(defun mastodon-toot--toggle-boost-or-favourite (action) + "Toggle boost or favourite of toot at point. +ACTION is a symbol, either `favourite' or `boost.'" + (mastodon-toot--with-toot-item + (let* ((n-type (mastodon-tl--property 'notification-type :no-move)) + (byline-region (mastodon-tl--find-property-range 'byline (point))) + (boost-p (eq action 'boost)) + (action-str (symbol-name action)) + (item-json (mastodon-tl--property 'item-json)) + (vis (mastodon-tl--field 'vis item-json))) + (cond + ((not byline-region) + (user-error "Nothing to %s here?!?" action-str)) + ;; there's nothing wrong with faving/boosting own toots + ;; & nothing wrong with faving/boosting own toots from notifs, + ;; it boosts/faves the base toot, not the notif status + ((or (equal n-type "follow") + (equal n-type "follow_request")) + (user-error "Can't %s %s notifications" action n-type)) + ((and boost-p + (or (equal vis "direct") + (equal vis "private"))) + (user-error "Can't boost posts with visibility: %s" vis)) + (t + (let* ((boosted (when byline-region (get-text-property (car byline-region) 'boosted-p))) (faved (when byline-region (get-text-property (car byline-region) 'favourited-p))) - (action (if boost-p - (if boosted "unreblog" "reblog") - (if faved "unfavourite" "favourite"))) - (msg (if boosted "unboosted" "boosted")) - (action-string (if boost-p "boost" "favourite")) - (remove (if boost-p (when boosted t) (when faved t))) - (item-json (mastodon-tl--property 'item-json)) - (toot-type (alist-get 'type item-json)) - (visibility (mastodon-tl--field 'visibility item-json))) - (if byline-region - (if (and (or (equal visibility "direct") - (equal visibility "private")) - boost-p) - (message "You cant boost posts with visibility: %s" visibility) - (cond ;; actually there's nothing wrong with faving/boosting own toots! - ;;((mastodon-toot--own-toot-p (mastodon-tl--property 'item-json)) - ;;(error "You can't %s your own toots" action-string)) - ;; & nothing wrong with faving/boosting own toots from notifs: - ;; this boosts/faves the base toot, not the notif status - ((and (equal "reblog" toot-type) - (not (mastodon-tl--buffer-type-eq 'notifications))) - (user-error "You can't %s boosts" action-string)) - ((and (equal "favourite" toot-type) - (not (mastodon-tl--buffer-type-eq 'notifications))) - (user-error "You can't %s favourites" action-string)) - ((and (equal "private" visibility) - (equal type 'boost)) - (user-error "You can't boost private toots")) - (t - (mastodon-toot--action - action - (lambda (_) - (let ((inhibit-read-only t)) - (add-text-properties (car byline-region) - (cdr byline-region) - (if boost-p - (list 'boosted-p (not boosted)) - (list 'favourited-p (not faved)))) - (mastodon-toot--update-stats-on-action type remove) - (mastodon-toot--action-success (if boost-p - (mastodon-tl--symbol 'boost) - (mastodon-tl--symbol 'favourite)) - byline-region remove)) - (message (format "%s #%s" (if boost-p msg action) id))))))) - (message (format "Nothing to %s here?!?" action-string)))))))) + (str-api (if boost-p "reblog" action-str)) + (action-str-api (mastodon-toot--str-negify str-api faved boosted)) + (action-pp (concat (mastodon-toot--str-negify action-str faved boosted) + (if boost-p "ed" "d"))) + (remove (if boost-p (when boosted t) (when faved t)))) + (mastodon-toot--action + action-str-api + (lambda (_) + (let ((inhibit-read-only t)) + (add-text-properties (car byline-region) + (cdr byline-region) + (if boost-p + (list 'boosted-p (not boosted)) + (list 'favourited-p (not faved)))) + (mastodon-toot--update-stats-on-action action remove) + (mastodon-toot--action-success (mastodon-tl--symbol action) + byline-region remove item-json)) + (message "%s #%s" action-pp id))))))))) + +(defun mastodon-toot--str-negify (str faved boosted) + "Add \"un\" to STR if FAVED or BOOSTED is non-nil." + (if (or faved boosted) + (concat "un" str) + str)) (defun mastodon-toot--inc-or-dec (count subtract) "If SUBTRACT, decrement COUNT, else increment." @@ -467,40 +481,33 @@ SUBTRACT means we are un-favouriting or unboosting, so we decrement." (defun mastodon-toot--toggle-bookmark () "Bookmark or unbookmark toot at point." (interactive) - (mastodon-tl--do-if-item-strict - (let ((n-type (mastodon-tl--property 'notification-type :no-move))) - (if (or (equal n-type "follow") - (equal n-type "follow_request")) - (user-error (format "Can't do action on %s notifications." n-type)) - (let* ((id (mastodon-tl--property 'base-item-id)) - (bookmarked-p - (mastodon-tl--property - 'bookmarked-p - (if (mastodon-tl--property 'byline :no-move) - ;; no move if not in byline, the idea being if in body, we do - ;; move forward to byline to toggle correctly. - ;; alternatively we could bookmarked-p whole posts. - :no-move))) - (byline-region (when id - (mastodon-tl--find-property-range 'byline (point)))) - (action (if bookmarked-p "unbookmark" "bookmark")) - (bookmark-str (mastodon-tl--symbol 'bookmark)) - (message (if bookmarked-p - "Bookmark removed!" - "Toot bookmarked!")) - (remove (when bookmarked-p t))) - (if byline-region - (mastodon-toot--action - action - (lambda (_) - (let ((inhibit-read-only t)) - (add-text-properties (car byline-region) - (cdr byline-region) - (list 'bookmarked-p (not bookmarked-p)))) - (mastodon-toot--action-success bookmark-str - byline-region remove) - (message (format "%s #%s" message id)))) - (message (format "Nothing to %s here?!?" action)))))))) + (mastodon-toot--with-toot-item + (let* ((n-type (mastodon-tl--property 'notification-type :no-move)) + (byline-region (mastodon-tl--find-property-range 'byline (point))) + (bookmarked-p (when byline-region + (get-text-property (car byline-region) 'bookmarked-p))) + (action (if bookmarked-p "unbookmark" "bookmark"))) + (cond ((or (equal n-type "follow") + (equal n-type "follow_request")) + (user-error "Can't bookmark %s notifications" n-type)) + ((not byline-region) + (user-error "Nothing to %s here?!?" action)) + (t + (let* ((bookmark-str (mastodon-tl--symbol 'bookmark)) + (message (if bookmarked-p + "Bookmark removed!" + "Toot bookmarked!")) + (item-json (mastodon-tl--property 'item-json))) + (mastodon-toot--action + action + (lambda (_) + (let ((inhibit-read-only t)) + (add-text-properties (car byline-region) + (cdr byline-region) + (list 'bookmarked-p (not bookmarked-p))) + (mastodon-toot--action-success bookmark-str + byline-region bookmarked-p item-json) + (message "%s #%s" message id)))))))))) (defun mastodon-toot--list-toot-boosters () "List the boosters of toot at point." @@ -515,23 +522,21 @@ SUBTRACT means we are un-favouriting or unboosting, so we decrement." (defun mastodon-toot--list-toot-boosters-or-favers (&optional favourite) "List the favouriters or boosters of toot at point. With FAVOURITE, list favouriters, else list boosters." - (mastodon-tl--do-if-item-strict - (let* ((base-toot (mastodon-tl--property 'base-item-id)) - (endpoint (if favourite "favourited_by" "reblogged_by")) - (url (mastodon-http--api (format "statuses/%s/%s" base-toot endpoint))) + (mastodon-toot--with-toot-item + (let* ((endpoint (if favourite "favourited_by" "reblogged_by")) + (url (mastodon-http--api (format "statuses/%s/%s" id endpoint))) (params '(("limit" . "80"))) (json (mastodon-http--get-json url params))) (if (eq (caar json) 'error) - (user-error "%s (Status does not exist or is private)" (alist-get 'error json)) + (user-error "%s (Status does not exist or is private)" + (alist-get 'error json)) (let ((handles (mastodon-tl--map-alist 'acct json)) (type-string (if favourite "Favouriters" "Boosters"))) (if (not handles) (user-error "Looks like this toot has no %s" type-string) (let ((choice (completing-read (format "%s (enter to view profile): " type-string) - handles - nil - t))) + handles nil t))) (mastodon-profile--show-user choice)))))))) (defun mastodon-toot--copy-toot-url () @@ -550,8 +555,7 @@ base toot." (defun mastodon-toot--toot-url () "Return the URL of the base toot at point." - (let* ((toot (or (mastodon-tl--property 'base-toot) - (mastodon-tl--property 'item-json)))) + (let* ((toot (mastodon-toot--base-toot-or-item-json))) (if (mastodon-tl--field 'reblog toot) (alist-get 'url (alist-get 'reblog toot)) (alist-get 'url toot)))) @@ -561,8 +565,7 @@ base toot." If the toot is a fave/boost notification, copy the text of the base toot." (interactive) - (let* ((toot (or (mastodon-tl--property 'base-toot) - (mastodon-tl--property 'item-json)))) + (let* ((toot (mastodon-toot--base-toot-or-item-json))) (kill-new (mastodon-tl--content toot)) (message "Toot content copied to the clipboard."))) @@ -578,10 +581,10 @@ Uses `lingva.el'." (when mastodon-tl--enable-proportional-fonts t)) (void-function - (message "Looks like you need to install lingva.el. Error: %s" - (error-message-string x)))) - (message "No toot to translate?")) - (message "No mastodon buffer?"))) + (user-error "Looks like you need to install lingva.el. Error: %s" + (error-message-string x)))) + (user-error "No toot to translate?")) + (user-error "No mastodon buffer?"))) (defun mastodon-toot--own-toot-p (toot) "Check if TOOT is user's own, for deleting, editing, or pinning it." @@ -594,15 +597,14 @@ Uses `lingva.el'." (defun mastodon-toot--pin-toot-toggle () "Pin or unpin user's toot at point." (interactive) - (let* ((toot (or (mastodon-tl--property 'base-toot) ;fave/boost notifs - (mastodon-tl--property 'item-json))) + (let* ((toot (mastodon-toot--base-toot-or-item-json)) (pinnable-p (mastodon-toot--own-toot-p toot)) (pinned-p (equal (alist-get 'pinned toot) t)) (action (if pinned-p "unpin" "pin")) (msg (if pinned-p "unpinned" "pinned")) (msg-y-or-n (if pinned-p "Unpin" "Pin"))) (if (not pinnable-p) - (message "You can only pin your own toots.") + (user-error "You can only pin your own toots") (when (y-or-n-p (format "%s this toot? " msg-y-or-n)) (mastodon-toot--action action (lambda (_) @@ -623,8 +625,7 @@ Uses `lingva.el'." "Delete and redraft user's toot at point synchronously. NO-REDRAFT means delete toot only." (interactive) - (let* ((toot (or (mastodon-tl--property 'base-toot) ;fave/boost notifs - (mastodon-tl--property 'item-json))) + (let* ((toot (mastodon-toot--base-toot-or-item-json)) (id (mastodon-tl--as-string (mastodon-tl--item-id toot))) (url (mastodon-http--api (format "statuses/%s" id))) (toot-cw (alist-get 'spoiler_text toot)) @@ -632,7 +633,7 @@ NO-REDRAFT means delete toot only." (reply-id (alist-get 'in_reply_to_id toot)) (pos (point))) (if (not (mastodon-toot--own-toot-p toot)) - (message "You can only delete (and redraft) your own toots.") + (user-error "You can only delete (and redraft) your own toots") (when (y-or-n-p (if no-redraft (format "Delete this toot? ") (format "Delete and redraft this toot? "))) @@ -654,8 +655,7 @@ NO-REDRAFT means delete toot only." "Set content warning to CW if it is non-nil." (unless (or (null cw) ; cw is nil for `mastodon-tl--dm-user' (string-empty-p cw)) - (setq mastodon-toot--content-warning t) - (setq mastodon-toot--content-warning-from-reply-or-redraft cw))) + (setq mastodon-toot--content-warning cw))) ;;; REDRAFT @@ -754,9 +754,12 @@ TEXT-ONLY means don't check for attachments or polls." ;;; EMOJIS -(defalias 'mastodon-toot--insert-emoji - #'emojify-insert-emoji - "Prompt to insert an emoji.") +(defun mastodon-toot--insert-emoji () + "Prompt to insert an emoji." + (interactive) + (if mastodon-use-emojify + (emojify-insert-emoji) + (emoji-search))) (defun mastodon-toot--emoji-dir () "Return the file path for the mastodon custom emojis directory." @@ -772,7 +775,7 @@ To use the downloaded emoji, run `mastodon-toot--enable-custom-emoji'." (custom-emoji (mastodon-http--get-json url)) (mastodon-custom-emoji-dir (mastodon-toot--emoji-dir))) (if (not (file-directory-p emojify-emojis-dir)) - (message "Looks like you need to set up emojify first.") + (user-error "Looks like you need to set up emojify first") (unless (file-directory-p mastodon-custom-emoji-dir) (make-directory mastodon-custom-emoji-dir nil)) ; no add parent (mapc (lambda (x) @@ -852,13 +855,6 @@ to `emojify-user-emojis', and the emoji data is updated." `(("poll[multiple]" . ,(symbol-name (plist-get mastodon-toot-poll :multi)))) `(("poll[hide_totals]" . ,(symbol-name (plist-get mastodon-toot-poll :hide)))))) -(defun mastodon-toot--read-cw-string () - "Read a content warning from the minibuffer." - (when (and (not (mastodon-toot--empty-p)) - mastodon-toot--content-warning) - (read-string "Warning: " - mastodon-toot--content-warning-from-reply-or-redraft))) - ;;; SEND TOOT FUNCTION @@ -876,13 +872,12 @@ instance to edit a toot." (endpoint (if edit-id ; we are sending an edit: (mastodon-http--api (format "statuses/%s" edit-id)) (mastodon-http--api "statuses"))) - (cw (mastodon-toot--read-cw-string)) (args-no-media (append `(("status" . ,toot) ("in_reply_to_id" . ,mastodon-toot--reply-to-id) ("visibility" . ,mastodon-toot--visibility) ("sensitive" . ,(when mastodon-toot--content-nsfw (symbol-name t))) - ("spoiler_text" . ,cw) + ("spoiler_text" . ,mastodon-toot--content-warning) ("language" . ,mastodon-toot--language)) ;; Pleroma instances can't handle null-valued ;; scheduled_at args, so only add if non-nil @@ -906,12 +901,13 @@ instance to edit a toot." (or (not args-media) (not (= (length mastodon-toot--media-attachments) (length mastodon-toot--media-attachment-ids))))) - (message "Something is wrong with your uploads. Wait for them to complete or try again.")) + (user-error "Something is wrong with your uploads. Wait for them to complete or try again")) ((and mastodon-toot--max-toot-chars - (> (mastodon-toot--count-toot-chars toot cw) mastodon-toot--max-toot-chars)) - (message "Looks like your toot (inc. CW) is longer than that maximum allowed length.")) + (> (mastodon-toot--count-toot-chars toot mastodon-toot--content-warning) + mastodon-toot--max-toot-chars)) + (user-error "Looks like your toot (inc. CW) is longer than that maximum allowed length")) ((mastodon-toot--empty-p) - (message "Empty toot. Cowardly refusing to post this.")) + (user-error "Empty toot. Cowardly refusing to post this")) (t (let ((response (if edit-id ; we are sending an edit: (mastodon-http--put endpoint args) @@ -921,9 +917,7 @@ instance to edit a toot." (lambda (_) ;; kill buffer: (mastodon-toot--kill) - (if scheduled - (message "Toot scheduled!") - (message "Toot toot!")) + (message "Toot %s!" (if scheduled "scheduled" "toot")) ;; cancel scheduled toot if we were editing it: (when scheduled-id (mastodon-views--cancel-scheduled-toot @@ -948,28 +942,23 @@ instance to edit a toot." (defun mastodon-toot--edit-toot-at-point () "Edit the user's toot at point." (interactive) - (mastodon-tl--do-if-item-strict - (let ((toot (or (mastodon-tl--property 'base-toot) ; fave/boost notifs - (mastodon-tl--property 'item-json)))) + (mastodon-toot--with-toot-item + (let ((toot (mastodon-toot--base-toot-or-item-json))) (if (not (mastodon-toot--own-toot-p toot)) - (message "You can only edit your own toots.") - (let* ((id (mastodon-tl--as-string (mastodon-tl--item-id toot))) - (source (mastodon-toot--get-toot-source id)) + (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)) - (toot-visibility (alist-get 'visibility toot)) - (toot-language (alist-get 'language toot)) - (reply-id (alist-get 'in_reply_to_id toot)) - (media (alist-get 'media_attachments toot)) - (poll (alist-get 'poll toot))) - (when (y-or-n-p "Edit this toot? ") - (mastodon-toot--compose-buffer nil reply-id nil content :edit) - (goto-char (point-max)) - ;; adopt reply-to-id, visibility, CW, language, and media: - (mastodon-toot--set-toot-properties reply-id toot-visibility - source-cw toot-language nil - nil media poll) - (setq mastodon-toot--edit-item-id id))))))) + (source-cw (alist-get 'spoiler_text source))) + (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 + .media_attachments .poll) + (setq mastodon-toot--edit-item-id id)))))))) (defun mastodon-toot--get-toot-source (id) "Fetch the source JSON of toot with ID." @@ -1032,7 +1021,7 @@ Remove empty string (self) from result and joins the sequence with whitespace." "Add domain to local ACCT and replace the curent user name with \"\". Mastodon requires the full @user@domain, even in the case of local accts. eg. \"user\" -> \"@user@local.social\" (when local.social is the domain of the -mastodon-instance-url). +`mastodon-instance-url'). eg. \"yourusername\" -> \"\" eg. \"feduser@fed.social\" -> \"@feduser@fed.social\"." (cond ((string-match-p "@" acct) (concat "@" acct)) ; federated acct @@ -1174,18 +1163,16 @@ text of the toot being replied to in the compose buffer. If the region is active, inject it into the reply buffer, prefixed by >." (interactive) - (mastodon-tl--do-if-item-strict + (mastodon-toot--with-toot-item (let* ((quote (when (region-active-p) (buffer-substring (region-beginning) (region-end)))) - (toot (mastodon-tl--property 'item-json)) ;; no-move arg for base toot: don't try next toot - (base-toot (mastodon-tl--property 'base-toot :no-move)) ; for new notifs handling - (id (mastodon-tl--as-string (mastodon-tl--field 'id (or base-toot toot)))) + (toot (mastodon-toot--base-toot-or-item-json)) (account (mastodon-tl--field 'account toot)) (user (alist-get 'acct account)) - (mentions (mastodon-toot--mentions (or base-toot toot))) - (boosted (mastodon-tl--field 'reblog (or base-toot toot))) + (mentions (mastodon-toot--mentions toot)) + (boosted (mastodon-tl--field 'reblog toot)) (booster (when boosted (alist-get 'acct (alist-get 'account toot))))) @@ -1209,17 +1196,17 @@ prefixed by >." ;; user in mentions already: (mastodon-toot--mentions-to-string (copy-sequence mentions))))) id - (or base-toot toot) + toot quote)))) ;;; COMPOSE TOOT SETTINGS -(defun mastodon-toot--toggle-warning () - "Toggle `mastodon-toot--content-warning'." +(defun mastodon-toot--set-content-warning () + "Set a content warning for the current toot." (interactive) (setq mastodon-toot--content-warning - (not mastodon-toot--content-warning)) + (read-string "Warning: " mastodon-toot--content-warning)) (mastodon-toot--update-status-fields)) (defun mastodon-toot--toggle-nsfw () @@ -1234,7 +1221,7 @@ prefixed by >." "Change the current visibility to the next valid value." (interactive) (if (mastodon-tl--buffer-type-eq 'edit-toot) - (message "You can't change visibility when editing toots.") + (user-error "You can't change visibility when editing toots") (setq mastodon-toot--visibility (cond ((string= mastodon-toot--visibility "public") "unlisted") @@ -1278,7 +1265,7 @@ File is actually attached to the toot upon posting." ;; Only a max. of 4 attachments are allowed, so pop the oldest one. (pop mastodon-toot--media-attachments)) (if (file-directory-p file) - (message "Looks like you chose a directory not a 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)) @@ -1417,7 +1404,7 @@ LENGTH is the maximum character length allowed for a poll option." (longest (apply #'max (mapcar #'length choices)))) (if (> longest length) (progn - (message "looks like you went over the max length. Try again.") + (user-error "Looks like you went over the max length. Try again") (sleep-for 2) (mastodon-toot--read-poll-options count length)) choices))) @@ -1485,10 +1472,10 @@ With RESCHEDULE, reschedule the scheduled toot at point without editing." ;; https://codeberg.org/martianh/mastodon.el/issues/285 (interactive) (cond ((mastodon-tl--buffer-type-eq 'edit-toot) - (message "You can't schedule toots you're editing.")) + (user-error "You can't schedule toots you're editing")) ((not (or (mastodon-tl--buffer-type-eq 'new-toot) (mastodon-tl--buffer-type-eq 'scheduled-statuses))) - (message "You can only schedule toots from the compose buffer or scheduled toots view.")) + (user-error "You can only schedule toots from the compose buffer or scheduled toots view")) (t (let* ((id (when reschedule (mastodon-tl--property 'id :no-move))) (ts (when reschedule @@ -1532,7 +1519,7 @@ With RESCHEDULE, reschedule the scheduled toot at point without editing." ;;; DISPLAY KEYBINDINGS (defun mastodon-toot--get-mode-kbinds () - "Get a list of the keybindings in the mastodon-toot-mode." + "Get a list of the keybindings in the `mastodon-toot-mode'." (let* ((binds (copy-tree mastodon-toot-mode-map)) (prefix (car (cadr binds))) (bindings (remove nil (mapcar (lambda (i) @@ -1545,7 +1532,7 @@ With RESCHEDULE, reschedule the scheduled toot at point without editing." (defun mastodon-toot--format-kbind-command (cmd) "Format CMD to be more readable. -e.g. mastodon-toot--send -> Send." +e.g. `mastodon-toot--send' -> Send." (let* ((str (symbol-name cmd)) (re "--\\(.*\\)$") (str2 (save-match-data @@ -1601,7 +1588,7 @@ LONGEST is the length of the longest binding." ;;; DISPLAY DOCS (defun mastodon-toot--make-mode-docs () - "Create formatted documentation text for the mastodon-toot-mode." + "Create formatted documentation text for the `mastodon-toot-mode'." (let* ((kbinds (mastodon-toot--get-mode-kbinds)) (longest-kbind (mastodon-toot--formatted-kbinds-longest (mastodon-toot--format-kbinds kbinds)))) @@ -1795,8 +1782,9 @@ REPLY-REGION is a string to be injected into the buffer." (prin1-to-string mastodon-toot-poll)) (mastodon-toot--apply-fields-props cw-region - (if mastodon-toot--content-warning - "CW" + (if (and mastodon-toot--content-warning + (not (equal "" mastodon-toot--content-warning))) + (format "CW: %s" mastodon-toot--content-warning) " ") ;; hold the blank space 'mastodon-cw-face)))) @@ -1838,19 +1826,19 @@ Added to `after-change-functions' in new toot buffers." (let ((text (completing-read "Select draft toot: " mastodon-toot-draft-toots-list nil t))) - (if (mastodon-toot--compose-buffer-p) - (when (and (not (mastodon-toot--empty-p :text-only)) - (y-or-n-p "Replace current text with draft?")) - (cl-pushnew mastodon-toot-current-toot-text - mastodon-toot-draft-toots-list) - (goto-char - (cdr (mastodon-tl--find-property-range 'toot-post-header - (point-min)))) - (kill-region (point) (point-max)) - ;; to not save to kill-ring: - ;; (delete-region (point) (point-max)) - (insert text)) - (mastodon-toot--compose-buffer nil nil nil text))) + (if (not (mastodon-toot--compose-buffer-p)) + (mastodon-toot--compose-buffer nil nil nil text) + (when (and (not (mastodon-toot--empty-p :text-only)) + (y-or-n-p "Replace current text with draft?")) + (cl-pushnew mastodon-toot-current-toot-text + mastodon-toot-draft-toots-list) + (goto-char + (cdr (mastodon-tl--find-property-range 'toot-post-header + (point-min)))) + (kill-region (point) (point-max)) + ;; to not save to kill-ring: + ;; (delete-region (point) (point-max)) + (insert text)))) (unless (mastodon-toot--compose-buffer-p) (mastodon-toot--compose-buffer)) (message "No drafts available."))) @@ -1858,14 +1846,14 @@ Added to `after-change-functions' in new toot buffers." (defun mastodon-toot--delete-draft-toot () "Prompt for a draft toot and delete it." (interactive) - (if mastodon-toot-draft-toots-list - (let ((draft (completing-read "Select draft to delete: " - mastodon-toot-draft-toots-list - nil t))) - (setq mastodon-toot-draft-toots-list - (cl-delete draft mastodon-toot-draft-toots-list :test #'equal)) - (message "Draft deleted!")) - (message "No drafts to delete."))) + (if (not mastodon-toot-draft-toots-list) + (user-error "No drafts to delete") + (let ((draft (completing-read "Select draft to delete: " + mastodon-toot-draft-toots-list + nil t))) + (setq mastodon-toot-draft-toots-list + (cl-delete draft mastodon-toot-draft-toots-list :test #'equal)) + (message "Draft deleted!")))) (defun mastodon-toot--delete-all-drafts () "Delete all drafts." @@ -2013,7 +2001,7 @@ EDIT means we are editing an existing toot, not composing a new one." (setq mastodon-toot-previous-window-config previous-window-config) (when mastodon-toot--proportional-fonts-compose (facemenu-set-face 'variable-pitch)) - (when (and mastodon-toot--emojify-in-compose-buffer + (when (and mastodon-use-emojify ;; emojify loaded but poss not enabled in our buffer: (boundp 'emojify-mode)) (emojify-mode)) diff --git a/lisp/mastodon.el b/lisp/mastodon.el index d0dddee..82a2491 100644 --- a/lisp/mastodon.el +++ b/lisp/mastodon.el @@ -32,8 +32,8 @@ ;; mastodon.el is a client for fediverse services that implement the Mastodon ;; API. See <https://github.com/mastodon/mastodon>. -;; See the readme file at https://codeberg.org/martianh/mastodon.el for set up -;; and usage details. +;; For set up and usage details, see the Info documentation, or the readme +;; file at https://codeberg.org/martianh/mastodon.el. ;;; Code: (require 'cl-lib) ; for `cl-some' call in mastodon @@ -144,6 +144,11 @@ The default value \"%F %T\" prints ISO8601-style YYYY-mm-dd HH:MM:SS. Use. e.g. \"%c\" for your locale's date and time format." :type 'string) +(defcustom mastodon-use-emojify nil + "Whether to use emojify.el to display emojis. +From version 28, Emacs can display emojis natively. But +currently, it doesn't seem to have a way to handle custom emoji, +while emojify,el has this feature and mastodon.el implements it.") (defun mastodon-kill-window () "Quit window and delete helper." @@ -229,6 +234,7 @@ Use. e.g. \"%c\" for your locale's date and time format." (define-key map (kbd "G") #'mastodon-views--view-follow-suggestions) (define-key map (kbd "X") #'mastodon-views--view-lists) (define-key map (kbd "SPC") #'mastodon-tl--scroll-up-command) + (define-key map (kbd "!") #'mastodon-tl--fold-post-toggle) (define-key map (kbd "z") #'bury-buffer) map) "Keymap for `mastodon-mode'.") @@ -408,24 +414,27 @@ not, just browse the URL in the normal fashion." "Check if QUERY resembles a fediverse URL." ;; calqued off https://github.com/tuskyapp/Tusky/blob/c8fc2418b8f5458a817bba221d025b822225e130/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt ;; thx to Conny Duck! + ;; mastodon at least seems to allow only [a-z0-9_] for usernames, plus "." + ;; but not at beginning or end, see https://github.com/mastodon/mastodon/issues/6830 + ;; objects may have - in them (let* ((uri-parsed (url-generic-parse-url query)) (query (url-filename uri-parsed))) (save-match-data (or (string-match "^/@[^/]+$" query) (string-match "^/@[^/]+/[[:digit:]]+$" query) - (string-match "^/user[s]?/@?[[:alnum:]]+$" query) ; @: pleroma or soapbox + (string-match "^/user[s]?/@?[[:alnum:]_]+$" query) ; @: pleroma or soapbox (string-match "^/notice/[[:alnum:]]+$" query) (string-match "^/objects/[-a-f0-9]+$" query) (string-match "^/notes/[a-z0-9]+$" query) (string-match "^/display/[-a-f0-9]+$" query) - (string-match "^/profile/[[:alpha:]]+$" query) - (string-match "^/p/[[:alpha:]]+/[[:digit:]]+$" query) - (string-match "^/[[:alpha:]]+$" query) - (string-match "^/u/[[:alpha:]]+$" query) - (string-match "^/c/[[:alnum:]]+$" query) + (string-match "^/profile/[[:alpha:]_]+$" query) + (string-match "^/p/[[:alpha:]_]+/[[:digit:]]+$" query) + (string-match "^/[[:alpha:]_]+$" query) + (string-match "^/u/[[:alpha:]_]+$" query) + (string-match "^/c/[[:alnum:]_]+$" query) (string-match "^/post/[[:digit:]]+$" query) (string-match "^/comment/[[:digit:]]+$" query) ; lemmy - (string-match "^/user[s]?/[[:alnum:]]+/statuses/[[:digit:]]+$" query) ; hometown + (string-match "^/user[s]?/[[:alnum:]_]+/statuses/[[:digit:]]+$" query) ; hometown (string-match "^/notes/[[:alnum:]]+$" query))))) ; misskey post (defun mastodon-live-buffers () @@ -464,7 +473,8 @@ Calls `mastodon-tl--get-buffer-type', which see." (defun mastodon-mode-hook-fun () "Function to add to `mastodon-mode-hook'." - (when (require 'emojify nil :noerror) + (when (and mastodon-use-emojify + (require 'emojify nil :noerror)) (emojify-mode t) (when mastodon-toot--enable-custom-instance-emoji (mastodon-toot--enable-custom-emoji))) diff --git a/mastodon-index.org b/mastodon-index.org index 4637403..4c57478 100644 --- a/mastodon-index.org +++ b/mastodon-index.org @@ -101,6 +101,7 @@ | | 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-search-mode | Toggle mastodon search minor mode. | +| / | mastodon-switch-to-buffer | Switch to a live mastodon buffer. | | 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--copy-image-caption | Copy the caption of the image at point. | @@ -110,7 +111,9 @@ | | mastodon-tl--do-link-action-at-point | Do the action of the link at POSITION. | | | mastodon-tl--enable-notify-user-posts | Query for USER-HANDLE and enable notifications when they post. | | | mastodon-tl--filter-user-user-posts-by-language | Query for USER-HANDLE and filter display of their posts by language. | -| | mastodon-tl--follow-tag | Prompt for a tag and follow it. | +| | mastodon-tl--fold-post | Fold post at point, if it is too long. | +| ! | mastodon-tl--fold-post-toggle | Toggle the folding status of the toot at point. | +| | mastodon-tl--follow-tag | Prompt for a tag (from post at point) and follow it. | | W | mastodon-tl--follow-user | Query for USER-HANDLE from current status and follow that user. | | | mastodon-tl--follow-user-disable-boosts | Prompt for a USER-HANDLE, and disable display of boosts in home timeline. | | | mastodon-tl--follow-user-enable-boosts | Prompt for a USER-HANDLE, and enable display of boosts in home timeline. | @@ -121,9 +124,7 @@ | \ | mastodon-tl--get-remote-local-timeline | Prompt for an instance domain and try to display its local timeline. | | # | mastodon-tl--get-tag-timeline | Prompt for tag and opens its timeline. | | n | mastodon-tl--goto-next-item | Jump to next item. | -| C-<down> | mastodon-tl--goto-next-toot | | | p | mastodon-tl--goto-prev-item | Jump to previous item. | -| C-<up> | mastodon-tl--goto-prev-toot | | | " | mastodon-tl--list-followed-tags | List followed tags. View timeline of tag user choses. | | C-<return> | mastodon-tl--mpv-play-video-at-point | Play the video or gif at point with an mpv process. | | | mastodon-tl--mpv-play-video-from-byline | Run `mastodon-tl--mpv-play-video-at-point' on first moving image in post. | @@ -136,12 +137,13 @@ | 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. | -| RET, T | mastodon-tl--thread | Open thread buffer for toot at point or with ID. | +| RET, T | mastodon-tl--thread | Open thread buffer for toot at point or with THREAD-ID. | | | 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. | | c | mastodon-tl--toggle-spoiler-text-in-toot | Toggle the visibility of the spoiler text in the current toot. | | C-S-b | mastodon-tl--unblock-user | Query for USER-HANDLE from list of blocked users and unblock that user. | | | mastodon-tl--unfilter-user-languages | Remove any language filters for USER-HANDLE. | +| | mastodon-tl--unfold-post | Unfold the toot at point if it is folded (read-more). | | | mastodon-tl--unfollow-tag | Prompt for a followed tag, and unfollow it. | | C-S-w | mastodon-tl--unfollow-user | Query for USER-HANDLE from current status and unfollow that user. | | | mastodon-tl--unmute-thread | Mute the thread displayed in the current buffer. | @@ -178,13 +180,13 @@ | | 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-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'. | | f | mastodon-toot--toggle-favourite | Favourite/unfavourite toot at `point'. | | C-c C-n | mastodon-toot--toggle-nsfw | Toggle `mastodon-toot--content-nsfw'. | -| C-c C-w | mastodon-toot--toggle-warning | Toggle `mastodon-toot--content-warning'. | | a | mastodon-toot--translate-toot-text | Translate text of toot at point. | | E | mastodon-toot--view-toot-edits | View editing history of the toot at point in a popup buffer. | | | mastodon-turn-on-discover | Turns on discover support | @@ -261,6 +263,7 @@ | mastodon-tl--enable-proportional-fonts | Nonnil to enable using proportional fonts when rendering HTML. | | mastodon-tl--enable-relative-timestamps | Whether to show relative (to the current time) timestamps. | | mastodon-tl--expand-content-warnings | Whether to expand content warnings by default. | +| mastodon-tl--fold-toots-at-length | Length, in characters, to fold a toot at. | | mastodon-tl--hide-replies | Whether to hide replies from the timelines. | | mastodon-tl--highlight-current-toot | Whether to highlight the toot at point. Uses `cursor-face' special property. | | mastodon-tl--load-full-sized-images-in-emacs | Whether to load full-sized images inside Emacs. | @@ -276,7 +279,6 @@ | mastodon-toot--completion-style-for-mentions | The company completion style to use for mentions. | | mastodon-toot--default-media-directory | The default directory when prompting for a media file to upload. | | mastodon-toot--default-reply-visibility | Default visibility settings when replying. | -| mastodon-toot--emojify-in-compose-buffer | Whether to enable `emojify-mode' in the compose buffer. | | mastodon-toot--enable-completion | Whether to enable completion of mentions and hashtags. | | mastodon-toot--enable-custom-instance-emoji | Whether to enable your instance's custom emoji by default. | | mastodon-toot--proportional-fonts-compose | Nonnil to enable using proportional fonts in the compose buffer. | @@ -285,3 +287,4 @@ | mastodon-toot-mode-hook | Hook run after entering or leaving `mastodon-toot-mode'. | | mastodon-toot-orig-in-reply-length | Length to crop toot replied to in the compose buffer to. | | mastodon-toot-timestamp-format | Format to use for timestamps. | +| mastodon-use-emojify | Whether to use emojify.el to display emojis. | diff --git a/mastodon.info b/mastodon.info index e300abe..887dc62 100644 --- a/mastodon.info +++ b/mastodon.info @@ -24,6 +24,7 @@ README * Contributing:: * Supporting ‘mastodon.el’: Supporting mastodonel. * Contributors:: +* screenshots:: Installation @@ -76,6 +77,7 @@ have it installed you should be able to browse this readme inside emacs. * Contributing:: * Supporting ‘mastodon.el’: Supporting mastodonel. * Contributors:: +* screenshots:: File: mastodon.info, Node: Installation, Next: Usage, Up: README @@ -150,10 +152,13 @@ File: mastodon.info, Node: Emoji, Next: Discover, Prev: Repo, Up: Installati 1.1.4 Emoji ----------- -‘mastodon-mode’ will enable Emojify -(https://github.com/iqbalansari/emacs-emojify) if it is loaded in your -Emacs environment, so there’s no need to write your own hook anymore. -‘emojify-mode’ is not required. +Since Emacs 28, it has builtin emoji support with ‘emoji.el’. If you +prefer to use Emojify (https://github.com/iqbalansari/emacs-emojify), +‘require’ it and set ‘mastodon-use-emojify’ to non-nil to display emoji +in timelines and to use it when composing toots. ‘Emoji.el’ is the +better option, but for now only ‘emojify’ supports downloading and using +custom emoji from your instance. From personal experience, ‘emojify’ +also tends to result in less TOFU. File: mastodon.info, Node: Discover, Prev: Emoji, Up: Installation @@ -297,6 +302,7 @@ your ‘mastodon-token-file’ does not contain ‘:client_id’ and ‘i’ (un)pin your toot at point ‘d’ delete your toot at point, and reload current timeline ‘D’ delete and redraft toot at point, preserving reply/CW/visibility + ‘!’ toggle folding of toot at point (‘S-C-’) ‘W’, ‘M’, ‘B’ (un)follow, (un)mute, (un)block author of toot at point *Profile view* ‘C-c C-c’ cycle between statuses, statuses without boosts, followers, and following @@ -361,7 +367,7 @@ is as follows: 1. Keybindings Key Action - -------------------------------------------------- + --------------------------------------------- ‘C-c C-c’ Send toot ‘C-c C-k’ Cancel toot ‘C-c C-w’ Add content warning @@ -369,7 +375,7 @@ is as follows: ‘C-c C-n’ Add sensitive media/nsfw flag ‘C-c C-a’ Upload attachment(s) ‘C-c !’ Remove all attachments - ‘C-c C-e’ Add emoji (if ‘emojify’ installed) + ‘C-c C-e’ Insert emoji ‘C-c C-p’ Create a poll ‘C-c C-l’ Set toot language ‘-C-c C-s’ Schedule toot @@ -381,7 +387,8 @@ is as follows: ‘mastodon-toot--enable-completion’ is enabled by default. To trigger completion, type a prefix followed by a few letters, ‘@’ - for mentions, ‘#’ for tags, and ‘:’ for emoji. + for mentions, ‘#’ for tags, and ‘:’ for emoji (for now this only + works when using ‘emojify.el’). If you want to enable ‘company-mode’ in the toot compose buffer, set ‘mastodon-toot--use-company-for-completion’ to ‘t’. @@ -574,11 +581,12 @@ File: mastodon.info, Node: Bookmarks and mastodonel, Prev: Translating toots, 1.2.10 Bookmarks and ‘mastodon.el’ ---------------------------------- -‘mastodon.el’ doesn’t currently implement its own bookmark record and -handler, which means that emacs bookmarks will not work as is. Until we -implement them, you can get bookmarks going immediately by using -bookmark+.el -(https://github.com/emacsmirror/emacswiki.org/blob/master/bookmark%2b.el). +‘mastodon.el’ implements a basic bookmark record and handler. +Currently, this means that you can bookmark a post item and later load +it in thread view. This could be expanded to any item with an id, but +probably not to things like timeline views. If you want to be able to +bookmark something, open an issue and ask, as it’s trivial to expand the +bookmarking code. File: mastodon.info, Node: Dependencies, Next: Network compatibility, Prev: Usage, Up: README @@ -589,10 +597,11 @@ File: mastodon.info, Node: Dependencies, Next: Network compatibility, Prev: U Hard dependencies (should all install with ‘mastodon.el’): • ‘request’ (for uploading attachments), emacs-request (https://github.com/tkf/emacs-request) - • ‘persist’ for storing some settings across sessions + • ‘persist’ for storing some settings across sessions (we now use + ‘multisession.el’ for this) Optional dependencies (install yourself, ‘mastodon.el’ can use them): - • ‘emojify’ for inserting and viewing emojis + • ‘emojify’ to use custom emoji (else we use builtin ‘emoji.el’) • ‘mpv’ and ‘mpv.el’ for viewing videos and gifs • ‘lingva.el’ for translating toots @@ -701,7 +710,7 @@ I can provide IBAN or other bank account details. out. -File: mastodon.info, Node: Contributors, Prev: Supporting mastodonel, Up: README +File: mastodon.info, Node: Contributors, Next: screenshots, Prev: Supporting mastodonel, Up: README 1.7 Contributors ================ @@ -716,41 +725,58 @@ File: mastodon.info, Node: Contributors, Prev: Supporting mastodonel, Up: REA • <https://github.com/hdurer> • <https://codeberg.org/Red_Starfish> + +File: mastodon.info, Node: screenshots, Prev: Contributors, Up: README + +1.8 screenshots +=============== + +Here’s a (federated) timeline: + + + + + Here’s a notifcations view plus a compose buffer: + + + + Tag Table: Node: Top210 -Node: README962 -Node: Installation1596 -Node: ELPA1885 -Node: MELPA2113 -Node: Repo2493 -Node: Emoji2986 -Node: Discover3317 -Node: Usage3869 -Node: Logging in to your instance4312 -Node: Timelines5309 -Ref: Keybindings5784 -Ref: Toot byline legend10558 -Node: Composing toots10867 -Ref: Keybindings (1)12419 -Ref: Autocompletion of mentions tags and emoji12985 -Ref: Draft toots13851 -Node: Other commands and account settings14322 -Node: Customization17489 -Node: Commands and variables index18367 -Node: Alternative timeline layout18883 -Node: Live-updating timelines mastodon-async-mode19288 -Node: Translating toots20140 -Node: Bookmarks and mastodonel21322 -Node: Dependencies21794 -Node: Network compatibility22404 -Node: Contributing23286 -Node: Bug reports23782 -Node: Fixes and features24693 -Node: Coding style25194 -Node: Supporting mastodonel25818 -Node: Contributors26385 +Node: README978 +Node: Installation1628 +Node: ELPA1917 +Node: MELPA2145 +Node: Repo2525 +Node: Emoji3018 +Node: Discover3612 +Node: Usage4164 +Node: Logging in to your instance4607 +Node: Timelines5604 +Ref: Keybindings6079 +Ref: Toot byline legend10919 +Node: Composing toots11228 +Ref: Keybindings (1)12780 +Ref: Autocompletion of mentions tags and emoji13315 +Ref: Draft toots14240 +Node: Other commands and account settings14711 +Node: Customization17878 +Node: Commands and variables index18756 +Node: Alternative timeline layout19272 +Node: Live-updating timelines mastodon-async-mode19677 +Node: Translating toots20529 +Node: Bookmarks and mastodonel21711 +Node: Dependencies22253 +Node: Network compatibility22936 +Node: Contributing23818 +Node: Bug reports24314 +Node: Fixes and features25225 +Node: Coding style25726 +Node: Supporting mastodonel26350 +Node: Contributors26917 +Node: screenshots27352 End Tag Table diff --git a/mastodon.texi b/mastodon.texi index 571a2d7..c31caf1 100644 --- a/mastodon.texi +++ b/mastodon.texi @@ -38,6 +38,7 @@ README * Contributing:: * Supporting @samp{mastodon.el}: Supporting @samp{mastodonel}. * Contributors:: +* screenshots:: Installation @@ -85,6 +86,7 @@ NB: @samp{mastodon.el} now ships this readme as an .info file, so if you have it * Contributing:: * Supporting @samp{mastodon.el}: Supporting @samp{mastodonel}. * Contributors:: +* screenshots:: @end menu @node Installation @@ -150,8 +152,7 @@ it shouldn't be very hard to get it working. @node Emoji @subsection Emoji -@samp{mastodon-mode} will enable @uref{https://github.com/iqbalansari/emacs-emojify, Emojify} if it is loaded in your Emacs environment, so -there's no need to write your own hook anymore. @samp{emojify-mode} is not required. +Since Emacs 28, it has builtin emoji support with @samp{emoji.el}. If you prefer to use @uref{https://github.com/iqbalansari/emacs-emojify, Emojify}, @samp{require} it and set @samp{mastodon-use-emojify} to non-nil to display emoji in timelines and to use it when composing toots. @samp{Emoji.el} is the better option, but for now only @samp{emojify} supports downloading and using custom emoji from your instance. From personal experience, @samp{emojify} also tends to result in less TOFU@. @node Discover @subsection Discover @@ -343,6 +344,8 @@ For a full list of commands and variables, see @uref{mastodon-index.org, mastodo @tab delete your toot at point, and reload current timeline @item @samp{D} @tab delete and redraft toot at point, preserving reply/CW/visibility +@item @samp{!} +@tab toggle folding of toot at point @item (@samp{S-C-}) @samp{W}, @samp{M}, @samp{B} @tab (un)follow, (un)mute, (un)block author of toot at point @item @@ -418,7 +421,7 @@ value of that hook is as follows: @anchor{Keybindings (1)}Keybindings -@multitable {aaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@multitable {aaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaa} @headitem Key @tab Action @item @samp{C-c C-c} @@ -436,7 +439,7 @@ value of that hook is as follows: @item @samp{C-c !} @tab Remove all attachments @item @samp{C-c C-e} -@tab Add emoji (if @samp{emojify} installed) +@tab Insert emoji @item @samp{C-c C-p} @tab Create a poll @item @samp{C-c C-l} @@ -454,7 +457,7 @@ Autocompletion of mentions, tags, and emojis is provided by @samp{mastodon-toot--enable-completion} is enabled by default. To trigger completion, type a prefix followed by a few letters, @samp{@@} for -mentions, @samp{#} for tags, and @samp{:} for emoji. +mentions, @samp{#} for tags, and @samp{:} for emoji (for now this only works when using @samp{emojify.el}). If you want to enable @samp{company-mode} in the toot compose buffer, set @samp{mastodon-toot--use-company-for-completion} to @samp{t}. (@samp{mastodon.el} used to run its @@ -690,9 +693,7 @@ to your translator function as its text argument. Here's what @node Bookmarks and @samp{mastodonel} @subsection Bookmarks and @samp{mastodon.el} -@samp{mastodon.el} doesn’t currently implement its own bookmark record and handler, -which means that emacs bookmarks will not work as is. Until we implement them, -you can get bookmarks going immediately by using @uref{https://github.com/emacsmirror/emacswiki.org/blob/master/bookmark%2b.el, bookmark+.el}. +@samp{mastodon.el} implements a basic bookmark record and handler. Currently, this means that you can bookmark a post item and later load it in thread view. This could be expanded to any item with an id, but probably not to things like timeline views. If you want to be able to bookmark something, open an issue and ask, as it's trivial to expand the bookmarking code. @node Dependencies @section Dependencies @@ -702,13 +703,13 @@ Hard dependencies (should all install with @samp{mastodon.el}): @item @samp{request} (for uploading attachments), @uref{https://github.com/tkf/emacs-request, emacs-request} @item -@samp{persist} for storing some settings across sessions +@samp{persist} for storing some settings across sessions (we now use @samp{multisession.el} for this) @end itemize Optional dependencies (install yourself, @samp{mastodon.el} can use them): @itemize @item -@samp{emojify} for inserting and viewing emojis +@samp{emojify} to use custom emoji (else we use builtin @samp{emoji.el}) @item @samp{mpv} and @samp{mpv.el} for viewing videos and gifs @item @@ -826,4 +827,15 @@ Some significant contributors are: @uref{https://codeberg.org/Red_Starfish} @end itemize +@node screenshots +@section screenshots + +Here's a (federated) timeline: + +@image{screenshot-tl,,,,png} + +Here's a notifcations view plus a compose buffer: + +@image{screenshot-notifs+compose,,,,png} + @bye
\ No newline at end of file diff --git a/screenshot-notifs+compose.png b/screenshot-notifs+compose.png Binary files differnew file mode 100644 index 0000000..98dbf5e --- /dev/null +++ b/screenshot-notifs+compose.png diff --git a/screenshot-tl.png b/screenshot-tl.png Binary files differnew file mode 100644 index 0000000..9097580 --- /dev/null +++ b/screenshot-tl.png |