aboutsummaryrefslogtreecommitdiff
path: root/lisp
diff options
context:
space:
mode:
Diffstat (limited to 'lisp')
-rw-r--r--lisp/mastodon-media.el8
-rw-r--r--lisp/mastodon-search.el4
-rw-r--r--lisp/mastodon-tl.el265
-rw-r--r--lisp/mastodon-toot.el426
-rw-r--r--lisp/mastodon.el30
5 files changed, 421 insertions, 312 deletions
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)))