aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHolger Dürer <me@hdurer.net>2017-05-08 22:17:39 +0100
committerHolger Dürer <me@hdurer.net>2017-05-10 21:26:08 +0100
commitb20265eea37884bde663aa6d1d498c9180b89947 (patch)
tree2887cee54247566ab2167abd6a7f16378024f0ec
parent0fc0d53dee2513b5923553531a8b6a9c5db10975 (diff)
Move the rendering of images fully into mastodon-media.el and use default images.
Having all the logic in one file reduces interdependencies. Having default images is more pleasing during the incremental loading.
-rw-r--r--lisp/mastodon-media.el118
-rw-r--r--lisp/mastodon-tl.el15
-rw-r--r--test/mastodon-tl-tests.el52
3 files changed, 160 insertions, 25 deletions
diff --git a/lisp/mastodon-media.el b/lisp/mastodon-media.el
index 289637e..734e11f 100644
--- a/lisp/mastodon-media.el
+++ b/lisp/mastodon-media.el
@@ -32,7 +32,6 @@
;;; Code:
(require 'mastodon-http nil t)
-(require 'mastodon)
(defgroup mastodon-media nil
"Inline Mastadon media."
@@ -43,6 +42,84 @@
(image-type-available-p 'imagemagick)
"A boolean value stating whether to show avatars in timelines.")
+(defvar mastodon-media--generic-avatar-data
+ (base64-decode-string
+ "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")
+ "The PNG data for a generic 100x100 avatar")
+
+(defvar mastodon-media--generic-broken-image-data
+ (base64-decode-string
+ "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")
+ "The PNG data for a generic 200x200 'broken image' view")
+
(defun mastodon-media--process-image-response (status-plist marker image-options region-length image-url)
"Callback function processing the url retrieve response for URL.
@@ -68,16 +145,18 @@ IMAGE-URL is the URL that was retrieved.
(save-restriction
(widen)
(put-text-property marker (+ marker region-length) 'media-state 'loaded)
- (put-text-property marker (+ marker region-length)
- 'display (or
- image
- (format "Failed to load %s" image-url)))
+ (when image
+ ;; We only set the image to display if we could load
+ ;; it; we already have set a default image when we
+ ;; added the tag.
+ (put-text-property marker (+ marker region-length)
+ 'display image))
;; We are done with the marker; release it:
(set-marker marker nil)))
(kill-buffer url-buffer)))))
(defun mastodon-media--load-image-from-url (url media-type start region-length)
- "Takes a URL and MEDIA-TYPE and return an image.
+ "Takes a URL and MEDIA-TYPE and load the image asynchronously.
MEDIA-TYPE is a symbol and either 'avatar or 'media-link."
;; TODO: Cache the avatars
@@ -134,5 +213,32 @@ not been returned."
(put-text-property start end 'media-state 'loading)
(mastodon-media--load-image-from-url image-url media-type start (- end start)))))))
+(defun mastodon-media--get-avatar-rendering (avatar-url)
+ "Returns the string to be written that renders the avatar at AVATAR-URL."
+ ;; We use just an empty space as the textual representation.
+ ;; This is what a user will see on a non-graphical display
+ ;; where not showing an avatar at all is preferable.
+ (let ((image-options (when (image-type-available-p 'imagemagick)
+ `(:height ,mastodon-avatar-height))))
+ (concat
+ (propertize " "
+ 'media-url avatar-url
+ 'media-state 'needs-loading
+ 'media-type 'avatar
+ 'display (apply #'create-image mastodon-media--generic-avatar-data
+ (when image-options 'imagemagick)
+ t image-options))
+ " ")))
+
+(defun mastodon-media--get-media-link-rendering (media-url)
+ "Returns the string to be written that renders the image at MEDIA-URL."
+ (concat
+ (propertize "[img]"
+ 'media-url media-url
+ 'media-state 'needs-loading
+ 'media-type 'media-link
+ 'display (create-image mastodon-media--generic-broken-image-data nil t))
+ " "))
+
(provide 'mastodon-media)
;;; mastodon-media.el ends here
diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el
index 1a5d9ae..b3550c6 100644
--- a/lisp/mastodon-tl.el
+++ b/lisp/mastodon-tl.el
@@ -108,14 +108,7 @@ Optionally start from POS."
(avatar-url (cdr (assoc 'avatar account))))
(concat
(when mastodon-media-show-avatars-p
- ;; We use just an empty space as the textual representation.
- ;; This is what a user will see on a non-graphical display
- ;; where not showing an avatar at all is preferable.
- (concat (propertize " "
- 'media-url avatar-url
- 'media-state 'needs-loading
- 'media-type 'avatar)
- " "))
+ (mastodon-media--get-avatar-rendering avatar-url))
(propertize name 'face 'warning)
" (@"
handle
@@ -191,11 +184,7 @@ also render the html"
(mapconcat
(lambda (media-attachement)
(let ((preview-url (cdr (assoc 'preview_url media-attachement))))
- (concat (propertize "[img]"
- 'media-url preview-url
- 'media-state 'needs-loading
- 'media-type 'media-link)
- " ")))
+ (mastodon-media--get-media-link-rendering preview-url)))
media-attachements "")))
(defun mastodon-tl--content (toot)
diff --git a/test/mastodon-tl-tests.el b/test/mastodon-tl-tests.el
index e89d313..0d0458d 100644
--- a/test/mastodon-tl-tests.el
+++ b/test/mastodon-tl-tests.el
@@ -105,7 +105,8 @@
(ert-deftest mastodon-tl--byline-regular ()
"Should format the regular toot correctly."
- (let ((timestamp (cdr (assoc 'created_at mastodon-tl-test-base-toot))))
+ (let ((mastodon-media-show-avatars-p nil)
+ (timestamp (cdr (assoc 'created_at mastodon-tl-test-base-toot))))
(with-mock
(mock (date-to-time timestamp) => '(22782 21551))
(mock (format-time-string mastodon-toot-timestamp-format '(22782 21551)) => "2999-99-99 00:11:22")
@@ -116,9 +117,24 @@
| Account 42 (@acct42@example.space) 2999-99-99 00:11:22
------------")))))
+(ert-deftest mastodon-tl--byline-regular-with-avatar ()
+ "Should format the regular toot correctly."
+ (let ((mastodon-media-show-avatars-p t)
+ (timestamp (cdr (assoc 'created_at mastodon-tl-test-base-toot))))
+ (with-mock
+ (mock (date-to-time timestamp) => '(22782 21551))
+ (mock (format-time-string mastodon-toot-timestamp-format '(22782 21551)) => "2999-99-99 00:11:22")
+
+ (should (string= (substring-no-properties
+ (mastodon-tl--byline mastodon-tl-test-base-toot))
+ "
+ | Account 42 (@acct42@example.space) 2999-99-99 00:11:22
+ ------------")))))
+
(ert-deftest mastodon-tl--byline-boosted ()
"Should format the boosted toot correctly."
- (let* ((toot (cons '(reblogged . t) mastodon-tl-test-base-toot))
+ (let* ((mastodon-media-show-avatars-p nil)
+ (toot (cons '(reblogged . t) mastodon-tl-test-base-toot))
(timestamp (cdr (assoc 'created_at toot))))
(with-mock
(mock (date-to-time timestamp) => '(22782 21551))
@@ -131,7 +147,8 @@
(ert-deftest mastodon-tl--byline-favorited ()
"Should format the favourited toot correctly."
- (let* ((toot (cons '(favourited . t) mastodon-tl-test-base-toot))
+ (let* ((mastodon-media-show-avatars-p nil)
+ (toot (cons '(favourited . t) mastodon-tl-test-base-toot))
(timestamp (cdr (assoc 'created_at toot))))
(with-mock
(mock (date-to-time timestamp) => '(22782 21551))
@@ -145,7 +162,8 @@
(ert-deftest mastodon-tl--byline-boosted/favorited ()
"Should format the boosted & favourited toot correctly."
- (let* ((toot `((favourited . t) (reblogged . t) ,@mastodon-tl-test-base-toot))
+ (let* ((mastodon-media-show-avatars-p nil)
+ (toot `((favourited . t) (reblogged . t) ,@mastodon-tl-test-base-toot))
(timestamp (cdr (assoc 'created_at toot))))
(with-mock
(mock (date-to-time timestamp) => '(22782 21551))
@@ -158,7 +176,8 @@
(ert-deftest mastodon-tl--byline-reblogged ()
"Should format the reblogged toot correctly."
- (let* ((toot mastodon-tl-test-base-boosted-toot)
+ (let* ((mastodon-media-show-avatars-p nil)
+ (toot mastodon-tl-test-base-boosted-toot)
(original-toot (cdr (assoc 'reblog mastodon-tl-test-base-boosted-toot)))
(timestamp (cdr (assoc 'created_at toot)))
(original-timestamp (cdr (assoc 'created_at original-toot))))
@@ -175,9 +194,30 @@
| Account 42 (@acct42@example.space) Boosted Account 43 (@acct43@example.space) original time
------------")))))
+(ert-deftest mastodon-tl--byline-reblogged-with-avatars ()
+ "Should format the reblogged toot correctly."
+ (let* ((mastodon-media-show-avatars-p t)
+ (toot mastodon-tl-test-base-boosted-toot)
+ (original-toot (cdr (assoc 'reblog mastodon-tl-test-base-boosted-toot)))
+ (timestamp (cdr (assoc 'created_at toot)))
+ (original-timestamp (cdr (assoc 'created_at original-toot))))
+ (with-mock
+ ;; We don't expect to use the toot's timestamp but the timestamp of the
+ ;; reblogged toot:
+ (mock (date-to-time timestamp) => '(1 2))
+ (mock (format-time-string mastodon-toot-timestamp-format '(1 2)) => "reblogging time")
+ (mock (date-to-time original-timestamp) => '(3 4))
+ (mock (format-time-string mastodon-toot-timestamp-format '(3 4)) => "original time")
+
+ (should (string= (substring-no-properties (mastodon-tl--byline toot))
+ "
+ | Account 42 (@acct42@example.space) Boosted Account 43 (@acct43@example.space) original time
+ ------------")))))
+
(ert-deftest mastodon-tl--byline-reblogged-boosted/favorited ()
"Should format the reblogged toot that was also boosted & favoritedcorrectly."
- (let* ((toot `((favourited . t) (reblogged . t) ,@mastodon-tl-test-base-boosted-toot))
+ (let* ((mastodon-media-show-avatars-p nil)
+ (toot `((favourited . t) (reblogged . t) ,@mastodon-tl-test-base-boosted-toot))
(original-toot (cdr (assoc 'reblog mastodon-tl-test-base-boosted-toot)))
(timestamp (cdr (assoc 'created_at toot)))
(original-timestamp (cdr (assoc 'created_at original-toot))))