mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 06:02:26 -05:00 
			
		
		
		
	[feature/frontend] Add player for audio files; use thumbnail for poster (#3099)
		
	* [feature/frontend] Audio player for audio media types * use video preview images for previews instead of video itself * don't preload * update tests for new zork status * collapse media gallery into single row when small
This commit is contained in:
		
					parent
					
						
							
								16421f7576
							
						
					
				
			
			
				commit
				
					
						9efb11d848
					
				
			
		
					 26 changed files with 327 additions and 95 deletions
				
			
		|  | @ -82,7 +82,7 @@ func (suite *OutboxGetTestSuite) TestGetOutbox() { | ||||||
|   "@context": "https://www.w3.org/ns/activitystreams", |   "@context": "https://www.w3.org/ns/activitystreams", | ||||||
|   "first": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40", |   "first": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40", | ||||||
|   "id": "http://localhost:8080/users/the_mighty_zork/outbox", |   "id": "http://localhost:8080/users/the_mighty_zork/outbox", | ||||||
|   "totalItems": 7, |   "totalItems": 8, | ||||||
|   "type": "OrderedCollection" |   "type": "OrderedCollection" | ||||||
| }`, dst.String()) | }`, dst.String()) | ||||||
| 
 | 
 | ||||||
|  | @ -161,7 +161,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxFirstPage() { | ||||||
|   ], |   ], | ||||||
|   "partOf": "http://localhost:8080/users/the_mighty_zork/outbox", |   "partOf": "http://localhost:8080/users/the_mighty_zork/outbox", | ||||||
|   "prev": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40\u0026min_id=01HH9KYNQPA416TNJ53NSATP40", |   "prev": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40\u0026min_id=01HH9KYNQPA416TNJ53NSATP40", | ||||||
|   "totalItems": 7, |   "totalItems": 8, | ||||||
|   "type": "OrderedCollectionPage" |   "type": "OrderedCollectionPage" | ||||||
| }`, dst.String()) | }`, dst.String()) | ||||||
| 
 | 
 | ||||||
