diff --git a/internal/api/model/account.go b/internal/api/model/account.go
index cf39dd08e..c5b629db0 100644
--- a/internal/api/model/account.go
+++ b/internal/api/model/account.go
@@ -110,17 +110,27 @@ type Account struct {
// If set, indicates that this account is currently inactive, and has migrated to the given account.
// Key/value omitted for accounts that haven't moved, and for suspended accounts.
Moved *Account `json:"moved,omitempty"`
+}
- // Additional fields not exposed via JSON
- // (used only internally for templating etc).
+// WebAccount is like Account, but with
+// additional fields not exposed via JSON;
+// used only internally for templating etc.
+//
+// swagger:ignore
+type WebAccount struct {
+ *Account
// Proper attachment model for the avatar.
//
- // Only set if this model was converted via
- // AccountToWebAccount, AND this account had
- // an avatar set (and not just the default
- // "blank" avatar image.)
- AvatarAttachment *Attachment `json:"-"`
+ // Only set if this account had an avatar set
+ // (and not just the default "blank" image.)
+ AvatarAttachment *WebAttachment `json:"-"`
+
+ // Proper attachment model for the header.
+ //
+ // Only set if this account had a header set
+ // (and not just the default "blank" image.)
+ HeaderAttachment *WebAttachment `json:"-"`
}
// MutedAccount extends Account with a field used only by the muted user list.
diff --git a/internal/api/model/attachment.go b/internal/api/model/attachment.go
index d0b0c81e5..21523a58e 100644
--- a/internal/api/model/attachment.go
+++ b/internal/api/model/attachment.go
@@ -107,6 +107,10 @@ type WebAttachment struct {
// MIME type of
// the attachment.
MIMEType string
+
+ // MIME type of
+ // the thumbnail.
+ PreviewMIMEType string
}
// MediaMeta models media metadata.
diff --git a/internal/api/model/status.go b/internal/api/model/status.go
index 7358916ab..b3ac746d7 100644
--- a/internal/api/model/status.go
+++ b/internal/api/model/status.go
@@ -113,6 +113,9 @@ type Status struct {
type WebStatus struct {
*Status
+ // Override API account with web account.
+ Account *WebAccount `json:"account"`
+
// Web version of media
// attached to this status.
MediaAttachments []*WebAttachment `json:"media_attachments"`
diff --git a/internal/api/util/opengraph.go b/internal/api/util/opengraph.go
index 062151836..094c80021 100644
--- a/internal/api/util/opengraph.go
+++ b/internal/api/util/opengraph.go
@@ -84,7 +84,7 @@ func OGBase(instance *apimodel.InstanceV1) *OGMeta {
// WithAccount uses the given account to build an ogMeta
// struct specific to that account. It's suitable for serving
// at account profile pages.
-func (og *OGMeta) WithAccount(account *apimodel.Account) *OGMeta {
+func (og *OGMeta) WithAccount(account *apimodel.WebAccount) *OGMeta {
og.Title = AccountTitle(account, og.SiteName)
og.Type = "profile"
og.URL = account.URL
@@ -148,7 +148,7 @@ func (og *OGMeta) WithStatus(status *apimodel.WebStatus) *OGMeta {
}
// AccountTitle parses a page title from account and accountDomain
-func AccountTitle(account *apimodel.Account, accountDomain string) string {
+func AccountTitle(account *apimodel.WebAccount, accountDomain string) string {
user := "@" + account.Acct + "@" + accountDomain
if len(account.DisplayName) == 0 {
diff --git a/internal/api/util/opengraph_test.go b/internal/api/util/opengraph_test.go
index 2ecd6a740..4e94d78ef 100644
--- a/internal/api/util/opengraph_test.go
+++ b/internal/api/util/opengraph_test.go
@@ -51,13 +51,15 @@ func (suite *OpenGraphTestSuite) TestWithAccountWithNote() {
Languages: []string{"en"},
})
- accountMeta := baseMeta.WithAccount(&apimodel.Account{
+ acct := &apimodel.Account{
Acct: "example_account",
DisplayName: "example person!!",
URL: "https://example.org/@example_account",
Note: "
This is my profile, read it and weep! Weep then!
",
Username: "example_account",
- })
+ }
+
+ accountMeta := baseMeta.WithAccount(&apimodel.WebAccount{Account: acct})
suite.EqualValues(OGMeta{
Title: "example person!!, @example_account@example.org",
@@ -84,13 +86,15 @@ func (suite *OpenGraphTestSuite) TestWithAccountNoNote() {
Languages: []string{"en"},
})
- accountMeta := baseMeta.WithAccount(&apimodel.Account{
+ acct := &apimodel.Account{
Acct: "example_account",
DisplayName: "example person!!",
URL: "https://example.org/@example_account",
Note: "", // <- empty
Username: "example_account",
- })
+ }
+
+ accountMeta := baseMeta.WithAccount(&apimodel.WebAccount{Account: acct})
suite.EqualValues(OGMeta{
Title: "example person!!, @example_account@example.org",
diff --git a/internal/processing/account/get.go b/internal/processing/account/get.go
index 32d45054d..eac0f0c3f 100644
--- a/internal/processing/account/get.go
+++ b/internal/processing/account/get.go
@@ -98,7 +98,7 @@ func (p *Processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account
}
// GetWeb returns the web model of a local account by username.
-func (p *Processor) GetWeb(ctx context.Context, username string) (*apimodel.Account, gtserror.WithCode) {
+func (p *Processor) GetWeb(ctx context.Context, username string) (*apimodel.WebAccount, gtserror.WithCode) {
targetAccount, err := p.state.DB.GetAccountByUsernameDomain(ctx, username, "")
if err != nil {
if errors.Is(err, db.ErrNoEntries) {
diff --git a/internal/text/emojify.go b/internal/text/emojify.go
index 23730eaf9..00d121d3b 100644
--- a/internal/text/emojify.go
+++ b/internal/text/emojify.go
@@ -32,20 +32,41 @@ func EmojifyWeb(emojis []apimodel.Emoji, html template.HTML) template.HTML {
out := emojify(
emojis,
string(html),
- func(url, code string, buf *bytes.Buffer) {
- buf.WriteString(``)
+ func(url, staticURL, code string, buf *bytes.Buffer) {
+ // Open a picture tag so we
+ // can present multiple options.
+ buf.WriteString(``)
+
+ // Static version.
+ buf.WriteString(``)
+
+ // Original image source.
+ buf.WriteString(``)
+
+ // Close the picture tag.
+ buf.WriteString(``)
},
)
@@ -60,17 +81,18 @@ func EmojifyRSS(emojis []apimodel.Emoji, text string) string {
return emojify(
emojis,
text,
- func(url, code string, buf *bytes.Buffer) {
- buf.WriteString(``)
+ func(url, staticURL, code string, buf *bytes.Buffer) {
+ // Original image source.
+ buf.WriteString(``)
},
)
}
@@ -85,7 +107,7 @@ func Demojify(text string) string {
func emojify(
emojis []apimodel.Emoji,
input string,
- write func(url, code string, buf *bytes.Buffer),
+ write func(url, staticURL, code string, buf *bytes.Buffer),
) string {
// Build map of shortcodes. Normalize each
// shortcode by readding closing colons.
@@ -107,10 +129,11 @@ func(shortcode string, buf *bytes.Buffer) string {
// Escape raw emoji content.
url := html.EscapeString(emoji.URL)
+ staticURL := html.EscapeString(emoji.StaticURL)
code := html.EscapeString(emoji.Shortcode)
// Write emoji repr to buffer.
- write(url, code, buf)
+ write(url, staticURL, code, buf)
return buf.String()
},
)
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index f11c4af21..03e09654d 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -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 .
+ 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 .
+ 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,7 +772,15 @@ 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,
+ false, // This is not a web status.
+ )
if err != nil {
return nil, err
}
@@ -958,20 +991,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.
+ true, // Web status.
)
if err != nil {
return nil, err
}
+ webAccount, err := c.AccountToWebAccount(ctx, s.Account)
+ if err != nil {
+ return nil, err
+ }
+
webStatus := &apimodel.WebStatus{
- Status: apiStatus,
+ Status: apiStatus,
+ Account: webAccount,
}
// Whack a newline before and after each "pre" to make it easier to outdent it.
@@ -1062,9 +1100,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,
}
}
@@ -1097,6 +1136,7 @@ func (c *Converter) statusToFrontend(
filterContext statusfilter.FilterContext,
filters []*gtsmodel.Filter,
mutes *usermute.CompiledUserMuteList,
+ web bool,
) (
*apimodel.Status,
error,
@@ -1107,6 +1147,7 @@ func (c *Converter) statusToFrontend(
filterContext,
filters,
mutes,
+ web,
)
if err != nil {
return nil, err
@@ -1119,6 +1160,7 @@ func (c *Converter) statusToFrontend(
filterContext,
filters,
mutes,
+ web,
)
if errors.Is(err, statusfilter.ErrHideStatus) {
// If we'd hide the original status, hide the boost.
@@ -1149,6 +1191,7 @@ func (c *Converter) baseStatusToFrontend(
filterContext statusfilter.FilterContext,
filters []*gtsmodel.Filter,
mutes *usermute.CompiledUserMuteList,
+ web bool,
) (
*apimodel.Status,
error,
@@ -1169,9 +1212,21 @@ func (c *Converter) baseStatusToFrontend(
}
}
- apiAuthorAccount, err := c.AccountToAPIAccountPublic(ctx, s.Account)
+ var (
+ apiAuthorAccount *apimodel.Account
+ err error
+ )
+
+ // Only bother converting the author account if
+ // this is an API status and not a web status.
+ //
+ // If it's a web status, the web function will
+ // convert the account into a web account instead.
+ if !web {
+ 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)
diff --git a/internal/web/thread.go b/internal/web/thread.go
index de3d1b361..d3ba6ea5e 100644
--- a/internal/web/thread.go
+++ b/internal/web/thread.go
@@ -108,7 +108,7 @@ func (m *Module) threadGETHandler(c *gin.Context) {
}
// Ensure status actually belongs to target account.
- if context.Status.GetAccountID() != targetAccount.ID {
+ if context.Status.Account.ID != targetAccount.ID {
err := fmt.Errorf("target account %s does not own status %s", targetUsername, targetStatusID)
apiutil.WebErrorHandler(c, gtserror.NewErrorNotFound(err), instanceGet)
return
diff --git a/web/source/css/base.css b/web/source/css/base.css
index 522820f15..630e4a6d2 100644
--- a/web/source/css/base.css
+++ b/web/source/css/base.css
@@ -187,18 +187,20 @@ input, select, textarea, .input {
margin: -0.2em 0.02em 0;
object-fit: contain;
vertical-align: middle;
- transition: 0.1s;
-
- /*
- Enlarge emojis on hover to give
- viewer a good look at them.
- */
- &:hover, &:active {
- transform: scale(2);
- background-color: $bg;
- box-shadow: $boxshadow;
- border: $boxshadow-border;
- border-radius: $br-inner;
+
+ @media (prefers-reduced-motion: no-preference) {
+ /*
+ Enlarge emojis on hover to give
+ viewer a good look at them.
+ */
+ transition: 0.1s;
+ &:hover, &:active {
+ transform: scale(2);
+ background-color: $bg;
+ box-shadow: $boxshadow;
+ border: $boxshadow-border;
+ border-radius: $br-inner;
+ }
}
}
diff --git a/web/source/css/status.css b/web/source/css/status.css
index 5c7400654..21dc3416e 100644
--- a/web/source/css/status.css
+++ b/web/source/css/status.css
@@ -193,14 +193,6 @@ main {
font-size: 1rem;
line-height: initial;
}
-
- img {
- max-width: 100%;
- margin: 5px auto;
- }
- img[alt~="!center"] {
- display: block;
- }
}
.poll {
diff --git a/web/template/profile.tmpl b/web/template/profile.tmpl
index 256bbdccf..a06c842ab 100644
--- a/web/template/profile.tmpl
+++ b/web/template/profile.tmpl
@@ -94,14 +94,26 @@
alt="{{- template "avatarAlt" . -}}"
title="{{- template "avatarAlt" . -}}"
>
-
+
+ {{- if .account.AvatarAttachment }}
+
+ {{- end }}
+
+
{{- end }}
@@ -115,11 +127,20 @@
{{- include "profileMovedTo" . | indent 2 }}
{{- end }}
-
+
+ {{- if .account.HeaderAttachment }}
+
+ {{- end }}
+
+
{{- with . }}
diff --git a/web/template/status_header.tmpl b/web/template/status_header.tmpl
index 8946a1030..01b73aea0 100644
--- a/web/template/status_header.tmpl
+++ b/web/template/status_header.tmpl
@@ -32,13 +32,23 @@
title="Open remote profile (opens in a new window)"
>
{{- end }}
-
+ {{- if .AvatarAttachment }}
+
+ {{- end }}
+
+