mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-18 13:17:29 -06:00
[feature/frontend] Respect prefers-reduced-motion for avatars, headers, and emojis (#3118)
* [feature/frontend] Respect `prefers-reduced-motion` for avatars, headers, and emojis * go fmt * fix tests * use static version of instance thumbnail when appropriate * use prefers-reduced-motion * simplify account conversion a bit * fix c&p error
This commit is contained in:
parent
b415337d40
commit
027a93facc
24 changed files with 435 additions and 140 deletions
|
|
@ -170,22 +170,47 @@ func (c *Converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
|||
func (c *Converter) AccountToWebAccount(
|
||||
ctx context.Context,
|
||||
a *gtsmodel.Account,
|
||||
) (*apimodel.Account, error) {
|
||||
webAccount, err := c.AccountToAPIAccountPublic(ctx, a)
|
||||
) (*apimodel.WebAccount, error) {
|
||||
apiAccount, err := c.AccountToAPIAccountPublic(ctx, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
webAccount := &apimodel.WebAccount{
|
||||
Account: apiAccount,
|
||||
}
|
||||
|
||||
// Set additional avatar information for
|
||||
// serving the avatar in a nice photobox.
|
||||
if a.AvatarMediaAttachment != nil {
|
||||
avatarAttachment, err := c.AttachmentToAPIAttachment(ctx, a.AvatarMediaAttachment)
|
||||
// serving the avatar in a nice <picture>.
|
||||
if ogAvi := a.AvatarMediaAttachment; ogAvi != nil {
|
||||
avatarAttachment, err := c.AttachmentToAPIAttachment(ctx, ogAvi)
|
||||
if err != nil {
|
||||
// This is just extra data so just
|
||||
// log but don't return any error.
|
||||
log.Errorf(ctx, "error converting account avatar attachment: %v", err)
|
||||
} else {
|
||||
webAccount.AvatarAttachment = &avatarAttachment
|
||||
webAccount.AvatarAttachment = &apimodel.WebAttachment{
|
||||
Attachment: &avatarAttachment,
|
||||
MIMEType: ogAvi.File.ContentType,
|
||||
PreviewMIMEType: ogAvi.Thumbnail.ContentType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set additional header information for
|
||||
// serving the header in a nice <picture>.
|
||||
if ogHeader := a.HeaderMediaAttachment; ogHeader != nil {
|
||||
headerAttachment, err := c.AttachmentToAPIAttachment(ctx, ogHeader)
|
||||
if err != nil {
|
||||
// This is just extra data so just
|
||||
// log but don't return any error.
|
||||
log.Errorf(ctx, "error converting account header attachment: %v", err)
|
||||
} else {
|
||||
webAccount.HeaderAttachment = &apimodel.WebAttachment{
|
||||
Attachment: &headerAttachment,
|
||||
MIMEType: ogHeader.File.ContentType,
|
||||
PreviewMIMEType: ogHeader.Thumbnail.ContentType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -747,11 +772,35 @@ func (c *Converter) StatusToAPIStatus(
|
|||
filters []*gtsmodel.Filter,
|
||||
mutes *usermute.CompiledUserMuteList,
|
||||
) (*apimodel.Status, error) {
|
||||
apiStatus, err := c.statusToFrontend(ctx, s, requestingAccount, filterContext, filters, mutes)
|
||||
apiStatus, err := c.statusToFrontend(
|
||||
ctx,
|
||||
s,
|
||||
requestingAccount, // Can be nil.
|
||||
filterContext, // Can be empty.
|
||||
filters,
|
||||
mutes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert author to API model.
|
||||
acct, err := c.AccountToAPIAccountPublic(ctx, s.Account)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error converting status acct: %w", err)
|
||||
}
|
||||
apiStatus.Account = acct
|
||||
|
||||
// Convert author of boosted
|
||||
// status (if set) to API model.
|
||||
if apiStatus.Reblog != nil {
|
||||
boostAcct, err := c.AccountToAPIAccountPublic(ctx, s.BoostOfAccount)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error converting boost acct: %w", err)
|
||||
}
|
||||
apiStatus.Reblog.Account = boostAcct
|
||||
}
|
||||
|
||||
// Normalize status for API by pruning
|
||||
// attachments that were not locally
|
||||
// stored, replacing them with a helpful
|
||||
|
|
@ -958,20 +1007,25 @@ func (c *Converter) StatusToWebStatus(
|
|||
ctx context.Context,
|
||||
s *gtsmodel.Status,
|
||||
) (*apimodel.WebStatus, error) {
|
||||
apiStatus, err := c.statusToFrontend(
|
||||
ctx,
|
||||
s,
|
||||
nil, // No authed requester.
|
||||
statusfilter.FilterContextNone,
|
||||
nil, // No filters.
|
||||
nil, // No mutes.
|
||||
apiStatus, err := c.statusToFrontend(ctx, s,
|
||||
nil, // No authed requester.
|
||||
statusfilter.FilterContextNone, // No filters.
|
||||
nil, // No filters.
|
||||
nil, // No mutes.
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert status author to web model.
|
||||
acct, err := c.AccountToWebAccount(ctx, s.Account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
webStatus := &apimodel.WebStatus{
|
||||
Status: apiStatus,
|
||||
Status: apiStatus,
|
||||
Account: acct,
|
||||
}
|
||||
|
||||
// Whack a newline before and after each "pre" to make it easier to outdent it.
|
||||
|
|
@ -1062,9 +1116,10 @@ func (c *Converter) StatusToWebStatus(
|
|||
for i, apiAttachment := range apiStatus.MediaAttachments {
|
||||
ogAttachment := ogAttachments[apiAttachment.ID]
|
||||
webStatus.MediaAttachments[i] = &apimodel.WebAttachment{
|
||||
Attachment: apiAttachment,
|
||||
Sensitive: apiStatus.Sensitive,
|
||||
MIMEType: ogAttachment.File.ContentType,
|
||||
Attachment: apiAttachment,
|
||||
Sensitive: apiStatus.Sensitive,
|
||||
MIMEType: ogAttachment.File.ContentType,
|
||||
PreviewMIMEType: ogAttachment.Thumbnail.ContentType,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1090,6 +1145,9 @@ func (c *Converter) StatusToAPIStatusSource(ctx context.Context, s *gtsmodel.Sta
|
|||
// parsing a status into its initial frontend representation.
|
||||
//
|
||||
// Requesting account can be nil.
|
||||
//
|
||||
// This function also doesn't handle converting the
|
||||
// account to api/web model -- the caller must do that.
|
||||
func (c *Converter) statusToFrontend(
|
||||
ctx context.Context,
|
||||
status *gtsmodel.Status,
|
||||
|
|
@ -1142,6 +1200,9 @@ func (c *Converter) statusToFrontend(
|
|||
// baseStatusToFrontend performs the main logic
|
||||
// of statusToFrontend() without handling of boost
|
||||
// logic, to prevent *possible* recursion issues.
|
||||
//
|
||||
// This function also doesn't handle converting the
|
||||
// account to api/web model -- the caller must do that.
|
||||
func (c *Converter) baseStatusToFrontend(
|
||||
ctx context.Context,
|
||||
s *gtsmodel.Status,
|
||||
|
|
@ -1169,11 +1230,6 @@ func (c *Converter) baseStatusToFrontend(
|
|||
}
|
||||
}
|
||||
|
||||
apiAuthorAccount, err := c.AccountToAPIAccountPublic(ctx, s.Account)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error converting status author: %w", err)
|
||||
}
|
||||
|
||||
repliesCount, err := c.state.DB.CountStatusReplies(ctx, s.ID)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error counting replies: %w", err)
|
||||
|
|
@ -1240,7 +1296,7 @@ func (c *Converter) baseStatusToFrontend(
|
|||
Content: s.Content,
|
||||
Reblog: nil, // Set below.
|
||||
Application: nil, // Set below.
|
||||
Account: apiAuthorAccount,
|
||||
Account: nil, // Caller must do this.
|
||||
MediaAttachments: apiAttachments,
|
||||
Mentions: apiMentions,
|
||||
Tags: apiTags,
|
||||
|
|
@ -1464,6 +1520,8 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
|
|||
|
||||
instance.Thumbnail = iAccount.AvatarMediaAttachment.URL
|
||||
instance.ThumbnailType = iAccount.AvatarMediaAttachment.File.ContentType
|
||||
instance.ThumbnailStatic = iAccount.AvatarMediaAttachment.Thumbnail.URL
|
||||
instance.ThumbnailStaticType = iAccount.AvatarMediaAttachment.Thumbnail.ContentType
|
||||
instance.ThumbnailDescription = iAccount.AvatarMediaAttachment.Description
|
||||
} else {
|
||||
instance.Thumbnail = config.GetProtocol() + "://" + i.Domain + "/assets/logo.webp" // default thumb
|
||||
|
|
@ -1533,6 +1591,8 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
|
|||
|
||||
thumbnail.URL = iAccount.AvatarMediaAttachment.URL
|
||||
thumbnail.Type = iAccount.AvatarMediaAttachment.File.ContentType
|
||||
thumbnail.StaticURL = iAccount.AvatarMediaAttachment.Thumbnail.URL
|
||||
thumbnail.StaticType = iAccount.AvatarMediaAttachment.Thumbnail.ContentType
|
||||
thumbnail.Description = iAccount.AvatarMediaAttachment.Description
|
||||
thumbnail.Blurhash = iAccount.AvatarMediaAttachment.Blurhash
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -981,28 +981,6 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
|
|||
"pinned": false,
|
||||
"content": "\u003cp\u003ehi \u003cspan class=\"h-card\"\u003e\u003ca href=\"http://localhost:8080/@admin\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\"\u003e@\u003cspan\u003eadmin\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e here's some media for ya\u003c/p\u003e",
|
||||
"reblog": null,
|
||||
"account": {
|
||||
"id": "01FHMQX3GAABWSM0S2VZEC2SWC",
|
||||
"username": "Some_User",
|
||||
"acct": "Some_User@example.org",
|
||||
"display_name": "some user",
|
||||
"locked": true,
|
||||
"discoverable": true,
|
||||
"bot": false,
|
||||
"created_at": "2020-08-10T12:13:28.000Z",
|
||||
"note": "i'm a real son of a gun",
|
||||
"url": "http://example.org/@Some_User",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.webp",
|
||||
"header_static": "http://localhost:8080/assets/default_header.webp",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 1,
|
||||
"last_status_at": "2023-11-02T10:44:25.000Z",
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
},
|
||||
"mentions": [
|
||||
{
|
||||
"id": "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
|
|
@ -1035,6 +1013,28 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
|
|||
"with_approval": []
|
||||
}
|
||||
},
|
||||
"account": {
|
||||
"id": "01FHMQX3GAABWSM0S2VZEC2SWC",
|
||||
"username": "Some_User",
|
||||
"acct": "Some_User@example.org",
|
||||
"display_name": "some user",
|
||||
"locked": true,
|
||||
"discoverable": true,
|
||||
"bot": false,
|
||||
"created_at": "2020-08-10T12:13:28.000Z",
|
||||
"note": "i'm a real son of a gun",
|
||||
"url": "http://example.org/@Some_User",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.webp",
|
||||
"header_static": "http://localhost:8080/assets/default_header.webp",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 1,
|
||||
"last_status_at": "2023-11-02T10:44:25.000Z",
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
},
|
||||
"media_attachments": [
|
||||
{
|
||||
"id": "01HE7Y3C432WRSNS10EZM86SA5",
|
||||
|
|
@ -1065,7 +1065,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
|
|||
"description": "Photograph of a sloth, Public Domain.",
|
||||
"blurhash": "LKE3VIw}0KD%a2o{M|t7NFWps:t7",
|
||||
"Sensitive": true,
|
||||
"MIMEType": "image/jpg"
|
||||
"MIMEType": "image/jpg",
|
||||
"PreviewMIMEType": "image/webp"
|
||||
},
|
||||
{
|
||||
"id": "01HE7ZFX9GKA5ZZVD4FACABSS9",
|
||||
|
|
@ -1079,7 +1080,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
|
|||
"description": "SVG line art of a sloth, public domain",
|
||||
"blurhash": "L26*j+~qE1RP?wxut7ofRlM{R*of",
|
||||
"Sensitive": true,
|
||||
"MIMEType": ""
|
||||
"MIMEType": "",
|
||||
"PreviewMIMEType": ""
|
||||
},
|
||||
{
|
||||
"id": "01HE88YG74PVAB81PX2XA9F3FG",
|
||||
|
|
@ -1093,7 +1095,8 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
|
|||
"description": "Jolly salsa song, public domain.",
|
||||
"blurhash": null,
|
||||
"Sensitive": true,
|
||||
"MIMEType": ""
|
||||
"MIMEType": "",
|
||||
"PreviewMIMEType": ""
|
||||
}
|
||||
],
|
||||
"LanguageTag": "en",
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ func (suite *InternalToRSSTestSuite) TestStatusToRSSItem2() {
|
|||
suite.Equal("62529", item.Enclosure.Length)
|
||||
suite.Equal("image/jpeg", item.Enclosure.Type)
|
||||
suite.Equal("http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg", item.Enclosure.Url)
|
||||
suite.Equal("hello world! #welcome ! first post on the instance <img src=\"http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png\" title=\":rainbow:\" alt=\":rainbow:\" width=\"25\" height=\"25\"/> !", item.Content)
|
||||
suite.Equal("hello world! #welcome ! first post on the instance <img src=\"http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png\" title=\":rainbow:\" alt=\":rainbow:\" width=\"25\" height=\"25\" /> !", item.Content)
|
||||
}
|
||||
|
||||
func (suite *InternalToRSSTestSuite) TestStatusToRSSItem3() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue