mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-22 11:46:40 +00:00
Compare commits
8 commits
095e745df4
...
b871989fb0
Author | SHA1 | Date | |
---|---|---|---|
b871989fb0 | |||
3f7dc10449 | |||
c17abea921 | |||
40c33ccc49 | |||
90b773ae2a | |||
4b7d7f9b8b | |||
af5a766f62 | |||
d9e59820ed |
|
@ -284,6 +284,12 @@ definitions:
|
|||
example: https://example.org/media/some_user/header/static/header.png
|
||||
type: string
|
||||
x-go-name: HeaderStatic
|
||||
hide_boosts:
|
||||
description: |-
|
||||
Account has opted to hide boosts from their profile.
|
||||
Key/value omitted if false.
|
||||
type: boolean
|
||||
x-go-name: HideBoosts
|
||||
hide_collections:
|
||||
description: |-
|
||||
Account has opted to hide their followers/following collections.
|
||||
|
@ -2284,6 +2290,12 @@ definitions:
|
|||
example: https://example.org/media/some_user/header/static/header.png
|
||||
type: string
|
||||
x-go-name: HeaderStatic
|
||||
hide_boosts:
|
||||
description: |-
|
||||
Account has opted to hide boosts from their profile.
|
||||
Key/value omitted if false.
|
||||
type: boolean
|
||||
x-go-name: HideBoosts
|
||||
hide_collections:
|
||||
description: |-
|
||||
Account has opted to hide their followers/following collections.
|
||||
|
|
|
@ -8,6 +8,18 @@ In order to make GoToSocial email sending work, you need an smtp-compatible mail
|
|||
|
||||
To validate your configuration, you can use the "Administration -> Actions -> Email" section of the settings panel to send a test email.
|
||||
|
||||
!!! warning
|
||||
Pending an smtp library update, currently only email providers that work with STARTTLS will work with GoToSocial. STARTTLS is generally available over **port 587**.
|
||||
|
||||
For more info, see:
|
||||
|
||||
- [STARTTLS vs SSL vs TLS](https://mailtrap.io/blog/starttls-ssl-tls/)
|
||||
- [Understanding Ports](https://www.mailgun.com/blog/email/which-smtp-port-understanding-ports-25-465-587/)
|
||||
- [Port 587](https://www.mailgun.com/blog/deliverability/smtp-port-587/)
|
||||
|
||||
!!! info
|
||||
For safety reasons, the smtp library used by GoToSocial will refuse to send authentication credentials over an unencrypted connection, unless the mail provider is running on localhost.
|
||||
|
||||
## Settings
|
||||
|
||||
The configuration options for smtp are as follows:
|
||||
|
@ -26,6 +38,7 @@ The configuration options for smtp are as follows:
|
|||
smtp-host: ""
|
||||
|
||||
# Int. Port to use to connect to the smtp server.
|
||||
# In the majority of cases, you should use port 587.
|
||||
# Examples: []
|
||||
# Default: 0
|
||||
smtp-port: 0
|
||||
|
@ -63,27 +76,16 @@ smtp-disclose-recipients: false
|
|||
|
||||
Note that if you don't set `Host`, then email sending via smtp will be disabled, and the other settings will be ignored. GoToSocial will still log (at trace level) emails that *would* have been sent if smtp was enabled.
|
||||
|
||||
## Behavior
|
||||
|
||||
### SSL
|
||||
|
||||
GoToSocial requires your smtp server to present valid SSL certificates. Most of the big services like Mailgun do this anyway, but if you're running your own mail server without SSL for some reason, and you're trying to connect GoToSocial to it, it will not work.
|
||||
|
||||
The exception to this requirement is if you're running your mail server (or bridge to a mail server) on `localhost`, in which case SSL certs are not required.
|
||||
|
||||
### When are emails sent?
|
||||
## When are emails sent?
|
||||
|
||||
Currently, emails are sent:
|
||||
|
||||
- To the provided email address of a new user to request email confirmation when a new account is created via the API.
|
||||
- To the provided email address of a new user to request email confirmation when a new account is created via the sign up page or API.
|
||||
- To instance admins when a new account is created in this way.
|
||||
- To all active instance moderators + admins when a new moderation report is received. By default, recipients are Bcc'd, but you can change this behavior with the setting `smtp-disclose-recipients`.
|
||||
- To the creator of a report (on this instance) when the report is closed by a moderator.
|
||||
|
||||
### Can I test if my SMTP configuration is correct?
|
||||
|
||||
Yes, you can use the API to send a test email to yourself. Check the API documentation for the `/api/v1/admin/email/test` endpoint.
|
||||
|
||||
### HTML versus Plaintext
|
||||
## HTML versus Plaintext
|
||||
|
||||
Emails are sent in plaintext by default. At this point, there is no option to send emails in html, but this is something that might be added later if there's enough demand for it.
|
||||
|
||||
|
|
|
@ -88,9 +88,9 @@ This setting does not affect visibility of your posts over the ActivityPub proto
|
|||
|
||||
!!! warning
|
||||
Be aware that changes to this setting also apply retroactively.
|
||||
|
||||
|
||||
That is, if you previously made a post on Unlisted visibility, while set to show only Public posts on your profile, and you change this setting to show Public and Unlisted, then the Unlisted post you previously made will be visible on your profile alongside your Public posts.
|
||||
|
||||
|
||||
Likewise, if you change this setting to show no posts, then all your posts will be hidden from your profile, regardless of when you created them, and what this option was set to at the time. This will apply until you change this setting again.
|
||||
|
||||
!!! tip
|
||||
|
@ -134,6 +134,11 @@ This feed only includes posts set as 'Public' (see [Privacy Settings](./posts.md
|
|||
!!! warning
|
||||
Exposing your RSS feed allows *anyone* to subscribe to updates on your Public posts anonymously, bypassing follows and follow requests.
|
||||
|
||||
#### Hide boosts from your public page
|
||||
|
||||
By default, GoToSocial will display posts boosted by you on your public web profile. If you do not wish to display them, You can hide them by checking this box.
|
||||
|
||||
|
||||
#### Hide Who You Follow / Are Followed By
|
||||
|
||||
By default, GoToSocial shows your following/followers counts on your public web profile, and allows others to see who you follow and are followed by. This can be useful for account discovery purposes. However, for privacy + safety reasons you may wish to hide these counts, and to hide your following/followers lists from other accounts. You can do this by checking this box.
|
||||
|
@ -196,7 +201,7 @@ If you want to reset all your policies to the initial defaults, you can click on
|
|||
|
||||
!!! danger
|
||||
While GoToSocial respects interaction policies, it is not guaranteed that other server softwares will, and it is possible that accounts on other servers will still send out replies and boosts of your post to their followers, even if your instance forbids these interactions.
|
||||
|
||||
|
||||
As more ActivityPub servers roll out support for interaction policies, this issue will hopefully diminish, but in the meantime GoToSocial can offer only a "best effort" attempt to restrict interactions with your posts according to the policies you have set.
|
||||
|
||||
## Email & Password
|
||||
|
|
|
@ -817,6 +817,7 @@ oidc-admin-groups: []
|
|||
smtp-host: ""
|
||||
|
||||
# Int. Port to use to connect to the smtp server.
|
||||
# In the majority of cases, you should use port 587.
|
||||
# Examples: []
|
||||
# Default: 0
|
||||
smtp-port: 0
|
||||
|
|
2
go.mod
2
go.mod
|
@ -22,7 +22,7 @@ require (
|
|||
codeberg.org/gruf/go-runners v1.6.3
|
||||
codeberg.org/gruf/go-sched v1.2.4
|
||||
codeberg.org/gruf/go-storage v0.2.0
|
||||
codeberg.org/gruf/go-structr v0.8.10
|
||||
codeberg.org/gruf/go-structr v0.8.11
|
||||
codeberg.org/superseriousbusiness/exif-terminator v0.9.0
|
||||
github.com/DmitriyVTitov/size v1.5.0
|
||||
github.com/KimMachineGun/automemlimit v0.6.1
|
||||
|
|
4
go.sum
4
go.sum
|
@ -72,8 +72,8 @@ codeberg.org/gruf/go-sched v1.2.4 h1:ddBB9o0D/2oU8NbQ0ldN5aWxogpXPRBATWi58+p++Hw
|
|||
codeberg.org/gruf/go-sched v1.2.4/go.mod h1:wad6l+OcYGWMA2TzNLMmLObsrbBDxdJfEy5WvTgBjNk=
|
||||
codeberg.org/gruf/go-storage v0.2.0 h1:mKj3Lx6AavEkuXXtxqPhdq+akW9YwrnP16yQBF7K5ZI=
|
||||
codeberg.org/gruf/go-storage v0.2.0/go.mod h1:o3GzMDE5QNUaRnm/daUzFqvuAaC4utlgXDXYO79sWKU=
|
||||
codeberg.org/gruf/go-structr v0.8.10 h1:uSapW97/StRnYEhCtycaM0isCsEMYC+tx/knYr6SiVo=
|
||||
codeberg.org/gruf/go-structr v0.8.10/go.mod h1:zkoXVrAnKosh8VFAsbP/Hhs8FmLBjbVVy5w/Ngm8ApM=
|
||||
codeberg.org/gruf/go-structr v0.8.11 h1:I3cQCHpK3fQSXWaaUfksAJRN4+efULiuF11Oi/m8c+o=
|
||||
codeberg.org/gruf/go-structr v0.8.11/go.mod h1:zkoXVrAnKosh8VFAsbP/Hhs8FmLBjbVVy5w/Ngm8ApM=
|
||||
codeberg.org/superseriousbusiness/exif-terminator v0.9.0 h1:/EfyGI6HIrbkhFwgXGSjZ9o1kr/+k8v4mKdfXTH02Go=
|
||||
codeberg.org/superseriousbusiness/exif-terminator v0.9.0/go.mod h1:gCWKduudUWFzsnixoMzu0FYVdxHWG+AbXnZ50DqxsUE=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
|
|
|
@ -348,6 +348,7 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest,
|
|||
form.Theme == nil &&
|
||||
form.CustomCSS == nil &&
|
||||
form.EnableRSS == nil &&
|
||||
form.HideBoosts == nil &&
|
||||
form.HideCollections == nil &&
|
||||
form.WebVisibility == nil) {
|
||||
return nil, errors.New("empty form submitted")
|
||||
|
|
|
@ -104,6 +104,9 @@ type Account struct {
|
|||
// Account has enabled RSS feed.
|
||||
// Key/value omitted if false.
|
||||
EnableRSS bool `json:"enable_rss,omitempty"`
|
||||
// Account has opted to hide boosts from their profile.
|
||||
// Key/value omitted if false.
|
||||
HideBoosts bool `json:"hide_boosts,omitempty"`
|
||||
// Account has opted to hide their followers/following collections.
|
||||
// Key/value omitted if false.
|
||||
HideCollections bool `json:"hide_collections,omitempty"`
|
||||
|
@ -225,6 +228,8 @@ type UpdateCredentialsRequest struct {
|
|||
CustomCSS *string `form:"custom_css" json:"custom_css"`
|
||||
// Enable RSS feed of public toots for this account at /@[username]/feed.rss
|
||||
EnableRSS *bool `form:"enable_rss" json:"enable_rss"`
|
||||
// Hide boosts from this account's profile page.
|
||||
HideBoosts *bool `form:"hide_boosts" json:"hide_boosts"`
|
||||
// Hide this account's following/followers collections.
|
||||
HideCollections *bool `form:"hide_collections" json:"hide_collections"`
|
||||
// Visibility of statuses to show via the web view.
|
||||
|
|
|
@ -118,6 +118,10 @@ type WebStatus struct {
|
|||
// Override API account with web account.
|
||||
Account *WebAccount `json:"account"`
|
||||
|
||||
// Account that reblogged the status.
|
||||
// needed to properly render reblogged statuses on profile pages.
|
||||
ReblogAccount *WebAccount `json:"reblog_account"`
|
||||
|
||||
// Web version of media
|
||||
// attached to this status.
|
||||
MediaAttachments []*WebAttachment `json:"media_attachments"`
|
||||
|
|
|
@ -1017,6 +1017,7 @@ func (a *accountDB) GetAccountWebStatuses(
|
|||
) ([]*gtsmodel.Status, error) {
|
||||
// Check for an easy case: account exposes no statuses via the web.
|
||||
webVisibility := account.Settings.WebVisibility
|
||||
hideBoosts := *account.Settings.HideBoosts
|
||||
if webVisibility == gtsmodel.VisibilityNone {
|
||||
return nil, db.ErrNoEntries
|
||||
}
|
||||
|
@ -1035,9 +1036,12 @@ func (a *accountDB) GetAccountWebStatuses(
|
|||
// Select only IDs from table
|
||||
Column("status.id").
|
||||
Where("? = ?", bun.Ident("status.account_id"), account.ID).
|
||||
// Don't show replies or boosts.
|
||||
Where("? IS NULL", bun.Ident("status.in_reply_to_uri")).
|
||||
Where("? IS NULL", bun.Ident("status.boost_of_id"))
|
||||
// Don't show replies.
|
||||
Where("? IS NULL", bun.Ident("status.in_reply_to_uri"))
|
||||
|
||||
if hideBoosts {
|
||||
q = q.Where("? IS NULL", bun.Ident("status.boost_of_id"))
|
||||
}
|
||||
|
||||
// Select statuses for this account according
|
||||
// to their web visibility preference.
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
_, err := db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? BOOLEAN DEFAULT FALSE", bun.Ident("account_settings"), bun.Ident("hide_boosts"))
|
||||
if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
_, err := db.ExecContext(ctx, "ALTER TABLE ? DROP COLUMN ?", bun.Ident("account_settings"), bun.Ident("hide_boosts"))
|
||||
return err
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ type AccountSettings struct {
|
|||
Theme string `bun:",nullzero"` // Preset CSS theme filename selected by this Account (empty string if nothing set).
|
||||
CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
|
||||
EnableRSS *bool `bun:",nullzero,notnull,default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed
|
||||
HideBoosts *bool `bun:",nullzero,notnull,default:false"` // Hide boosts from this accounts profile page.
|
||||
HideCollections *bool `bun:",nullzero,notnull,default:false"` // Hide this account's followers/following collections.
|
||||
WebVisibility Visibility `bun:",nullzero,notnull,default:public"` // Visibility level of statuses that visitors can view via the web profile.
|
||||
InteractionPolicyDirect *InteractionPolicy `bun:""` // Interaction policy to use for new direct visibility statuses by this account. If null, assume default policy.
|
||||
|
|
|
@ -42,6 +42,16 @@ func (suite *GetRSSTestSuite) TestGetAccountRSSAdmin() {
|
|||
<description>Posts from @admin@localhost:8080</description>
|
||||
<pubDate>Wed, 20 Oct 2021 10:41:37 +0000</pubDate>
|
||||
<lastBuildDate>Wed, 20 Oct 2021 10:41:37 +0000</lastBuildDate>
|
||||
<item>
|
||||
<title>introduction post</title>
|
||||
<link>http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</link>
|
||||
<description>@the_mighty_zork@localhost:8080 made a new post: "hello everyone!"</description>
|
||||
<content:encoded><![CDATA[hello everyone!]]></content:encoded>
|
||||
<author>@the_mighty_zork@localhost:8080</author>
|
||||
<guid isPermaLink="true">http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</guid>
|
||||
<pubDate>Wed, 20 Oct 2021 10:40:37 +0000</pubDate>
|
||||
<source>http://localhost:8080/@the_mighty_zork/feed.rss</source>
|
||||
</item>
|
||||
<item>
|
||||
<title>open to see some puppies</title>
|
||||
<link>http://localhost:8080/@admin/statuses/01F8MHAAY43M6RJ473VQFCVH37</link>
|
||||
|
|
|
@ -274,6 +274,11 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
|||
settingsColumns = append(settingsColumns, "enable_rss")
|
||||
}
|
||||
|
||||
if form.HideBoosts != nil {
|
||||
account.Settings.HideBoosts = form.HideBoosts
|
||||
settingsColumns = append(settingsColumns, "hide_boosts")
|
||||
}
|
||||
|
||||
if form.HideCollections != nil {
|
||||
account.Settings.HideCollections = form.HideCollections
|
||||
settingsColumns = append(settingsColumns, "hide_collections")
|
||||
|
|
|
@ -302,7 +302,7 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
|||
// Bits that vary between remote + local accounts:
|
||||
// - Account (acct) string.
|
||||
// - Role.
|
||||
// - Settings things (enableRSS, theme, customCSS, hideCollections).
|
||||
// - Settings things (enableRSS, theme, customCSS, hideBoosts ,hideCollections).
|
||||
|
||||
var (
|
||||
acct string
|
||||
|
@ -310,6 +310,7 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
|||
enableRSS bool
|
||||
theme string
|
||||
customCSS string
|
||||
hideBoosts bool
|
||||
hideCollections bool
|
||||
)
|
||||
|
||||
|
@ -338,6 +339,7 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
|||
enableRSS = *a.Settings.EnableRSS
|
||||
theme = a.Settings.Theme
|
||||
customCSS = a.Settings.CustomCSS
|
||||
hideBoosts = *a.Settings.HideBoosts
|
||||
hideCollections = *a.Settings.HideCollections
|
||||
}
|
||||
|
||||
|
@ -380,6 +382,7 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
|
|||
Theme: theme,
|
||||
CustomCSS: customCSS,
|
||||
EnableRSS: enableRSS,
|
||||
HideBoosts: hideBoosts,
|
||||
HideCollections: hideCollections,
|
||||
Roles: roles,
|
||||
}
|
||||
|
@ -1092,7 +1095,15 @@ func (c *Converter) StatusToWebStatus(
|
|||
ctx context.Context,
|
||||
s *gtsmodel.Status,
|
||||
) (*apimodel.WebStatus, error) {
|
||||
apiStatus, err := c.statusToFrontend(ctx, s,
|
||||
|
||||
isBoost := s.BoostOf != nil
|
||||
status := s
|
||||
|
||||
if isBoost {
|
||||
status = s.BoostOf
|
||||
}
|
||||
|
||||
apiStatus, err := c.statusToFrontend(ctx, status,
|
||||
nil, // No authed requester.
|
||||
statusfilter.FilterContextNone, // No filters.
|
||||
nil, // No filters.
|
||||
|
@ -1103,7 +1114,7 @@ func (c *Converter) StatusToWebStatus(
|
|||
}
|
||||
|
||||
// Convert status author to web model.
|
||||
acct, err := c.AccountToWebAccount(ctx, s.Account)
|
||||
acct, err := c.AccountToWebAccount(ctx, status.Account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1113,6 +1124,14 @@ func (c *Converter) StatusToWebStatus(
|
|||
Account: acct,
|
||||
}
|
||||
|
||||
if isBoost {
|
||||
reblogAcct, err := c.AccountToWebAccount(ctx, s.Account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webStatus.ReblogAccount = reblogAcct
|
||||
}
|
||||
|
||||
// Whack a newline before and after each "pre" to make it easier to outdent it.
|
||||
webStatus.Content = strings.ReplaceAll(webStatus.Content, "<pre>", "\n<pre>")
|
||||
webStatus.Content = strings.ReplaceAll(webStatus.Content, "</pre>", "</pre>\n")
|
||||
|
|
|
@ -1402,6 +1402,7 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
|
|||
"emojis": [],
|
||||
"fields": []
|
||||
},
|
||||
"reblog_account": null,
|
||||
"media_attachments": [
|
||||
{
|
||||
"id": "01HE7Y3C432WRSNS10EZM86SA5",
|
||||
|
|
|
@ -39,6 +39,12 @@
|
|||
func (c *Converter) StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*feeds.Item, error) {
|
||||
// see https://cyber.harvard.edu/rss/rss.html
|
||||
|
||||
// If status is a boost,
|
||||
// display the boost instead.
|
||||
if s.BoostOf != nil {
|
||||
s = s.BoostOf
|
||||
}
|
||||
|
||||
// Title -- The title of the item.
|
||||
// example: Venice Film Festival Tries to Quit Sinking
|
||||
var title string
|
||||
|
|
|
@ -657,6 +657,7 @@ func NewTestAccountSettings() map[string]*gtsmodel.AccountSettings {
|
|||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
EnableRSS: util.Ptr(false),
|
||||
HideBoosts: util.Ptr(false),
|
||||
HideCollections: util.Ptr(false),
|
||||
WebVisibility: gtsmodel.VisibilityPublic,
|
||||
},
|
||||
|
@ -668,6 +669,7 @@ func NewTestAccountSettings() map[string]*gtsmodel.AccountSettings {
|
|||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
EnableRSS: util.Ptr(true),
|
||||
HideBoosts: util.Ptr(false),
|
||||
HideCollections: util.Ptr(false),
|
||||
WebVisibility: gtsmodel.VisibilityPublic,
|
||||
},
|
||||
|
@ -679,6 +681,7 @@ func NewTestAccountSettings() map[string]*gtsmodel.AccountSettings {
|
|||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
EnableRSS: util.Ptr(true),
|
||||
HideBoosts: util.Ptr(false),
|
||||
HideCollections: util.Ptr(false),
|
||||
WebVisibility: gtsmodel.VisibilityUnlocked,
|
||||
},
|
||||
|
@ -690,6 +693,7 @@ func NewTestAccountSettings() map[string]*gtsmodel.AccountSettings {
|
|||
Sensitive: util.Ptr(true),
|
||||
Language: "fr",
|
||||
EnableRSS: util.Ptr(false),
|
||||
HideBoosts: util.Ptr(false),
|
||||
HideCollections: util.Ptr(true),
|
||||
WebVisibility: gtsmodel.VisibilityPublic,
|
||||
},
|
||||
|
|
47
vendor/codeberg.org/gruf/go-structr/cache.go
generated
vendored
47
vendor/codeberg.org/gruf/go-structr/cache.go
generated
vendored
|
@ -119,9 +119,9 @@ func (c *Cache[T]) Init(config CacheConfig[T]) {
|
|||
|
||||
// Index selects index with given name from cache, else panics.
|
||||
func (c *Cache[T]) Index(name string) *Index {
|
||||
for i := range c.indices {
|
||||
if c.indices[i].name == name {
|
||||
return &c.indices[i]
|
||||
for i, idx := range c.indices {
|
||||
if idx.name == name {
|
||||
return &(c.indices[i])
|
||||
}
|
||||
}
|
||||
panic("unknown index: " + name)
|
||||
|
@ -337,13 +337,16 @@ func (c *Cache[T]) Load(index *Index, keys []Key, load func([]Key) ([]T, error))
|
|||
panic("not initialized")
|
||||
}
|
||||
|
||||
for i := 0; i < len(keys); {
|
||||
// Iterate keys and catch uncached.
|
||||
toLoad := make([]Key, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
|
||||
// Value length before
|
||||
// any below appends.
|
||||
before := len(values)
|
||||
|
||||
// Concatenate all *values* from cached items.
|
||||
index.get(keys[i].key, func(item *indexed_item) {
|
||||
index.get(key.key, func(item *indexed_item) {
|
||||
if value, ok := item.data.(T); ok {
|
||||
// Append value COPY.
|
||||
value = c.copy(value)
|
||||
|
@ -358,30 +361,22 @@ func (c *Cache[T]) Load(index *Index, keys []Key, load func([]Key) ([]T, error))
|
|||
|
||||
// Only if values changed did
|
||||
// we actually find anything.
|
||||
if len(values) != before {
|
||||
|
||||
// We found values at key,
|
||||
// drop key from the slice.
|
||||
copy(keys[i:], keys[i+1:])
|
||||
keys = keys[:len(keys)-1]
|
||||
continue
|
||||
if len(values) == before {
|
||||
toLoad = append(toLoad, key)
|
||||
}
|
||||
|
||||
// Iter
|
||||
i++
|
||||
}
|
||||
|
||||
// Done with
|
||||
// the lock.
|
||||
unlock()
|
||||
|
||||
if len(keys) == 0 {
|
||||
if len(toLoad) == 0 {
|
||||
// We loaded everything!
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// Load uncached values.
|
||||
uncached, err := load(keys)
|
||||
// Load uncached key values.
|
||||
uncached, err := load(toLoad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -515,8 +510,8 @@ func (c *Cache[T]) Trim(perc float64) {
|
|||
}
|
||||
|
||||
// Compact index data stores.
|
||||
for i := range c.indices {
|
||||
c.indices[i].data.Compact()
|
||||
for _, idx := range c.indices {
|
||||
(&idx).data.Compact()
|
||||
}
|
||||
|
||||
// Done with lock.
|
||||
|
@ -536,17 +531,17 @@ func (c *Cache[T]) Len() int {
|
|||
|
||||
// Debug returns debug stats about cache.
|
||||
func (c *Cache[T]) Debug() map[string]any {
|
||||
m := make(map[string]any)
|
||||
m := make(map[string]any, 2)
|
||||
c.mutex.Lock()
|
||||
m["lru"] = c.lru.len
|
||||
indices := make(map[string]any)
|
||||
indices := make(map[string]any, len(c.indices))
|
||||
m["indices"] = indices
|
||||
for i := range c.indices {
|
||||
for _, idx := range c.indices {
|
||||
var n uint64
|
||||
for _, l := range c.indices[i].data.m {
|
||||
for _, l := range idx.data.m {
|
||||
n += uint64(l.len)
|
||||
}
|
||||
indices[c.indices[i].name] = n
|
||||
indices[idx.name] = n
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
return m
|
||||
|
@ -588,7 +583,7 @@ func (c *Cache[T]) store_value(index *Index, key string, value T) {
|
|||
|
||||
for i := range c.indices {
|
||||
// Get current index ptr.
|
||||
idx := &(c.indices[i])
|
||||
idx := (&c.indices[i])
|
||||
if idx == index {
|
||||
|
||||
// Already stored under
|
||||
|
|
42
vendor/codeberg.org/gruf/go-structr/index.go
generated
vendored
42
vendor/codeberg.org/gruf/go-structr/index.go
generated
vendored
|
@ -197,8 +197,13 @@ func (i *Index) get(key string, hook func(*indexed_item)) {
|
|||
return
|
||||
}
|
||||
|
||||
// Iterate all entries in list.
|
||||
l.rangefn(func(elem *list_elem) {
|
||||
// Iterate the list.
|
||||
for elem := l.head; //
|
||||
elem != nil; //
|
||||
{
|
||||
// Get next before
|
||||
// any modification.
|
||||
next := elem.next
|
||||
|
||||
// Extract element entry + item.
|
||||
entry := (*index_entry)(elem.data)
|
||||
|
@ -206,18 +211,21 @@ func (i *Index) get(key string, hook func(*indexed_item)) {
|
|||
|
||||
// Pass to hook.
|
||||
hook(item)
|
||||
})
|
||||
|
||||
// Set next.
|
||||
elem = next
|
||||
}
|
||||
}
|
||||
|
||||
// key uses hasher to generate Key{} from given raw parts.
|
||||
func (i *Index) key(buf *byteutil.Buffer, parts []unsafe.Pointer) string {
|
||||
buf.B = buf.B[:0]
|
||||
if len(parts) != len(i.fields) {
|
||||
panicf("incorrect number key parts: want=%d received=%d",
|
||||
len(i.fields),
|
||||
len(parts),
|
||||
)
|
||||
}
|
||||
buf.B = buf.B[:0]
|
||||
if !allow_zero(i.flags) {
|
||||
for x, field := range i.fields {
|
||||
before := len(buf.B)
|
||||
|
@ -301,8 +309,13 @@ func (i *Index) delete(key string, hook func(*indexed_item)) {
|
|||
// Delete at hash.
|
||||
i.data.Delete(key)
|
||||
|
||||
// Iterate entries in list.
|
||||
l.rangefn(func(elem *list_elem) {
|
||||
// Iterate the list.
|
||||
for elem := l.head; //
|
||||
elem != nil; //
|
||||
{
|
||||
// Get next before
|
||||
// any modification.
|
||||
next := elem.next
|
||||
|
||||
// Remove elem.
|
||||
l.remove(elem)
|
||||
|
@ -319,7 +332,10 @@ func (i *Index) delete(key string, hook func(*indexed_item)) {
|
|||
|
||||
// Pass to hook.
|
||||
hook(item)
|
||||
})
|
||||
|
||||
// Set next.
|
||||
elem = next
|
||||
}
|
||||
|
||||
// Release list.
|
||||
free_list(l)
|
||||
|
@ -375,17 +391,21 @@ type index_entry struct {
|
|||
func new_index_entry() *index_entry {
|
||||
v := index_entry_pool.Get()
|
||||
if v == nil {
|
||||
v = new(index_entry)
|
||||
e := new(index_entry)
|
||||
e.elem.data = unsafe.Pointer(e)
|
||||
v = e
|
||||
}
|
||||
entry := v.(*index_entry)
|
||||
ptr := unsafe.Pointer(entry)
|
||||
entry.elem.data = ptr
|
||||
return entry
|
||||
}
|
||||
|
||||
// free_index_entry releases the index_entry.
|
||||
func free_index_entry(entry *index_entry) {
|
||||
entry.elem.data = nil
|
||||
if entry.elem.next != nil ||
|
||||
entry.elem.prev != nil {
|
||||
should_not_reach()
|
||||
return
|
||||
}
|
||||
entry.key = ""
|
||||
entry.index = nil
|
||||
entry.item = nil
|
||||
|
|
16
vendor/codeberg.org/gruf/go-structr/item.go
generated
vendored
16
vendor/codeberg.org/gruf/go-structr/item.go
generated
vendored
|
@ -24,18 +24,22 @@ type indexed_item struct {
|
|||
func new_indexed_item() *indexed_item {
|
||||
v := indexed_item_pool.Get()
|
||||
if v == nil {
|
||||
v = new(indexed_item)
|
||||
i := new(indexed_item)
|
||||
i.elem.data = unsafe.Pointer(i)
|
||||
v = i
|
||||
}
|
||||
item := v.(*indexed_item)
|
||||
ptr := unsafe.Pointer(item)
|
||||
item.elem.data = ptr
|
||||
return item
|
||||
}
|
||||
|
||||
// free_indexed_item releases the indexed_item.
|
||||
func free_indexed_item(item *indexed_item) {
|
||||
item.elem.data = nil
|
||||
item.indexed = item.indexed[:0]
|
||||
if len(item.indexed) > 0 ||
|
||||
item.elem.next != nil ||
|
||||
item.elem.prev != nil {
|
||||
should_not_reach()
|
||||
return
|
||||
}
|
||||
item.data = nil
|
||||
indexed_item_pool.Put(item)
|
||||
}
|
||||
|
@ -50,7 +54,7 @@ func (i *indexed_item) drop_index(entry *index_entry) {
|
|||
continue
|
||||
}
|
||||
|
||||
// Move all index entries down + reslice.
|
||||
// Reslice index entries minus 'x'.
|
||||
_ = copy(i.indexed[x:], i.indexed[x+1:])
|
||||
i.indexed[len(i.indexed)-1] = nil
|
||||
i.indexed = i.indexed[:len(i.indexed)-1]
|
||||
|
|
52
vendor/codeberg.org/gruf/go-structr/list.go
generated
vendored
52
vendor/codeberg.org/gruf/go-structr/list.go
generated
vendored
|
@ -40,9 +40,12 @@ func new_list() *list {
|
|||
|
||||
// free_list releases the list.
|
||||
func free_list(list *list) {
|
||||
list.head = nil
|
||||
list.tail = nil
|
||||
list.len = 0
|
||||
if list.head != nil ||
|
||||
list.tail != nil ||
|
||||
list.len != 0 {
|
||||
should_not_reach()
|
||||
return
|
||||
}
|
||||
list_pool.Put(list)
|
||||
}
|
||||
|
||||
|
@ -115,21 +118,28 @@ func (l *list) remove(elem *list_elem) {
|
|||
elem.prev = nil
|
||||
|
||||
switch {
|
||||
// elem is ONLY one in list.
|
||||
case next == nil && prev == nil:
|
||||
l.head = nil
|
||||
l.tail = nil
|
||||
case next == nil:
|
||||
if prev == nil {
|
||||
// next == nil && prev == nil
|
||||
//
|
||||
// elem is ONLY one in list.
|
||||
l.head = nil
|
||||
l.tail = nil
|
||||
} else {
|
||||
// next == nil && prev != nil
|
||||
//
|
||||
// elem is last in list.
|
||||
l.tail = prev
|
||||
prev.next = nil
|
||||
}
|
||||
|
||||
// elem is front in list.
|
||||
case next != nil && prev == nil:
|
||||
case prev == nil:
|
||||
// next != nil && prev == nil
|
||||
//
|
||||
// elem is front in list.
|
||||
l.head = next
|
||||
next.prev = nil
|
||||
|
||||
// elem is last in list.
|
||||
case prev != nil && next == nil:
|
||||
l.tail = prev
|
||||
prev.next = nil
|
||||
|
||||
// elem in middle of list.
|
||||
default:
|
||||
next.prev = prev
|
||||
|
@ -139,17 +149,3 @@ func (l *list) remove(elem *list_elem) {
|
|||
// Decr count
|
||||
l.len--
|
||||
}
|
||||
|
||||
// rangefn will range all elems in list, passing each to fn.
|
||||
func (l *list) rangefn(fn func(*list_elem)) {
|
||||
if fn == nil {
|
||||
panic("nil fn")
|
||||
}
|
||||
for e := l.head; //
|
||||
e != nil; //
|
||||
{
|
||||
n := e.next
|
||||
fn(e)
|
||||
e = n
|
||||
}
|
||||
}
|
||||
|
|
180
vendor/codeberg.org/gruf/go-structr/ordered_list.bak
generated
vendored
Normal file
180
vendor/codeberg.org/gruf/go-structr/ordered_list.bak
generated
vendored
Normal file
|
@ -0,0 +1,180 @@
|
|||
package structr
|
||||
|
||||
import "sync"
|
||||
|
||||
type Timeline[StructType any, PK comparable] struct {
|
||||
|
||||
// hook functions.
|
||||
pkey func(StructType) PK
|
||||
gte func(PK, PK) bool
|
||||
lte func(PK, PK) bool
|
||||
copy func(StructType) StructType
|
||||
|
||||
// main underlying
|
||||
// ordered item list.
|
||||
list list
|
||||
|
||||
// indices used in storing passed struct
|
||||
// types by user defined sets of fields.
|
||||
indices []Index
|
||||
|
||||
// protective mutex, guards:
|
||||
// - TODO
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) Init(config any) {
|
||||
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) Index(name string) *Index {
|
||||
for i := range t.indices {
|
||||
if t.indices[i].name == name {
|
||||
return &t.indices[i]
|
||||
}
|
||||
}
|
||||
panic("unknown index: " + name)
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) Insert(values ...T) {
|
||||
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) LoadTop(min, max PK, length int, load func(min, max PK, length int) ([]T, error)) ([]T, error) {
|
||||
// Allocate expected no. values.
|
||||
values := make([]T, 0, length)
|
||||
|
||||
// Acquire lock.
|
||||
t.mutex.Lock()
|
||||
|
||||
// Wrap unlock to only do once.
|
||||
unlock := once(t.mutex.Unlock)
|
||||
defer unlock()
|
||||
|
||||
// Check init'd.
|
||||
if t.copy == nil {
|
||||
panic("not initialized")
|
||||
}
|
||||
|
||||
// Iterate through linked list from top (i.e. head).
|
||||
for next := t.list.head; next != nil; next = next.next {
|
||||
|
||||
// Check if we've gathered
|
||||
// enough values from timeline.
|
||||
if len(values) >= length {
|
||||
return values, nil
|
||||
}
|
||||
|
||||
item := (*indexed_item)(next.data)
|
||||
value := item.data.(T)
|
||||
pkey := t.pkey(value)
|
||||
|
||||
// Check if below min.
|
||||
if t.lte(pkey, min) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update min.
|
||||
min = pkey
|
||||
|
||||
// Check if above max.
|
||||
if t.gte(pkey, max) {
|
||||
break
|
||||
}
|
||||
|
||||
// Append value copy.
|
||||
value = t.copy(value)
|
||||
values = append(values, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) LoadBottom(min, max PK, length int, load func(min, max PK, length int) ([]T, error)) ([]T, error) {
|
||||
// Allocate expected no. values.
|
||||
values := make([]T, 0, length)
|
||||
|
||||
// Acquire lock.
|
||||
t.mutex.Lock()
|
||||
|
||||
// Wrap unlock to only do once.
|
||||
unlock := once(t.mutex.Unlock)
|
||||
defer unlock()
|
||||
|
||||
// Check init'd.
|
||||
if t.copy == nil {
|
||||
panic("not initialized")
|
||||
}
|
||||
|
||||
// Iterate through linked list from bottom (i.e. tail).
|
||||
for next := t.list.tail; next != nil; next = next.prev {
|
||||
|
||||
// Check if we've gathered
|
||||
// enough values from timeline.
|
||||
if len(values) >= length {
|
||||
return values, nil
|
||||
}
|
||||
|
||||
item := (*indexed_item)(next.data)
|
||||
value := item.data.(T)
|
||||
pkey := t.pkey(value)
|
||||
|
||||
// Check if above max.
|
||||
if t.gte(pkey, max) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update max.
|
||||
max = pkey
|
||||
|
||||
// Check if below min.
|
||||
if t.lte(pkey, min) {
|
||||
break
|
||||
}
|
||||
|
||||
// Append value copy.
|
||||
value = t.copy(value)
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
// Done with
|
||||
// the lock.
|
||||
unlock()
|
||||
|
||||
// Attempt to load values up to given length.
|
||||
next, err := load(min, max, length-len(values))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Acquire lock.
|
||||
t.mutex.Lock()
|
||||
|
||||
// Store uncached values.
|
||||
for i := range next {
|
||||
t.store_value(
|
||||
nil, "",
|
||||
uncached[i],
|
||||
)
|
||||
}
|
||||
|
||||
// Done with lock.
|
||||
t.mutex.Unlock()
|
||||
|
||||
// Append uncached to return values.
|
||||
values = append(values, next...)
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) index(value T) *indexed_item {
|
||||
pk := t.pkey(value)
|
||||
|
||||
switch {
|
||||
case t.list.len == 0:
|
||||
|
||||
case pk < t.list.head.data:
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) delete(item *indexed_item) {
|
||||
|
||||
}
|
16
vendor/codeberg.org/gruf/go-structr/queue.go
generated
vendored
16
vendor/codeberg.org/gruf/go-structr/queue.go
generated
vendored
|
@ -68,9 +68,9 @@ func (q *Queue[T]) Init(config QueueConfig[T]) {
|
|||
|
||||
// Index selects index with given name from queue, else panics.
|
||||
func (q *Queue[T]) Index(name string) *Index {
|
||||
for i := range q.indices {
|
||||
if q.indices[i].name == name {
|
||||
return &q.indices[i]
|
||||
for i, idx := range q.indices {
|
||||
if idx.name == name {
|
||||
return &(q.indices[i])
|
||||
}
|
||||
}
|
||||
panic("unknown index: " + name)
|
||||
|
@ -207,17 +207,17 @@ func (q *Queue[T]) Len() int {
|
|||
|
||||
// Debug returns debug stats about queue.
|
||||
func (q *Queue[T]) Debug() map[string]any {
|
||||
m := make(map[string]any)
|
||||
m := make(map[string]any, 2)
|
||||
q.mutex.Lock()
|
||||
m["queue"] = q.queue.len
|
||||
indices := make(map[string]any)
|
||||
indices := make(map[string]any, len(q.indices))
|
||||
m["indices"] = indices
|
||||
for i := range q.indices {
|
||||
for _, idx := range q.indices {
|
||||
var n uint64
|
||||
for _, l := range q.indices[i].data.m {
|
||||
for _, l := range idx.data.m {
|
||||
n += uint64(l.len)
|
||||
}
|
||||
indices[q.indices[i].name] = n
|
||||
indices[idx.name] = n
|
||||
}
|
||||
q.mutex.Unlock()
|
||||
return m
|
||||
|
|
28
vendor/codeberg.org/gruf/go-structr/runtime.go
generated
vendored
28
vendor/codeberg.org/gruf/go-structr/runtime.go
generated
vendored
|
@ -2,7 +2,10 @@
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
@ -182,7 +185,32 @@ func deref(p unsafe.Pointer, n uint) unsafe.Pointer {
|
|||
return p
|
||||
}
|
||||
|
||||
// eface_data returns the data ptr from an empty interface.
|
||||
func eface_data(a any) unsafe.Pointer {
|
||||
type eface struct{ _, data unsafe.Pointer }
|
||||
return (*eface)(unsafe.Pointer(&a)).data
|
||||
}
|
||||
|
||||
// panicf provides a panic with string formatting.
|
||||
func panicf(format string, args ...any) {
|
||||
panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// should_not_reach can be called to indicated a
|
||||
// block of code should not be able to be reached,
|
||||
// else it prints callsite info with a BUG report.
|
||||
//
|
||||
//go:noinline
|
||||
func should_not_reach() {
|
||||
pcs := make([]uintptr, 1)
|
||||
_ = runtime.Callers(2, pcs)
|
||||
fn := runtime.FuncForPC(pcs[0])
|
||||
funcname := "go-structr" // by default use just our library name
|
||||
if fn != nil {
|
||||
funcname = fn.Name()
|
||||
if i := strings.LastIndexByte(funcname, '/'); i != -1 {
|
||||
funcname = funcname[i+1:]
|
||||
}
|
||||
}
|
||||
os.Stderr.WriteString("BUG: assertion failed in " + funcname + "\n")
|
||||
}
|
||||
|
|
8
vendor/codeberg.org/gruf/go-structr/util.go
generated
vendored
8
vendor/codeberg.org/gruf/go-structr/util.go
generated
vendored
|
@ -1,7 +1,5 @@
|
|||
package structr
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// once only executes 'fn' once.
|
||||
func once(fn func()) func() {
|
||||
var once int32
|
||||
|
@ -13,9 +11,3 @@ func once(fn func()) func() {
|
|||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
// eface_data returns the data ptr from an empty interface.
|
||||
func eface_data(a any) unsafe.Pointer {
|
||||
type eface struct{ _, data unsafe.Pointer }
|
||||
return (*eface)(unsafe.Pointer(&a)).data
|
||||
}
|
||||
|
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
|
@ -66,7 +66,7 @@ codeberg.org/gruf/go-storage/disk
|
|||
codeberg.org/gruf/go-storage/internal
|
||||
codeberg.org/gruf/go-storage/memory
|
||||
codeberg.org/gruf/go-storage/s3
|
||||
# codeberg.org/gruf/go-structr v0.8.10
|
||||
# codeberg.org/gruf/go-structr v0.8.11
|
||||
## explicit; go 1.21
|
||||
codeberg.org/gruf/go-structr
|
||||
# codeberg.org/superseriousbusiness/exif-terminator v0.9.0
|
||||
|
|
|
@ -41,6 +41,12 @@ main {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.boosted {
|
||||
padding: 0 0.75rem 0.75rem;
|
||||
color: var(--fg-reduced);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-header > address {
|
||||
/*
|
||||
Avoid stretching so wide that user
|
||||
|
@ -59,17 +65,27 @@ main {
|
|||
"avatar author-strap author-strap";
|
||||
gap: 0 0.5rem;
|
||||
font-style: normal;
|
||||
|
||||
|
||||
.avatar {
|
||||
grid-area: avatar;
|
||||
height: 3.5rem;
|
||||
width: 3.5rem;
|
||||
object-fit: cover;
|
||||
|
||||
position: relative;
|
||||
|
||||
border: 0.15rem solid $avatar-border;
|
||||
border-radius: $br;
|
||||
overflow: hidden; /* hides corners from img overflowing */
|
||||
|
||||
|
||||
.boosted-avatar {
|
||||
height: 50%;
|
||||
width: 50%;
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
@ -77,7 +93,7 @@ main {
|
|||
background: $bg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.author-strap {
|
||||
grid-area: author-strap;
|
||||
display: grid;
|
||||
|
@ -87,7 +103,7 @@ main {
|
|||
"display display"
|
||||
"user user";
|
||||
gap: 0 0.5rem;
|
||||
|
||||
|
||||
.displayname, .username {
|
||||
justify-self: start;
|
||||
align-self: start;
|
||||
|
@ -95,12 +111,12 @@ main {
|
|||
font-size: 1rem;
|
||||
line-height: 1.3rem;
|
||||
}
|
||||
|
||||
|
||||
.displayname {
|
||||
grid-area: display;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.username {
|
||||
grid-area: user;
|
||||
color: $link-fg;
|
||||
|
@ -200,34 +216,34 @@ main {
|
|||
.poll {
|
||||
background-color: $gray2;
|
||||
z-index: 2;
|
||||
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: $br;
|
||||
padding: 0.5rem;
|
||||
margin: 0;
|
||||
gap: 1rem;
|
||||
|
||||
|
||||
.poll-options {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
|
||||
.poll-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
|
||||
|
||||
label {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
|
||||
meter {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.poll-vote-summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
@ -236,7 +252,7 @@ main {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.poll-info {
|
||||
background-color: $gray4;
|
||||
display: flex;
|
||||
|
@ -245,7 +261,7 @@ main {
|
|||
border-radius: $br-inner;
|
||||
padding: 0.25rem;
|
||||
gap: 0.25rem;
|
||||
|
||||
|
||||
span {
|
||||
justify-self: center;
|
||||
white-space: nowrap;
|
||||
|
@ -301,12 +317,12 @@ main {
|
|||
width: 100%;
|
||||
z-index: 3;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
display: grid;
|
||||
padding: 1rem;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
grid-template-areas:
|
||||
grid-template-areas:
|
||||
"eye sensitive ."
|
||||
". sensitive .";
|
||||
|
||||
|
@ -369,7 +385,7 @@ main {
|
|||
height: 100%;
|
||||
padding: 0.8rem;
|
||||
border: 0.2rem dashed $white2;
|
||||
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
@ -518,4 +534,4 @@ main {
|
|||
.plyr {
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ function UserProfileForm({ data: profile }) {
|
|||
maxPinnedFields: instance?.configuration?.accounts?.max_profile_fields ?? 6
|
||||
};
|
||||
}, [instance]);
|
||||
|
||||
|
||||
// Parse out available theme options into nice format.
|
||||
const { data: themes } = useAccountThemesQuery();
|
||||
const themeOptions = useMemo(() => {
|
||||
|
@ -114,6 +114,7 @@ function UserProfileForm({ data: profile }) {
|
|||
locked: useBoolInput("locked", { source: profile }),
|
||||
discoverable: useBoolInput("discoverable", { source: profile}),
|
||||
enableRSS: useBoolInput("enable_rss", { source: profile }),
|
||||
hideBoosts: useBoolInput("hide_boosts", { source: profile }),
|
||||
hideCollections: useBoolInput("hide_collections", { source: profile }),
|
||||
webVisibility: useTextInput("web_visibility", { source: profile, valueSelector: (p) => p.source?.web_visibility }),
|
||||
fields: useFieldArrayInput("fields_attributes", {
|
||||
|
@ -158,7 +159,7 @@ function UserProfileForm({ data: profile }) {
|
|||
autoCapitalize="sentences"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="file-input-with-image-description">
|
||||
<FileInput
|
||||
label="Avatar (1:1 images look best)"
|
||||
|
@ -257,6 +258,10 @@ function UserProfileForm({ data: profile }) {
|
|||
field={form.enableRSS}
|
||||
label="Enable RSS feed of posts."
|
||||
/>
|
||||
<Checkbox
|
||||
field={form.hideBoosts}
|
||||
label="Hide boosts from your public page"
|
||||
/>
|
||||
<Checkbox
|
||||
field={form.hideCollections}
|
||||
label="Hide who you follow / are followed by."
|
||||
|
|
|
@ -247,6 +247,16 @@
|
|||
class="status expanded"
|
||||
{{- includeAttr "status_attributes.tmpl" . | indentAttr 6 }}
|
||||
>
|
||||
{{- if .ReblogAccount }}
|
||||
<div class="boosted text-cutoff">
|
||||
<i class="fa fa-retweet" aria-hidden="true"></i> 
|
||||
{{- if $.account.DisplayName }}
|
||||
{{- emojify $.account.Emojis (escape $.account.DisplayName) }} boosted
|
||||
{{- else }}
|
||||
{{- $.account.Username }} boosted
|
||||
{{- end }}
|
||||
</div>
|
||||
{{- end }}
|
||||
{{- include "status.tmpl" . | indent 6 }}
|
||||
</article>
|
||||
{{- end }}
|
||||
|
@ -264,4 +274,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
|
|
@ -48,6 +48,16 @@
|
|||
alt="Avatar for {{ .Username -}}"
|
||||
title="Avatar for {{ .Username -}}"
|
||||
>
|
||||
{{ if $.ReblogAccount }}
|
||||
<img
|
||||
class="boosted-avatar"
|
||||
src="{{ $.ReblogAccount.Avatar }}"
|
||||
alt="Avatar for {{ $.ReblogAccount.Username -}}"
|
||||
title="Avatar for {{ $.ReblogAccount.Username -}}"
|
||||
>
|
||||
{{ end }}
|
||||
|
||||
|
||||
</picture>
|
||||
<div class="author-strap">
|
||||
<span class="displayname text-cutoff">
|
||||
|
@ -63,4 +73,4 @@
|
|||
<span class="sr-only">(open profile)</span>
|
||||
</a>
|
||||
</address>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
|
Loading…
Reference in a new issue