|  | @ -224,7 +224,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() { | ||||||
|   "id": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40&max_id=01F8MHAMCHF6Y650WCRSCP4WMY", |   "id": "http://localhost:8080/users/the_mighty_zork/outbox?limit=40&max_id=01F8MHAMCHF6Y650WCRSCP4WMY", | ||||||
|   "orderedItems": [], |   "orderedItems": [], | ||||||
|   "partOf": "http://localhost:8080/users/the_mighty_zork/outbox", |   "partOf": "http://localhost:8080/users/the_mighty_zork/outbox", | ||||||
|   "totalItems": 7, |   "totalItems": 8, | ||||||
|   "type": "OrderedCollectionPage" |   "type": "OrderedCollectionPage" | ||||||
| }`, dst.String()) | }`, dst.String()) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -79,7 +79,7 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() { | ||||||
| 	suite.Equal("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg", apimodelAccount.HeaderStatic) | 	suite.Equal("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg", apimodelAccount.HeaderStatic) | ||||||
| 	suite.Equal(2, apimodelAccount.FollowersCount) | 	suite.Equal(2, apimodelAccount.FollowersCount) | ||||||
| 	suite.Equal(2, apimodelAccount.FollowingCount) | 	suite.Equal(2, apimodelAccount.FollowingCount) | ||||||
| 	suite.Equal(7, apimodelAccount.StatusesCount) | 	suite.Equal(8, apimodelAccount.StatusesCount) | ||||||
| 	suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy) | 	suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy) | ||||||
| 	suite.Equal(testAccount.Settings.Language, apimodelAccount.Source.Language) | 	suite.Equal(testAccount.Settings.Language, apimodelAccount.Source.Language) | ||||||
| 	suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note) | 	suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note) | ||||||
|  |  | ||||||
|  | @ -240,8 +240,8 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() { | ||||||
|       "header_description": "A very old-school screenshot of the original team fortress mod for quake", |       "header_description": "A very old-school screenshot of the original team fortress mod for quake", | ||||||
|       "followers_count": 2, |       "followers_count": 2, | ||||||
|       "following_count": 2, |       "following_count": 2, | ||||||
|       "statuses_count": 7, |       "statuses_count": 8, | ||||||
|       "last_status_at": "2023-12-10T09:24:00.000Z", |       "last_status_at": "2024-01-10T09:24:00.000Z", | ||||||
|       "emojis": [], |       "emojis": [], | ||||||
|       "fields": [], |       "fields": [], | ||||||
|       "enable_rss": true, |       "enable_rss": true, | ||||||
|  |  | ||||||
|  | @ -135,7 +135,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() { | ||||||
|   }, |   }, | ||||||
|   "stats": { |   "stats": { | ||||||
|     "domain_count": 2, |     "domain_count": 2, | ||||||
|     "status_count": 19, |     "status_count": 20, | ||||||
|     "user_count": 4 |     "user_count": 4 | ||||||
|   }, |   }, | ||||||
|   "thumbnail": "http://localhost:8080/assets/logo.png", |   "thumbnail": "http://localhost:8080/assets/logo.png", | ||||||
|  | @ -256,7 +256,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() { | ||||||
|   }, |   }, | ||||||
|   "stats": { |   "stats": { | ||||||
|     "domain_count": 2, |     "domain_count": 2, | ||||||
|     "status_count": 19, |     "status_count": 20, | ||||||
|     "user_count": 4 |     "user_count": 4 | ||||||
|   }, |   }, | ||||||
|   "thumbnail": "http://localhost:8080/assets/logo.png", |   "thumbnail": "http://localhost:8080/assets/logo.png", | ||||||
|  | @ -377,7 +377,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() { | ||||||
|   }, |   }, | ||||||
|   "stats": { |   "stats": { | ||||||
|     "domain_count": 2, |     "domain_count": 2, | ||||||
|     "status_count": 19, |     "status_count": 20, | ||||||
|     "user_count": 4 |     "user_count": 4 | ||||||
|   }, |   }, | ||||||
|   "thumbnail": "http://localhost:8080/assets/logo.png", |   "thumbnail": "http://localhost:8080/assets/logo.png", | ||||||
|  | @ -549,7 +549,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() { | ||||||
|   }, |   }, | ||||||
|   "stats": { |   "stats": { | ||||||
|     "domain_count": 2, |     "domain_count": 2, | ||||||
|     "status_count": 19, |     "status_count": 20, | ||||||
|     "user_count": 4 |     "user_count": 4 | ||||||
|   }, |   }, | ||||||
|   "thumbnail": "http://localhost:8080/assets/logo.png", |   "thumbnail": "http://localhost:8080/assets/logo.png", | ||||||
|  | @ -692,7 +692,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() { | ||||||
|   }, |   }, | ||||||
|   "stats": { |   "stats": { | ||||||
|     "domain_count": 2, |     "domain_count": 2, | ||||||
|     "status_count": 19, |     "status_count": 20, | ||||||
|     "user_count": 4 |     "user_count": 4 | ||||||
|   }, |   }, | ||||||
|   "thumbnail": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/attachment/original/`+instanceAccount.AvatarMediaAttachment.ID+`.gif",`+` |   "thumbnail": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/attachment/original/`+instanceAccount.AvatarMediaAttachment.ID+`.gif",`+` | ||||||
|  | @ -850,7 +850,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() { | ||||||
|   }, |   }, | ||||||
|   "stats": { |   "stats": { | ||||||
|     "domain_count": 2, |     "domain_count": 2, | ||||||
|     "status_count": 19, |     "status_count": 20, | ||||||
|     "user_count": 4 |     "user_count": 4 | ||||||
|   }, |   }, | ||||||
|   "thumbnail": "http://localhost:8080/assets/logo.png", |   "thumbnail": "http://localhost:8080/assets/logo.png", | ||||||
|  |  | ||||||
|  | @ -916,7 +916,7 @@ func (suite *SearchGetTestSuite) TestSearchAAny() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	suite.Len(searchResult.Accounts, 5) | 	suite.Len(searchResult.Accounts, 5) | ||||||
| 	suite.Len(searchResult.Statuses, 6) | 	suite.Len(searchResult.Statuses, 7) | ||||||
| 	suite.Len(searchResult.Hashtags, 0) | 	suite.Len(searchResult.Hashtags, 0) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -959,7 +959,7 @@ func (suite *SearchGetTestSuite) TestSearchAAnyFollowingOnly() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	suite.Len(searchResult.Accounts, 2) | 	suite.Len(searchResult.Accounts, 2) | ||||||
| 	suite.Len(searchResult.Statuses, 6) | 	suite.Len(searchResult.Statuses, 7) | ||||||
| 	suite.Len(searchResult.Hashtags, 0) | 	suite.Len(searchResult.Hashtags, 0) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1002,7 +1002,7 @@ func (suite *SearchGetTestSuite) TestSearchAStatuses() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	suite.Len(searchResult.Accounts, 0) | 	suite.Len(searchResult.Accounts, 0) | ||||||
| 	suite.Len(searchResult.Statuses, 6) | 	suite.Len(searchResult.Statuses, 7) | ||||||
| 	suite.Len(searchResult.Hashtags, 0) | 	suite.Len(searchResult.Hashtags, 0) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -114,8 +114,8 @@ func (suite *StatusHistoryTestSuite) TestGetHistory() { | ||||||
|       "header_description": "A very old-school screenshot of the original team fortress mod for quake", |       "header_description": "A very old-school screenshot of the original team fortress mod for quake", | ||||||
|       "followers_count": 2, |       "followers_count": 2, | ||||||
|       "following_count": 2, |       "following_count": 2, | ||||||
|       "statuses_count": 7, |       "statuses_count": 8, | ||||||
|       "last_status_at": "2023-12-10T09:24:00.000Z", |       "last_status_at": "2024-01-10T09:24:00.000Z", | ||||||
|       "emojis": [], |       "emojis": [], | ||||||
|       "fields": [], |       "fields": [], | ||||||
|       "enable_rss": true, |       "enable_rss": true, | ||||||
|  |  | ||||||
|  | @ -132,8 +132,8 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() { | ||||||
|     "header_description": "A very old-school screenshot of the original team fortress mod for quake", |     "header_description": "A very old-school screenshot of the original team fortress mod for quake", | ||||||
|     "followers_count": 2, |     "followers_count": 2, | ||||||
|     "following_count": 2, |     "following_count": 2, | ||||||
|     "statuses_count": 7, |     "statuses_count": 8, | ||||||
|     "last_status_at": "2023-12-10T09:24:00.000Z", |     "last_status_at": "2024-01-10T09:24:00.000Z", | ||||||
|     "emojis": [], |     "emojis": [], | ||||||
|     "fields": [], |     "fields": [], | ||||||
|     "enable_rss": true, |     "enable_rss": true, | ||||||
|  | @ -197,8 +197,8 @@ func (suite *StatusMuteTestSuite) TestMuteUnmuteStatus() { | ||||||
|     "header_description": "A very old-school screenshot of the original team fortress mod for quake", |     "header_description": "A very old-school screenshot of the original team fortress mod for quake", | ||||||
|     "followers_count": 2, |     "followers_count": 2, | ||||||
|     "following_count": 2, |     "following_count": 2, | ||||||
|     "statuses_count": 7, |     "statuses_count": 8, | ||||||
|     "last_status_at": "2023-12-10T09:24:00.000Z", |     "last_status_at": "2024-01-10T09:24:00.000Z", | ||||||
|     "emojis": [], |     "emojis": [], | ||||||
|     "fields": [], |     "fields": [], | ||||||
|     "enable_rss": true, |     "enable_rss": true, | ||||||
|  |  | ||||||
|  | @ -90,12 +90,23 @@ type Attachment struct { | ||||||
| 	// A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet. | 	// A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet. | ||||||
| 	// See https://github.com/woltapp/blurhash | 	// See https://github.com/woltapp/blurhash | ||||||
| 	Blurhash *string `json:"blurhash"` | 	Blurhash *string `json:"blurhash"` | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	// Additional fields not exposed via JSON | // WebAttachment is like Attachment, but with | ||||||
| 	// (used only internally for templating etc). | // additional fields not exposed via JSON; | ||||||
|  | // used only internally for templating etc. | ||||||
|  | // | ||||||
|  | // swagger:ignore | ||||||
|  | type WebAttachment struct { | ||||||
|  | 	*Attachment | ||||||
| 
 | 
 | ||||||
| 	// Parent status of this media is sensitive. | 	// Parent status of this | ||||||
| 	Sensitive bool `json:"-"` | 	// media is sensitive. | ||||||
|  | 	Sensitive bool | ||||||
|  | 
 | ||||||
|  | 	// MIME type of | ||||||
|  | 	// the attachment. | ||||||
|  | 	MIMEType string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // MediaMeta models media metadata. | // MediaMeta models media metadata. | ||||||
|  |  | ||||||
|  | @ -111,6 +111,10 @@ type Status struct { | ||||||
| type WebStatus struct { | type WebStatus struct { | ||||||
| 	*Status | 	*Status | ||||||
| 
 | 
 | ||||||
|  | 	// Web version of media | ||||||
|  | 	// attached to this status. | ||||||
|  | 	MediaAttachments []*WebAttachment `json:"media_attachments"` | ||||||
|  | 
 | ||||||
| 	// Template-ready language tag and | 	// Template-ready language tag and | ||||||
| 	// string, based on *status.Language. | 	// string, based on *status.Language. | ||||||
| 	LanguageTag *language.Language | 	LanguageTag *language.Language | ||||||
|  |  | ||||||
|  | @ -46,7 +46,7 @@ type AccountTestSuite struct { | ||||||
| func (suite *AccountTestSuite) TestGetAccountStatuses() { | func (suite *AccountTestSuite) TestGetAccountStatuses() { | ||||||
| 	statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, false, false, "", "", false, false) | 	statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, false, false, "", "", false, false) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Len(statuses, 7) | 	suite.Len(statuses, 8) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountTestSuite) TestGetAccountStatusesPageDown() { | func (suite *AccountTestSuite) TestGetAccountStatusesPageDown() { | ||||||
|  | @ -69,7 +69,7 @@ func (suite *AccountTestSuite) TestGetAccountStatusesPageDown() { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		suite.FailNow(err.Error()) | 		suite.FailNow(err.Error()) | ||||||
| 	} | 	} | ||||||
| 	suite.Len(statuses, 1) | 	suite.Len(statuses, 2) | ||||||
| 
 | 
 | ||||||
| 	// try to get the last page (should be empty) | 	// try to get the last page (should be empty) | ||||||
| 	statuses, err = suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 3, false, false, statuses[len(statuses)-1].ID, "", false, false) | 	statuses, err = suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 3, false, false, statuses[len(statuses)-1].ID, "", false, false) | ||||||
|  | @ -187,7 +187,7 @@ func (suite *AccountTestSuite) TestGetAccountStatusesExcludeRepliesExcludesSelfR | ||||||
| func (suite *AccountTestSuite) TestGetAccountStatusesMediaOnly() { | func (suite *AccountTestSuite) TestGetAccountStatusesMediaOnly() { | ||||||
| 	statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, false, false, "", "", true, false) | 	statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, false, false, "", "", true, false) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Len(statuses, 1) | 	suite.Len(statuses, 2) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *AccountTestSuite) TestGetAccountBy() { | func (suite *AccountTestSuite) TestGetAccountBy() { | ||||||
|  |  | ||||||
|  | @ -114,7 +114,7 @@ func (suite *BasicTestSuite) TestGetAllStatuses() { | ||||||
| 	s := []*gtsmodel.Status{} | 	s := []*gtsmodel.Status{} | ||||||
| 	err := suite.db.GetAll(context.Background(), &s) | 	err := suite.db.GetAll(context.Background(), &s) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Len(s, 23) | 	suite.Len(s, 24) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *BasicTestSuite) TestGetAllNotNull() { | func (suite *BasicTestSuite) TestGetAllNotNull() { | ||||||
|  |  | ||||||
|  | @ -47,7 +47,7 @@ func (suite *InstanceTestSuite) TestCountInstanceUsersRemote() { | ||||||
| func (suite *InstanceTestSuite) TestCountInstanceStatuses() { | func (suite *InstanceTestSuite) TestCountInstanceStatuses() { | ||||||
| 	count, err := suite.db.CountInstanceStatuses(context.Background(), config.GetHost()) | 	count, err := suite.db.CountInstanceStatuses(context.Background(), config.GetHost()) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal(19, count) | 	suite.Equal(20, count) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *InstanceTestSuite) TestCountInstanceStatusesRemote() { | func (suite *InstanceTestSuite) TestCountInstanceStatusesRemote() { | ||||||
|  |  | ||||||
|  | @ -169,12 +169,7 @@ func (suite *StatusTestSuite) TestGetStatusChildren() { | ||||||
| 	targetStatus := suite.testStatuses["local_account_1_status_1"] | 	targetStatus := suite.testStatuses["local_account_1_status_1"] | ||||||
| 	children, err := suite.db.GetStatusChildren(context.Background(), targetStatus.ID) | 	children, err := suite.db.GetStatusChildren(context.Background(), targetStatus.ID) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Len(children, 2) | 	suite.Len(children, 3) | ||||||
| 	for _, c := range children { |  | ||||||
| 		suite.Equal(targetStatus.URI, c.InReplyToURI) |  | ||||||
| 		suite.Equal(targetStatus.AccountID, c.InReplyToAccountID) |  | ||||||
| 		suite.Equal(targetStatus.ID, c.InReplyToID) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *StatusTestSuite) TestDeleteStatus() { | func (suite *StatusTestSuite) TestDeleteStatus() { | ||||||
|  |  | ||||||
|  | @ -155,7 +155,7 @@ func (suite *TimelineTestSuite) TestGetHomeTimeline() { | ||||||
| 		suite.FailNow(err.Error()) | 		suite.FailNow(err.Error()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	suite.checkStatuses(s, id.Highest, id.Lowest, 19) | 	suite.checkStatuses(s, id.Highest, id.Lowest, 20) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *TimelineTestSuite) TestGetHomeTimelineNoFollowing() { | func (suite *TimelineTestSuite) TestGetHomeTimelineNoFollowing() { | ||||||
|  | @ -187,7 +187,7 @@ func (suite *TimelineTestSuite) TestGetHomeTimelineNoFollowing() { | ||||||
| 		suite.FailNow(err.Error()) | 		suite.FailNow(err.Error()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	suite.checkStatuses(s, id.Highest, id.Lowest, 7) | 	suite.checkStatuses(s, id.Highest, id.Lowest, 8) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *TimelineTestSuite) TestGetHomeTimelineWithFutureStatus() { | func (suite *TimelineTestSuite) TestGetHomeTimelineWithFutureStatus() { | ||||||
|  | @ -209,7 +209,7 @@ func (suite *TimelineTestSuite) TestGetHomeTimelineWithFutureStatus() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	suite.NotContains(s, futureStatus) | 	suite.NotContains(s, futureStatus) | ||||||
| 	suite.checkStatuses(s, id.Highest, id.Lowest, 19) | 	suite.checkStatuses(s, id.Highest, id.Lowest, 20) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *TimelineTestSuite) TestGetHomeTimelineBackToFront() { | func (suite *TimelineTestSuite) TestGetHomeTimelineBackToFront() { | ||||||
|  | @ -240,8 +240,8 @@ func (suite *TimelineTestSuite) TestGetHomeTimelineFromHighest() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	suite.checkStatuses(s, id.Highest, id.Lowest, 5) | 	suite.checkStatuses(s, id.Highest, id.Lowest, 5) | ||||||
| 	suite.Equal("01HH9KYNQPA416TNJ53NSATP40", s[0].ID) | 	suite.Equal("01J2M1HPFSS54S60Y0KYV23KJE", s[0].ID) | ||||||
| 	suite.Equal("01G20ZM733MGN8J344T4ZDDFY1", s[len(s)-1].ID) | 	suite.Equal("01G36SF3V6Y6V5BF9P4R7PQG7G", s[len(s)-1].ID) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *TimelineTestSuite) TestGetListTimelineNoParams() { | func (suite *TimelineTestSuite) TestGetListTimelineNoParams() { | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ | ||||||
| package media | package media | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"cmp" | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | @ -198,6 +199,30 @@ func (res *ffprobeResult) ImageMeta() (width int, height int, err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // EmbeddedImageMeta extracts embedded image metadata contained within ffprobe'd media result | ||||||
|  | // streams, should be used for pulling album image (can be animated image) from audio files. | ||||||
|  | func (res *ffprobeResult) EmbeddedImageMeta() (width int, height int, framerate float32, err error) { | ||||||
|  | 	for _, stream := range res.Streams { | ||||||
|  | 		if stream.Width > width { | ||||||
|  | 			width = stream.Width | ||||||
|  | 		} | ||||||
|  | 		if stream.Height > height { | ||||||
|  | 			height = stream.Height | ||||||
|  | 		} | ||||||
|  | 		if fr := stream.GetFrameRate(); fr > 0 { | ||||||
|  | 			if framerate == 0 || fr < framerate { | ||||||
|  | 				framerate = fr | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// Need width + height but | ||||||
|  | 	// no framerate is fine. | ||||||
|  | 	if width == 0 || height == 0 { | ||||||
|  | 		err = errors.New("invalid image stream(s)") | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // VideoMeta extracts video metadata contained within ffprobe'd media result streams. | // VideoMeta extracts video metadata contained within ffprobe'd media result streams. | ||||||
| func (res *ffprobeResult) VideoMeta() (width, height int, framerate float32, err error) { | func (res *ffprobeResult) VideoMeta() (width, height int, framerate float32, err error) { | ||||||
| 	for _, stream := range res.Streams { | 	for _, stream := range res.Streams { | ||||||
|  | @ -222,6 +247,7 @@ func (res *ffprobeResult) VideoMeta() (width, height int, framerate float32, err | ||||||
| type ffprobeStream struct { | type ffprobeStream struct { | ||||||
| 	CodecName    string `json:"codec_name"` | 	CodecName    string `json:"codec_name"` | ||||||
| 	AvgFrameRate string `json:"avg_frame_rate"` | 	AvgFrameRate string `json:"avg_frame_rate"` | ||||||
|  | 	RFrameRate   string `json:"r_frame_rate"` | ||||||
| 	Width        int    `json:"width"` | 	Width        int    `json:"width"` | ||||||
| 	Height       int    `json:"height"` | 	Height       int    `json:"height"` | ||||||
| 	// + unused fields. | 	// + unused fields. | ||||||
|  | @ -229,7 +255,7 @@ type ffprobeStream struct { | ||||||
| 
 | 
 | ||||||
| // GetFrameRate calculates float32 framerate value from stream json string. | // GetFrameRate calculates float32 framerate value from stream json string. | ||||||
| func (str *ffprobeStream) GetFrameRate() float32 { | func (str *ffprobeStream) GetFrameRate() float32 { | ||||||
| 	if str.AvgFrameRate != "" { | 	numDen := func(strFR string) (float32, float32) { | ||||||
| 		var ( | 		var ( | ||||||
| 			// numerator | 			// numerator | ||||||
| 			num float32 | 			num float32 | ||||||
|  | @ -239,7 +265,7 @@ func (str *ffprobeStream) GetFrameRate() float32 { | ||||||
| 		) | 		) | ||||||
| 
 | 
 | ||||||
| 		// Check for a provided inequality, i.e. numerator / denominator. | 		// Check for a provided inequality, i.e. numerator / denominator. | ||||||
| 		if p := strings.SplitN(str.AvgFrameRate, "/", 2); len(p) == 2 { | 		if p := strings.SplitN(strFR, "/", 2); len(p) == 2 { | ||||||
| 			n, _ := strconv.ParseFloat(p[0], 32) | 			n, _ := strconv.ParseFloat(p[0], 32) | ||||||
| 			d, _ := strconv.ParseFloat(p[1], 32) | 			d, _ := strconv.ParseFloat(p[1], 32) | ||||||
| 			num, den = float32(n), float32(d) | 			num, den = float32(n), float32(d) | ||||||
|  | @ -248,8 +274,26 @@ func (str *ffprobeStream) GetFrameRate() float32 { | ||||||
| 			num = float32(n) | 			num = float32(n) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return num / den | 		return num, den | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	var num, den float32 | ||||||
|  | 	if str.AvgFrameRate != "" { | ||||||
|  | 		// Check if we have avg_frame_rate. | ||||||
|  | 		num, den = numDen(str.AvgFrameRate) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if num == 0 && str.RFrameRate != "" { | ||||||
|  | 		// Check if we have r_frame_rate. | ||||||
|  | 		num, den = numDen(str.RFrameRate) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if num != 0 { | ||||||
|  | 		// Found it. | ||||||
|  | 		// Avoid divide by zero. | ||||||
|  | 		return num / cmp.Or(den, 1) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return 0 | 	return 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -299,8 +299,14 @@ func (p *ProcessingMedia) store(ctx context.Context) error { | ||||||
| 
 | 
 | ||||||
| 		// Extract image metadata from streams (if any), | 		// Extract image metadata from streams (if any), | ||||||
| 		// this will only exist for embedded album art. | 		// this will only exist for embedded album art. | ||||||
| 		width, height, _ := result.ImageMeta() | 		width, height, framerate, _ := result.EmbeddedImageMeta() | ||||||
| 		if width > 0 && height > 0 { | 		if width > 0 && height > 0 { | ||||||
|  | 			// Unlikely to need these but masto API includes them. | ||||||
|  | 			p.media.FileMeta.Original.Width = width | ||||||
|  | 			p.media.FileMeta.Original.Height = height | ||||||
|  | 			if framerate != 0 { | ||||||
|  | 				p.media.FileMeta.Original.Framerate = &framerate | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 			// Determine thumbnail dimensions to use. | 			// Determine thumbnail dimensions to use. | ||||||
| 			thumbWidth, thumbHeight := thumbSize(width, height) | 			thumbWidth, thumbHeight := thumbSize(width, height) | ||||||
|  |  | ||||||
|  | @ -41,11 +41,11 @@ func (suite *GetRSSTestSuite) TestGetAccountRSSAdmin() { | ||||||
| func (suite *GetRSSTestSuite) TestGetAccountRSSZork() { | func (suite *GetRSSTestSuite) TestGetAccountRSSZork() { | ||||||
| 	getFeed, lastModified, err := suite.accountProcessor.GetRSSFeedForUsername(context.Background(), "the_mighty_zork") | 	getFeed, lastModified, err := suite.accountProcessor.GetRSSFeedForUsername(context.Background(), "the_mighty_zork") | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.EqualValues(1702200240, lastModified.Unix()) | 	suite.EqualValues(1704878640, lastModified.Unix()) | ||||||
| 
 | 
 | ||||||
| 	feed, err := getFeed() | 	feed, err := getFeed() | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n  <channel>\n    <title>Posts from @the_mighty_zork@localhost:8080</title>\n    <link>http://localhost:8080/@the_mighty_zork</link>\n    <description>Posts from @the_mighty_zork@localhost:8080</description>\n    <pubDate>Sun, 10 Dec 2023 09:24:00 +0000</pubDate>\n    <lastBuildDate>Sun, 10 Dec 2023 09:24:00 +0000</lastBuildDate>\n    <image>\n      <url>http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg</url>\n      <title>Avatar for @the_mighty_zork@localhost:8080</title>\n      <link>http://localhost:8080/@the_mighty_zork</link>\n    </image>\n    <item>\n      <title>HTML in post</title>\n      <link>http://localhost:8080/@the_mighty_zork/statuses/01HH9KYNQPA416TNJ53NSATP40</link>\n      <description>@the_mighty_zork@localhost:8080 made a new post: "Here's a bunch of HTML, read it and weep, weep then!

```html
<section class="about-user">
 <div class="col-header">
 <h2>About</h2>
 </div> 
 <div class="fields">
 <h3 class="sr-only">Fields</h3>
 <dl>
...</description>\n      <content:encoded><![CDATA[<p>Here's a bunch of HTML, read it and weep, weep then!</p><pre><code class=\"language-html\"><section class="about-user">\n    <div class="col-header">\n        <h2>About</h2>\n    </div>            \n    <div class="fields">\n        <h3 class="sr-only">Fields</h3>\n        <dl>\n            <div class="field">\n                <dt>should you follow me?</dt>\n                <dd>maybe!</dd>\n            </div>\n            <div class="field">\n                <dt>age</dt>\n                <dd>120</dd>\n            </div>\n        </dl>\n    </div>\n    <div class="bio">\n        <h3 class="sr-only">Bio</h3>\n        <p>i post about things that concern me</p>\n    </div>\n    <div class="sr-only" role="group">\n        <h3 class="sr-only">Stats</h3>\n        <span>Joined in Jun, 2022.</span>\n        <span>8 posts.</span>\n        <span>Followed by 1.</span>\n        <span>Following 1.</span>\n    </div>\n    <div class="accountstats" aria-hidden="true">\n        <b>Joined</b><time datetime="2022-06-04T13:12:00.000Z">Jun, 2022</time>\n        <b>Posts</b><span>8</span>\n        <b>Followed by</b><span>1</span>\n        <b>Following</b><span>1</span>\n    </div>\n</section>\n</code></pre><p>There, hope you liked that!</p>]]></content:encoded>\n      <author>@the_mighty_zork@localhost:8080</author>\n      <guid isPermaLink=\"true\">http://localhost:8080/@the_mighty_zork/statuses/01HH9KYNQPA416TNJ53NSATP40</guid>\n      <pubDate>Sun, 10 Dec 2023 09:24:00 +0000</pubDate>\n      <source>http://localhost:8080/@the_mighty_zork/feed.rss</source>\n    </item>\n    <item>\n      <title>introduction post</title>\n      <link>http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</link>\n      <description>@the_mighty_zork@localhost:8080 made a new post: "hello everyone!"</description>\n      <content:encoded><![CDATA[hello everyone!]]></content:encoded>\n      <author>@the_mighty_zork@localhost:8080</author>\n      <guid isPermaLink=\"true\">http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</guid>\n      <pubDate>Wed, 20 Oct 2021 10:40:37 +0000</pubDate>\n      <source>http://localhost:8080/@the_mighty_zork/feed.rss</source>\n    </item>\n  </channel>\n</rss>", feed) | 	suite.Equal("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n  <channel>\n    <title>Posts from @the_mighty_zork@localhost:8080</title>\n    <link>http://localhost:8080/@the_mighty_zork</link>\n    <description>Posts from @the_mighty_zork@localhost:8080</description>\n    <pubDate>Wed, 10 Jan 2024 09:24:00 +0000</pubDate>\n    <lastBuildDate>Wed, 10 Jan 2024 09:24:00 +0000</lastBuildDate>\n    <image>\n      <url>http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg</url>\n      <title>Avatar for @the_mighty_zork@localhost:8080</title>\n      <link>http://localhost:8080/@the_mighty_zork</link>\n    </image>\n    <item>\n      <title>HTML in post</title>\n      <link>http://localhost:8080/@the_mighty_zork/statuses/01HH9KYNQPA416TNJ53NSATP40</link>\n      <description>@the_mighty_zork@localhost:8080 made a new post: "Here's a bunch of HTML, read it and weep, weep then!

```html
<section class="about-user">
 <div class="col-header">
 <h2>About</h2>
 </div> 
 <div class="fields">
 <h3 class="sr-only">Fields</h3>
 <dl>
...</description>\n      <content:encoded><![CDATA[<p>Here's a bunch of HTML, read it and weep, weep then!</p><pre><code class=\"language-html\"><section class="about-user">\n    <div class="col-header">\n        <h2>About</h2>\n    </div>            \n    <div class="fields">\n        <h3 class="sr-only">Fields</h3>\n        <dl>\n            <div class="field">\n                <dt>should you follow me?</dt>\n                <dd>maybe!</dd>\n            </div>\n            <div class="field">\n                <dt>age</dt>\n                <dd>120</dd>\n            </div>\n        </dl>\n    </div>\n    <div class="bio">\n        <h3 class="sr-only">Bio</h3>\n        <p>i post about things that concern me</p>\n    </div>\n    <div class="sr-only" role="group">\n        <h3 class="sr-only">Stats</h3>\n        <span>Joined in Jun, 2022.</span>\n        <span>8 posts.</span>\n        <span>Followed by 1.</span>\n        <span>Following 1.</span>\n    </div>\n    <div class="accountstats" aria-hidden="true">\n        <b>Joined</b><time datetime="2022-06-04T13:12:00.000Z">Jun, 2022</time>\n        <b>Posts</b><span>8</span>\n        <b>Followed by</b><span>1</span>\n        <b>Following</b><span>1</span>\n    </div>\n</section>\n</code></pre><p>There, hope you liked that!</p>]]></content:encoded>\n      <author>@the_mighty_zork@localhost:8080</author>\n      <guid isPermaLink=\"true\">http://localhost:8080/@the_mighty_zork/statuses/01HH9KYNQPA416TNJ53NSATP40</guid>\n      <pubDate>Sun, 10 Dec 2023 09:24:00 +0000</pubDate>\n      <source>http://localhost:8080/@the_mighty_zork/feed.rss</source>\n    </item>\n    <item>\n      <title>introduction post</title>\n      <link>http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</link>\n      <description>@the_mighty_zork@localhost:8080 made a new post: "hello everyone!"</description>\n      <content:encoded><![CDATA[hello everyone!]]></content:encoded>\n      <author>@the_mighty_zork@localhost:8080</author>\n      <guid isPermaLink=\"true\">http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</guid>\n      <pubDate>Wed, 20 Oct 2021 10:40:37 +0000</pubDate>\n      <source>http://localhost:8080/@the_mighty_zork/feed.rss</source>\n    </item>\n  </channel>\n</rss>", feed) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *GetRSSTestSuite) TestGetAccountRSSZorkNoPosts() { | func (suite *GetRSSTestSuite) TestGetAccountRSSZorkNoPosts() { | ||||||
|  |  | ||||||
|  | @ -228,7 +228,7 @@ func (suite *GetTestSuite) TestGetNewTimelineMoreThanPossible() { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		suite.FailNow(err.Error()) | 		suite.FailNow(err.Error()) | ||||||
| 	} | 	} | ||||||
| 	suite.checkStatuses(statuses, id.Highest, id.Lowest, 19) | 	suite.checkStatuses(statuses, id.Highest, id.Lowest, 20) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *GetTestSuite) TestGetNewTimelineMoreThanPossiblePageUp() { | func (suite *GetTestSuite) TestGetNewTimelineMoreThanPossiblePageUp() { | ||||||
|  | @ -255,7 +255,7 @@ func (suite *GetTestSuite) TestGetNewTimelineMoreThanPossiblePageUp() { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		suite.FailNow(err.Error()) | 		suite.FailNow(err.Error()) | ||||||
| 	} | 	} | ||||||
| 	suite.checkStatuses(statuses, id.Highest, id.Lowest, 19) | 	suite.checkStatuses(statuses, id.Highest, id.Lowest, 20) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *GetTestSuite) TestGetNewTimelineNoFollowing() { | func (suite *GetTestSuite) TestGetNewTimelineNoFollowing() { | ||||||
|  | @ -284,7 +284,7 @@ func (suite *GetTestSuite) TestGetNewTimelineNoFollowing() { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		suite.FailNow(err.Error()) | 		suite.FailNow(err.Error()) | ||||||
| 	} | 	} | ||||||
| 	suite.checkStatuses(statuses, id.Highest, id.Lowest, 7) | 	suite.checkStatuses(statuses, id.Highest, id.Lowest, 8) | ||||||
| 
 | 
 | ||||||
| 	for _, s := range statuses { | 	for _, s := range statuses { | ||||||
| 		if s.GetAccountID() != testAccount.ID { | 		if s.GetAccountID() != testAccount.ID { | ||||||
|  |  | ||||||
|  | @ -40,7 +40,7 @@ func (suite *PruneTestSuite) TestPrune() { | ||||||
| 
 | 
 | ||||||
| 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal(18, pruned) | 	suite.Equal(19, pruned) | ||||||
| 	suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | 	suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -56,7 +56,7 @@ func (suite *PruneTestSuite) TestPruneTwice() { | ||||||
| 
 | 
 | ||||||
| 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal(18, pruned) | 	suite.Equal(19, pruned) | ||||||
| 	suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | 	suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | ||||||
| 
 | 
 | ||||||
| 	// Prune same again, nothing should be pruned this time. | 	// Prune same again, nothing should be pruned this time. | ||||||
|  | @ -78,7 +78,7 @@ func (suite *PruneTestSuite) TestPruneTo0() { | ||||||
| 
 | 
 | ||||||
| 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal(23, pruned) | 	suite.Equal(24, pruned) | ||||||
| 	suite.Equal(0, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | 	suite.Equal(0, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -95,7 +95,7 @@ func (suite *PruneTestSuite) TestPruneToInfinityAndBeyond() { | ||||||
| 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal(0, pruned) | 	suite.Equal(0, pruned) | ||||||
| 	suite.Equal(23, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | 	suite.Equal(24, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestPruneTestSuite(t *testing.T) { | func TestPruneTestSuite(t *testing.T) { | ||||||
|  |  | ||||||
|  | @ -624,7 +624,7 @@ func (c *Converter) AttachmentToAPIAttachment(ctx context.Context, a *gtsmodel.M | ||||||
| 			Y: a.FileMeta.Focus.Y, | 			Y: a.FileMeta.Focus.Y, | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	case gtsmodel.FileTypeVideo: | 	case gtsmodel.FileTypeVideo, gtsmodel.FileTypeAudio: | ||||||
| 		if i := a.FileMeta.Original.Duration; i != nil { | 		if i := a.FileMeta.Original.Duration; i != nil { | ||||||
| 			apiAttachment.Meta.Original.Duration = *i | 			apiAttachment.Meta.Original.Duration = *i | ||||||
| 		} | 		} | ||||||
|  | @ -1062,14 +1062,36 @@ func (c *Converter) StatusToWebStatus( | ||||||
| 		webStatus.PollOptions = PollOptions | 		webStatus.PollOptions = PollOptions | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Mark local. | ||||||
|  | 	webStatus.Local = *s.Local | ||||||
|  | 
 | ||||||
| 	// Set additional templating | 	// Set additional templating | ||||||
| 	// variables on media attachments. | 	// variables on media attachments. | ||||||
| 	for _, a := range webStatus.MediaAttachments { | 
 | ||||||
| 		a.Sensitive = webStatus.Sensitive | 	// Get gtsmodel attachments | ||||||
|  | 	// into a convenient map. | ||||||
|  | 	ogAttachments := make( | ||||||
|  | 		map[string]*gtsmodel.MediaAttachment, | ||||||
|  | 		len(s.Attachments), | ||||||
|  | 	) | ||||||
|  | 	for _, a := range s.Attachments { | ||||||
|  | 		ogAttachments[a.ID] = a | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Mark this as a local status. | 	// Convert each API attachment | ||||||
| 	webStatus.Local = *s.Local | 	// into a web attachment. | ||||||
|  | 	webStatus.MediaAttachments = make( | ||||||
|  | 		[]*apimodel.WebAttachment, | ||||||
|  | 		len(apiStatus.MediaAttachments), | ||||||
|  | 	) | ||||||
|  | 	for i, apiAttachment := range apiStatus.MediaAttachments { | ||||||
|  | 		ogAttachment := ogAttachments[apiAttachment.ID] | ||||||
|  | 		webStatus.MediaAttachments[i] = &apimodel.WebAttachment{ | ||||||
|  | 			Attachment: apiAttachment, | ||||||
|  | 			Sensitive:  apiStatus.Sensitive, | ||||||
|  | 			MIMEType:   ogAttachment.File.ContentType, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	return webStatus, nil | 	return webStatus, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -63,8 +63,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontend() { | ||||||
|   "header_description": "A very old-school screenshot of the original team fortress mod for quake", |   "header_description": "A very old-school screenshot of the original team fortress mod for quake", | ||||||
|   "followers_count": 2, |   "followers_count": 2, | ||||||
|   "following_count": 2, |   "following_count": 2, | ||||||
|   "statuses_count": 7, |   "statuses_count": 8, | ||||||
|   "last_status_at": "2023-12-10T09:24:00.000Z", |   "last_status_at": "2024-01-10T09:24:00.000Z", | ||||||
|   "emojis": [], |   "emojis": [], | ||||||
|   "fields": [], |   "fields": [], | ||||||
|   "enable_rss": true, |   "enable_rss": true, | ||||||
|  | @ -116,8 +116,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendAliasedAndMoved() | ||||||
|   "header_description": "A very old-school screenshot of the original team fortress mod for quake", |   "header_description": "A very old-school screenshot of the original team fortress mod for quake", | ||||||
|   "followers_count": 2, |   "followers_count": 2, | ||||||
|   "following_count": 2, |   "following_count": 2, | ||||||
|   "statuses_count": 7, |   "statuses_count": 8, | ||||||
|   "last_status_at": "2023-12-10T09:24:00.000Z", |   "last_status_at": "2024-01-10T09:24:00.000Z", | ||||||
|   "emojis": [], |   "emojis": [], | ||||||
|   "fields": [], |   "fields": [], | ||||||
|   "source": { |   "source": { | ||||||
|  | @ -209,8 +209,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiStruct() | ||||||
|   "header_description": "A very old-school screenshot of the original team fortress mod for quake", |   "header_description": "A very old-school screenshot of the original team fortress mod for quake", | ||||||
|   "followers_count": 2, |   "followers_count": 2, | ||||||
|   "following_count": 2, |   "following_count": 2, | ||||||
|   "statuses_count": 7, |   "statuses_count": 8, | ||||||
|   "last_status_at": "2023-12-10T09:24:00.000Z", |   "last_status_at": "2024-01-10T09:24:00.000Z", | ||||||
|   "emojis": [ |   "emojis": [ | ||||||
|     { |     { | ||||||
|       "shortcode": "rainbow", |       "shortcode": "rainbow", | ||||||
|  | @ -259,8 +259,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiIDs() { | ||||||
|   "header_description": "A very old-school screenshot of the original team fortress mod for quake", |   "header_description": "A very old-school screenshot of the original team fortress mod for quake", | ||||||
|   "followers_count": 2, |   "followers_count": 2, | ||||||
|   "following_count": 2, |   "following_count": 2, | ||||||
|   "statuses_count": 7, |   "statuses_count": 8, | ||||||
|   "last_status_at": "2023-12-10T09:24:00.000Z", |   "last_status_at": "2024-01-10T09:24:00.000Z", | ||||||
|   "emojis": [ |   "emojis": [ | ||||||
|     { |     { | ||||||
|       "shortcode": "rainbow", |       "shortcode": "rainbow", | ||||||
|  | @ -305,8 +305,8 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() { | ||||||
|   "header_description": "A very old-school screenshot of the original team fortress mod for quake", |   "header_description": "A very old-school screenshot of the original team fortress mod for quake", | ||||||
|   "followers_count": 2, |   "followers_count": 2, | ||||||
|   "following_count": 2, |   "following_count": 2, | ||||||
|   "statuses_count": 7, |   "statuses_count": 8, | ||||||
|   "last_status_at": "2023-12-10T09:24:00.000Z", |   "last_status_at": "2024-01-10T09:24:00.000Z", | ||||||
|   "emojis": [], |   "emojis": [], | ||||||
|   "fields": [], |   "fields": [], | ||||||
|   "source": { |   "source": { | ||||||
|  | @ -943,6 +943,18 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() { | ||||||
|     "emojis": [], |     "emojis": [], | ||||||
|     "fields": [] |     "fields": [] | ||||||
|   }, |   }, | ||||||
|  |   "mentions": [ | ||||||
|  |     { | ||||||
|  |       "id": "01F8MH17FWEB39HZJ76B6VXSKF", | ||||||
|  |       "username": "admin", | ||||||
|  |       "url": "http://localhost:8080/@admin", | ||||||
|  |       "acct": "admin" | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "tags": [], | ||||||
|  |   "emojis": [], | ||||||
|  |   "card": null, | ||||||
|  |   "poll": null, | ||||||
|   "media_attachments": [ |   "media_attachments": [ | ||||||
|     { |     { | ||||||
|       "id": "01HE7Y3C432WRSNS10EZM86SA5", |       "id": "01HE7Y3C432WRSNS10EZM86SA5", | ||||||
|  | @ -971,7 +983,9 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() { | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       "description": "Photograph of a sloth, Public Domain.", |       "description": "Photograph of a sloth, Public Domain.", | ||||||
|       "blurhash": "LNEC{|w}0K9GsEtPM|j[NFbHoeof" |       "blurhash": "LNEC{|w}0K9GsEtPM|j[NFbHoeof", | ||||||
|  |       "Sensitive": true, | ||||||
|  |       "MIMEType": "image/jpg" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "id": "01HE7ZFX9GKA5ZZVD4FACABSS9", |       "id": "01HE7ZFX9GKA5ZZVD4FACABSS9", | ||||||
|  | @ -983,7 +997,9 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() { | ||||||
|       "preview_remote_url": null, |       "preview_remote_url": null, | ||||||
|       "meta": null, |       "meta": null, | ||||||
|       "description": "SVG line art of a sloth, public domain", |       "description": "SVG line art of a sloth, public domain", | ||||||
|       "blurhash": "L26*j+~qE1RP?wxut7ofRlM{R*of" |       "blurhash": "L26*j+~qE1RP?wxut7ofRlM{R*of", | ||||||
|  |       "Sensitive": true, | ||||||
|  |       "MIMEType": "image/svg" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "id": "01HE88YG74PVAB81PX2XA9F3FG", |       "id": "01HE88YG74PVAB81PX2XA9F3FG", | ||||||
|  | @ -995,21 +1011,11 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() { | ||||||
|       "preview_remote_url": null, |       "preview_remote_url": null, | ||||||
|       "meta": null, |       "meta": null, | ||||||
|       "description": "Jolly salsa song, public domain.", |       "description": "Jolly salsa song, public domain.", | ||||||
|       "blurhash": null |       "blurhash": null, | ||||||
|  |       "Sensitive": true, | ||||||
|  |       "MIMEType": "audio/mpeg" | ||||||
|     } |     } | ||||||
|   ], |   ], | ||||||
|   "mentions": [ |  | ||||||
|     { |  | ||||||
|       "id": "01F8MH17FWEB39HZJ76B6VXSKF", |  | ||||||
|       "username": "admin", |  | ||||||
|       "url": "http://localhost:8080/@admin", |  | ||||||
|       "acct": "admin" |  | ||||||
|     } |  | ||||||
|   ], |  | ||||||
|   "tags": [], |  | ||||||
|   "emojis": [], |  | ||||||
|   "card": null, |  | ||||||
|   "poll": null, |  | ||||||
|   "LanguageTag": "en", |   "LanguageTag": "en", | ||||||
|   "PollOptions": null, |   "PollOptions": null, | ||||||
|   "Local": false, |   "Local": false, | ||||||
|  | @ -1249,7 +1255,7 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() { | ||||||
|   }, |   }, | ||||||
|   "stats": { |   "stats": { | ||||||
|     "domain_count": 2, |     "domain_count": 2, | ||||||
|     "status_count": 19, |     "status_count": 20, | ||||||
|     "user_count": 4 |     "user_count": 4 | ||||||
|   }, |   }, | ||||||
|   "thumbnail": "http://localhost:8080/assets/logo.png", |   "thumbnail": "http://localhost:8080/assets/logo.png", | ||||||
|  |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								testrig/media/ghosts-original.mp3
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								testrig/media/ghosts-original.mp3
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								testrig/media/ghosts-small.jpg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								testrig/media/ghosts-small.jpg
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 6 KiB | 
|  | @ -989,6 +989,53 @@ func NewTestAttachments() map[string]*gtsmodel.MediaAttachment { | ||||||
| 			Header: util.Ptr(true), | 			Header: util.Ptr(true), | ||||||
| 			Cached: util.Ptr(true), | 			Cached: util.Ptr(true), | ||||||
| 		}, | 		}, | ||||||
|  | 		"local_account_1_status_8_attachment_1": { | ||||||
|  | 			ID:        "01J2M20K6K9XQC4WSB961YJHV6", | ||||||
|  | 			StatusID:  "01J2M1HPFSS54S60Y0KYV23KJE", | ||||||
|  | 			URL:       "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01J2M20K6K9XQC4WSB961YJHV6.mp3", | ||||||
|  | 			RemoteURL: "", | ||||||
|  | 			CreatedAt: TimeMustParse("2024-01-10T11:24:00+02:00"), | ||||||
|  | 			UpdatedAt: TimeMustParse("2024-01-10T11:24:00+02:00"), | ||||||
|  | 			Type:      gtsmodel.FileTypeAudio, | ||||||
|  | 			FileMeta: gtsmodel.FileMeta{ | ||||||
|  | 				Original: gtsmodel.Original{ | ||||||
|  | 					Width:  500, | ||||||
|  | 					Height: 500, | ||||||
|  | 					Size:   0, | ||||||
|  | 					Aspect: 0, | ||||||
|  | 				}, | ||||||
|  | 				Small: gtsmodel.Small{ | ||||||
|  | 					Width:  500, | ||||||
|  | 					Height: 500, | ||||||
|  | 					Size:   250000, | ||||||
|  | 					Aspect: 1, | ||||||
|  | 				}, | ||||||
|  | 				Focus: gtsmodel.Focus{ | ||||||
|  | 					X: 0, | ||||||
|  | 					Y: 0, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			AccountID:         "01F8MH1H7YV1Z7D2C8K2730QBF", | ||||||
|  | 			Description:       "This is a track from Nine Inch Nail's \"Ghosts I-V\" album. This is the third track from \"Ghosts II\".", | ||||||
|  | 			ScheduledStatusID: "", | ||||||
|  | 			Blurhash:          "LeDvfpayIUof01j[xuayxuayaxj[", | ||||||
|  | 			Processing:        2, | ||||||
|  | 			File: gtsmodel.File{ | ||||||
|  | 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01J2M20K6K9XQC4WSB961YJHV6.mp3", | ||||||
|  | 				ContentType: "audio/mpeg", | ||||||
|  | 				FileSize:    7483917, | ||||||
|  | 			}, | ||||||
|  | 			Thumbnail: gtsmodel.Thumbnail{ | ||||||
|  | 				Path:        "01F8MH1H7YV1Z7D2C8K2730QBF/attachment/small/01J2M20K6K9XQC4WSB961YJHV6.jpg", | ||||||
|  | 				ContentType: "image/jpeg", | ||||||
|  | 				FileSize:    6132, | ||||||
|  | 				URL:         "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/small/01J2M20K6K9XQC4WSB961YJHV6.jpg", | ||||||
|  | 				RemoteURL:   "", | ||||||
|  | 			}, | ||||||
|  | 			Avatar: util.Ptr(false), | ||||||
|  | 			Header: util.Ptr(false), | ||||||
|  | 			Cached: util.Ptr(true), | ||||||
|  | 		}, | ||||||
| 		"remote_account_1_status_1_attachment_1": { | 		"remote_account_1_status_1_attachment_1": { | ||||||
| 			ID:        "01FVW7RXPQ8YJHTEXYPE7Q8ZY0", | 			ID:        "01FVW7RXPQ8YJHTEXYPE7Q8ZY0", | ||||||
| 			StatusID:  "01FVW7JHQFSFK166WWKR8CBA6M", | 			StatusID:  "01FVW7JHQFSFK166WWKR8CBA6M", | ||||||
|  | @ -1347,6 +1394,10 @@ func newTestStoredAttachments() map[string]filenames { | ||||||
| 			Original: "team-fortress-original.jpg", | 			Original: "team-fortress-original.jpg", | ||||||
| 			Small:    "team-fortress-small.jpg", | 			Small:    "team-fortress-small.jpg", | ||||||
| 		}, | 		}, | ||||||
|  | 		"local_account_1_status_8_attachment_1": { | ||||||
|  | 			Original: "ghosts-original.mp3", | ||||||
|  | 			Small:    "ghosts-small.jpg", | ||||||
|  | 		}, | ||||||
| 		"remote_account_1_status_1_attachment_1": { | 		"remote_account_1_status_1_attachment_1": { | ||||||
| 			Original: "thoughtsofdog-original.jpg", | 			Original: "thoughtsofdog-original.jpg", | ||||||
| 			Small:    "thoughtsofdog-small.jpg", | 			Small:    "thoughtsofdog-small.jpg", | ||||||
|  | @ -1644,6 +1695,31 @@ func NewTestStatuses() map[string]*gtsmodel.Status { | ||||||
| 			Federated:                util.Ptr(true), | 			Federated:                util.Ptr(true), | ||||||
| 			ActivityStreamsType:      ap.ObjectNote, | 			ActivityStreamsType:      ap.ObjectNote, | ||||||
| 		}, | 		}, | ||||||
|  | 		"local_account_1_status_8": { | ||||||
|  | 			ID:                       "01J2M1HPFSS54S60Y0KYV23KJE", | ||||||
|  | 			URI:                      "http://localhost:8080/users/the_mighty_zork/statuses/01J2M1HPFSS54S60Y0KYV23KJE", | ||||||
|  | 			URL:                      "http://localhost:8080/@the_mighty_zork/statuses/01J2M1HPFSS54S60Y0KYV23KJE", | ||||||
|  | 			Content:                  "<p>Thanks! Here's a NIN track</p>", | ||||||
|  | 			Text:                     "Thanks! Here's a NIN track", | ||||||
|  | 			AttachmentIDs:            []string{"01J2M20K6K9XQC4WSB961YJHV6"}, | ||||||
|  | 			CreatedAt:                TimeMustParse("2024-01-10T11:24:00+02:00"), | ||||||
|  | 			UpdatedAt:                TimeMustParse("2024-01-10T11:24:00+02:00"), | ||||||
|  | 			Local:                    util.Ptr(true), | ||||||
|  | 			AccountURI:               "http://localhost:8080/users/the_mighty_zork", | ||||||
|  | 			AccountID:                "01F8MH1H7YV1Z7D2C8K2730QBF", | ||||||
|  | 			InReplyToID:              "01FF25D5Q0DH7CHD57CTRS6WK0", | ||||||
|  | 			InReplyToAccountID:       "01F8MH17FWEB39HZJ76B6VXSKF", | ||||||
|  | 			InReplyToURI:             "http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0", | ||||||
|  | 			BoostOfID:                "", | ||||||
|  | 			ThreadID:                 "01HCWDKKBWECZJQ93E262N36VN", | ||||||
|  | 			ContentWarning:           "", | ||||||
|  | 			Visibility:               gtsmodel.VisibilityPublic, | ||||||
|  | 			Sensitive:                util.Ptr(false), | ||||||
|  | 			Language:                 "en", | ||||||
|  | 			CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG", | ||||||
|  | 			Federated:                util.Ptr(true), | ||||||
|  | 			ActivityStreamsType:      ap.ObjectNote, | ||||||
|  | 		}, | ||||||
| 		"local_account_2_status_1": { | 		"local_account_2_status_1": { | ||||||
| 			ID:                       "01F8MHBQCBTDKN6X5VHGMMN4MA", | 			ID:                       "01F8MHBQCBTDKN6X5VHGMMN4MA", | ||||||
| 			URI:                      "http://localhost:8080/users/1happyturtle/statuses/01F8MHBQCBTDKN6X5VHGMMN4MA", | 			URI:                      "http://localhost:8080/users/1happyturtle/statuses/01F8MHBQCBTDKN6X5VHGMMN4MA", | ||||||
|  | @ -2208,6 +2284,10 @@ func NewTestThreadToStatus() []*gtsmodel.ThreadToStatus { | ||||||
| 			ThreadID: "01HCWDKKBWECZJQ93E262N36VN", | 			ThreadID: "01HCWDKKBWECZJQ93E262N36VN", | ||||||
| 			StatusID: "01FCQSQ667XHJ9AV9T27SJJSX5", | 			StatusID: "01FCQSQ667XHJ9AV9T27SJJSX5", | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			ThreadID: "01HCWDKKBWECZJQ93E262N36VN", | ||||||
|  | 			StatusID: "01J2M1HPFSS54S60Y0KYV23KJE", | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			ThreadID: "01HCWE71MGRRDSHBKXFD5DDSWR", | 			ThreadID: "01HCWE71MGRRDSHBKXFD5DDSWR", | ||||||
| 			StatusID: "01FN3VJGFH10KR7S2PB0GFJZYG", | 			StatusID: "01FN3VJGFH10KR7S2PB0GFJZYG", | ||||||
|  |  | ||||||
|  | @ -336,6 +336,10 @@ main { | ||||||
| 						grid-area: sensitive; | 						grid-area: sensitive; | ||||||
| 						align-self: center; | 						align-self: center; | ||||||
| 
 | 
 | ||||||
|  | 						text-overflow: ellipsis; | ||||||
|  | 						overflow: hidden; | ||||||
|  | 						white-space: nowrap; | ||||||
|  | 
 | ||||||
| 						.button { | 						.button { | ||||||
| 							cursor: pointer; | 							cursor: pointer; | ||||||
| 							align-self: center; | 							align-self: center; | ||||||
|  | @ -401,10 +405,18 @@ main { | ||||||
| 			grid-column: span 2; | 			grid-column: span 2; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		&.odd .media-wrapper:first-child, &.double .media-wrapper { | 		&.odd .media-wrapper:first-child, | ||||||
|  | 		&.double .media-wrapper { | ||||||
| 			grid-row: span 2; | 			grid-row: span 2; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		@media screen and (max-width: 42rem) { | ||||||
|  | 			.media-wrapper { | ||||||
|  | 				grid-column: span 2; | ||||||
|  | 				grid-row: span 2; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		img { | 		img { | ||||||
| 			width: 100%; | 			width: 100%; | ||||||
| 			height: 100%; | 			height: 100%; | ||||||
|  |  | ||||||
|  | @ -36,16 +36,42 @@ | ||||||
| {{- end }} | {{- end }} | ||||||
| 
 | 
 | ||||||
| {{- define "videoPreview" }} | {{- define "videoPreview" }} | ||||||
| <video | <img | ||||||
|  |     src="{{- .PreviewURL -}}" | ||||||
|  |     loading="lazy" | ||||||
|     {{- if .Description }} |     {{- if .Description }} | ||||||
|     alt="{{- .Description -}}" |     alt="{{- .Description -}}" | ||||||
|     title="{{- .Description -}}" |     title="{{- .Description -}}" | ||||||
|     {{- end }} |     {{- end }} | ||||||
|     width="{{- .Meta.Original.Width -}}" |     width="{{- .Meta.Small.Width -}}" | ||||||
|     height="{{- .Meta.Original.Height -}}" |     height="{{- .Meta.Small.Height -}}" | ||||||
| > | /> | ||||||
|     <source type="video/mp4" src="{{- .URL -}}"/> | {{- end }} | ||||||
| </video> | 
 | ||||||
|  | {{- define "audioPreview" }} | ||||||
|  | {{- if and .PreviewURL .Meta.Small.Width }} | ||||||
|  | <img | ||||||
|  |     src="{{- .PreviewURL -}}" | ||||||
|  |     loading="lazy" | ||||||
|  |     {{- if .Description }} | ||||||
|  |     alt="{{- .Description -}}" | ||||||
|  |     title="{{- .Description -}}" | ||||||
|  |     {{- end }} | ||||||
|  |     width="{{- .Meta.Small.Width -}}" | ||||||
|  |     height="{{- .Meta.Small.Height -}}" | ||||||
|  | /> | ||||||
|  | {{- else }} | ||||||
|  | <img | ||||||
|  |     src="/assets/logo.png" | ||||||
|  |     loading="lazy" | ||||||
|  |     {{- if .Description }} | ||||||
|  |     alt="{{- .Description -}}" | ||||||
|  |     title="{{- .Description -}}" | ||||||
|  |     {{- end }} | ||||||
|  |     width="518" | ||||||
|  |     height="460" | ||||||
|  | /> | ||||||
|  | {{- end }} | ||||||
| {{- end }} | {{- end }} | ||||||
| 
 | 
 | ||||||
| {{- /* Produces something like "1 attachment", "2 attachments", etc */ -}} | {{- /* Produces something like "1 attachment", "2 attachments", etc */ -}} | ||||||
|  | @ -77,21 +103,47 @@ media photoswipe-gallery {{ (len .) | oddOrEven }} {{ if eq (len .) 1 }}single{{ | ||||||
|                 {{- include "videoPreview" $media | indent 4 }} |                 {{- include "videoPreview" $media | indent 4 }} | ||||||
|                 {{- else if eq .Type "image" }} |                 {{- else if eq .Type "image" }} | ||||||
|                 {{- include "imagePreview" $media | indent 4 }} |                 {{- include "imagePreview" $media | indent 4 }} | ||||||
|  |                 {{- else if eq .Type "audio" }} | ||||||
|  |                 {{- include "audioPreview" $media | indent 4 }} | ||||||
|                 {{- end }} |                 {{- end }} | ||||||
|             </summary> |             </summary> | ||||||
|             {{- if eq .Type "video" }} |             {{- if eq .Type "video" }} | ||||||
|             <video |             <video | ||||||
|  |                 preload="none" | ||||||
|                 class="plyr-video photoswipe-slide" |                 class="plyr-video photoswipe-slide" | ||||||
|                 controls |                 controls | ||||||
|                 data-pswp-index="{{- $index -}}" |                 data-pswp-index="{{- $index -}}" | ||||||
|                 data-pswp-width="{{- $media.Meta.Original.Width -}}px" |                 poster="{{- .PreviewURL -}}" | ||||||
|                 data-pswp-height="{{- $media.Meta.Original.Height -}}px" |                 data-pswp-width="{{- $media.Meta.Small.Width -}}px" | ||||||
|  |                 data-pswp-height="{{- $media.Meta.Small.Height -}}px" | ||||||
|                 {{- if .Description }} |                 {{- if .Description }} | ||||||
|                 alt="{{- $media.Description -}}" |                 alt="{{- $media.Description -}}" | ||||||
|                 title="{{- $media.Description -}}" |                 title="{{- $media.Description -}}" | ||||||
|                 {{- end }} |                 {{- end }} | ||||||
|             > |             > | ||||||
|                 <source type="video/mp4" src="{{- $media.URL -}}"/> |                 <source type="{{- $media.MIMEType -}}" src="{{- $media.URL -}}"/> | ||||||
|  |             </video> | ||||||
|  |             {{- else if eq .Type "audio" }} | ||||||
|  |             <video | ||||||
|  |                 preload="none" | ||||||
|  |                 class="plyr-video photoswipe-slide" | ||||||
|  |                 controls | ||||||
|  |                 data-pswp-index="{{- $index -}}" | ||||||
|  |                 {{- if and $media.PreviewURL $media.Meta.Small.Width }} | ||||||
|  |                 poster="{{- .PreviewURL -}}" | ||||||
|  |                 data-pswp-width="{{- $media.Meta.Small.Width -}}px" | ||||||
|  |                 data-pswp-height="{{- $media.Meta.Small.Height -}}px" | ||||||
|  |                 {{- else }} | ||||||
|  |                 poster="/assets/logo.png" | ||||||
|  |                 width="518px" | ||||||
|  |                 height="460px" | ||||||
|  |                 {{- end }} | ||||||
|  |                 {{- if .Description }} | ||||||
|  |                 alt="{{- $media.Description -}}" | ||||||
|  |                 title="{{- $media.Description -}}" | ||||||
|  |                 {{- end }} | ||||||
|  |             > | ||||||
|  |                 <source type="{{- $media.MIMEType -}}" src="{{- $media.URL -}}"/> | ||||||
|             </video> |             </video> | ||||||
|             {{- else if eq .Type "image" }} |             {{- else if eq .Type "image" }} | ||||||
|             <a |             <a | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue