diff options
Diffstat (limited to 'lisp')
-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 |
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))) |