gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 26683b3d49beea9b1f0e8f78df4720285d4c0825
parent a7e9dee33d038c55090b1e657fdf1008d3c4e227
Author: tobi <31960611+tsmethurst@users.noreply.github.com>
Date:   Fri, 15 Apr 2022 14:33:01 +0200

[feature] Web profile pages for accounts (#449)

* add default avatars

* allow webModule to error

* return errWithCode from account get

* add AccountGetLocalByUsername

* check nil requesting account

* add timestampShort function for just month/year

* move loading logic to New + add default avatars

* add profile page view

* update swagger docs

* add excludeReblogs to GetAccountStatuses

* ignore casing when selecting local account by username

* appropriate redirects

* css fiddling

* add 'about' heading

* adjust thread page to work with routing

* return AP representation if requested + authorized

* simplify auth check

* go fmt

* golangci-lint ignore math/rand
Diffstat:
Mcmd/gotosocial/action/server/server.go | 11+++++++++--
Mcmd/gotosocial/action/testrig/testrig.go | 11+++++++++--
Mdocs/api/swagger.yaml | 7++++++-
Minternal/api/client/account/account.go | 2++
Minternal/api/client/account/accountget.go | 6++++--
Minternal/api/client/account/statuses.go | 24+++++++++++++++++++++---
Minternal/api/s2s/user/inboxpost_test.go | 2+-
Minternal/db/account.go | 2+-
Minternal/db/bundb/account.go | 8++++++--
Minternal/processing/account.go | 10+++++++---
Minternal/processing/account/account.go | 6++++--
Minternal/processing/account/delete.go | 2+-
Minternal/processing/account/get.go | 34++++++++++++++++++++++++++--------
Minternal/processing/account/getstatuses.go | 14++++++++------
Minternal/processing/federation/getoutbox.go | 2+-
Minternal/processing/federation/getuser.go | 15++++++++-------
Minternal/processing/fromfederator_test.go | 2+-
Minternal/processing/processor.go | 6++++--
Minternal/router/template.go | 6++++++
Minternal/typeutils/internaltoas_test.go | 2+-
Minternal/web/base.go | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Ainternal/web/profile.go | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minternal/web/thread.go | 23++++++++++++-----------
Mweb/assets/base.css | 29+++++++++++++++--------------
Aweb/assets/default_avatars/GoToSocial_icon1.svg | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweb/assets/default_avatars/GoToSocial_icon2.svg | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweb/assets/default_avatars/GoToSocial_icon3.svg | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweb/assets/default_avatars/GoToSocial_icon4.svg | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweb/assets/default_avatars/GoToSocial_icon5.svg | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweb/assets/default_avatars/GoToSocial_icon6.svg | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweb/assets/profile.css | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweb/gotosocial-styling/templates/profile.css | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aweb/template/profile.tmpl | 48++++++++++++++++++++++++++++++++++++++++++++++++
33 files changed, 1484 insertions(+), 87 deletions(-)

diff --git a/cmd/gotosocial/action/server/server.go b/cmd/gotosocial/action/server/server.go @@ -148,6 +148,12 @@ var Start action.GTSAction = func(ctx context.Context) error { return fmt.Errorf("error creating oidc idp: %s", err) } + // build web module + webModule, err := web.New(processor) + if err != nil { + return fmt.Errorf("error creating web module: %s", err) + } + // build client api modules authModule := auth.New(dbService, oauthServer, idp) accountModule := account.New(processor) @@ -156,7 +162,6 @@ var Start action.GTSAction = func(ctx context.Context) error { followRequestsModule := followrequest.New(processor) webfingerModule := webfinger.New(processor) nodeInfoModule := nodeinfo.New(processor) - webBaseModule := web.New(processor) usersModule := user.New(processor) timelineModule := timeline.New(processor) notificationModule := notification.New(processor) @@ -179,8 +184,10 @@ var Start action.GTSAction = func(ctx context.Context) error { securityModule, authModule, + // now the web module + webModule, + // now everything else - webBaseModule, accountModule, instanceModule, appsModule, diff --git a/cmd/gotosocial/action/testrig/testrig.go b/cmd/gotosocial/action/testrig/testrig.go @@ -95,6 +95,12 @@ var Start action.GTSAction = func(ctx context.Context) error { return fmt.Errorf("error creating oidc idp: %s", err) } + // build web module + webModule, err := web.New(processor) + if err != nil { + return fmt.Errorf("error creating web module: %s", err) + } + // build client api modules authModule := auth.New(dbService, oauthServer, idp) accountModule := account.New(processor) @@ -103,7 +109,6 @@ var Start action.GTSAction = func(ctx context.Context) error { followRequestsModule := followrequest.New(processor) webfingerModule := webfinger.New(processor) nodeInfoModule := nodeinfo.New(processor) - webBaseModule := web.New(processor) usersModule := user.New(processor) timelineModule := timeline.New(processor) notificationModule := notification.New(processor) @@ -126,8 +131,10 @@ var Start action.GTSAction = func(ctx context.Context) error { securityModule, authModule, + // now the web module + webModule, + // now everything else - webBaseModule, accountModule, instanceModule, appsModule, diff --git a/docs/api/swagger.yaml b/docs/api/swagger.yaml @@ -2086,6 +2086,11 @@ paths: in: query name: exclude_replies type: boolean + - default: false + description: Exclude statuses that are a reblog/boost of another status. + in: query + name: exclude_reblogs + type: boolean - description: |- Return only statuses *OLDER* than the given max status ID. The status with the specified ID will not be included in the response. @@ -2099,7 +2104,7 @@ paths: name: min_id type: string - default: false - description: Show only pinned statuses. In other words,e xclude statuses that + description: Show only pinned statuses. In other words, exclude statuses that are not pinned to the given account ID. in: query name: pinned_only diff --git a/internal/api/client/account/account.go b/internal/api/client/account/account.go @@ -34,6 +34,8 @@ const ( LimitKey = "limit" // ExcludeRepliesKey is for specifying whether to exclude replies in a list of returned statuses by an account. ExcludeRepliesKey = "exclude_replies" + // ExcludeReblogsKey is for specifying whether to exclude reblogs in a list of returned statuses by an account. + ExcludeReblogsKey = "exclude_reblogs" // PinnedKey is for specifying whether to include pinned statuses in a list of returned statuses by an account. PinnedKey = "pinned" // MaxIDKey is for specifying the maximum ID of the status to retrieve. diff --git a/internal/api/client/account/accountget.go b/internal/api/client/account/accountget.go @@ -22,6 +22,7 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/oauth" ) @@ -76,9 +77,10 @@ func (m *Module) AccountGETHandler(c *gin.Context) { return } - acctInfo, err := m.processor.AccountGet(c.Request.Context(), authed, targetAcctID) + acctInfo, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, targetAcctID) if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) + logrus.Debug(errWithCode.Error()) + c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) return } diff --git a/internal/api/client/account/statuses.go b/internal/api/client/account/statuses.go @@ -60,6 +60,12 @@ import ( // default: false // in: query // required: false +// - name: exclude_reblogs +// type: boolean +// description: Exclude statuses that are a reblog/boost of another status. +// default: false +// in: query +// required: false // - name: max_id // type: string // description: |- @@ -75,7 +81,7 @@ import ( // required: false // - name: pinned_only // type: boolean -// description: Show only pinned statuses. In other words,e xclude statuses that are not pinned to the given account ID. +// description: Show only pinned statuses. In other words, exclude statuses that are not pinned to the given account ID. // default: false // in: query // required: false @@ -149,13 +155,25 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) { if excludeRepliesString != "" { i, err := strconv.ParseBool(excludeRepliesString) if err != nil { - l.Debugf("error parsing replies string: %s", err) + l.Debugf("error parsing exclude replies string: %s", err) c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse exclude replies query param"}) return } excludeReplies = i } + excludeReblogs := false + excludeReblogsString := c.Query(ExcludeReblogsKey) + if excludeReblogsString != "" { + i, err := strconv.ParseBool(excludeReblogsString) + if err != nil { + l.Debugf("error parsing exclude reblogs string: %s", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse exclude reblogs query param"}) + return + } + excludeReblogs = i + } + maxID := "" maxIDString := c.Query(MaxIDKey) if maxIDString != "" { @@ -204,7 +222,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) { publicOnly = i } - statuses, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, maxID, minID, pinnedOnly, mediaOnly, publicOnly) + statuses, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly) if errWithCode != nil { l.Debugf("error from processor account statuses get: %s", errWithCode) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) diff --git a/internal/api/s2s/user/inboxpost_test.go b/internal/api/s2s/user/inboxpost_test.go @@ -440,7 +440,7 @@ func (suite *InboxPostTestSuite) TestPostDelete() { suite.ErrorIs(err, db.ErrNoEntries) // no statuses from foss satan should be left in the database - dbStatuses, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, "", "", false, false, false) + dbStatuses, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, false, "", "", false, false, false) suite.ErrorIs(err, db.ErrNoEntries) suite.Empty(dbStatuses) diff --git a/internal/db/account.go b/internal/db/account.go @@ -52,7 +52,7 @@ type Account interface { // then all statuses will be returned. If limit is set to 0, the size of the returned slice will not be limited. This can // be very memory intensive so you probably shouldn't do this! // In case of no entries, a 'no entries' error will be returned - GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, Error) + GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, Error) GetAccountBlocks(ctx context.Context, accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, Error) diff --git a/internal/db/bundb/account.go b/internal/db/bundb/account.go @@ -199,7 +199,7 @@ func (a *accountDB) GetLocalAccountByUsername(ctx context.Context, username stri account := new(gtsmodel.Account) q := a.newAccountQ(account). - Where("username = ?", username). + Where("LOWER(?) = LOWER(?)", bun.Ident("username"), username). // ignore casing WhereGroup(" AND ", whereEmptyOrNull("domain")) if err := q.Scan(ctx); err != nil { @@ -230,7 +230,7 @@ func (a *accountDB) CountAccountStatuses(ctx context.Context, accountID string) Count(ctx) } -func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, db.Error) { +func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, db.Error) { statuses := []*gtsmodel.Status{} q := a.conn. @@ -250,6 +250,10 @@ func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, li q = q.WhereGroup(" AND ", whereEmptyOrNull("in_reply_to_id")) } + if excludeReblogs { + q = q.WhereGroup(" AND ", whereEmptyOrNull("boost_of_id")) + } + if maxID != "" { q = q.Where("id < ?", maxID) } diff --git a/internal/processing/account.go b/internal/processing/account.go @@ -34,16 +34,20 @@ func (p *processor) AccountDeleteLocal(ctx context.Context, authed *oauth.Auth, return p.accountProcessor.DeleteLocal(ctx, authed.Account, form) } -func (p *processor) AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, error) { +func (p *processor) AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, gtserror.WithCode) { return p.accountProcessor.Get(ctx, authed.Account, targetAccountID) } +func (p *processor) AccountGetLocalByUsername(ctx context.Context, authed *oauth.Auth, username string) (*apimodel.Account, gtserror.WithCode) { + return p.accountProcessor.GetLocalByUsername(ctx, authed.Account, username) +} + func (p *processor) AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error) { return p.accountProcessor.Update(ctx, authed.Account, form) } -func (p *processor) AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) { - return p.accountProcessor.StatusesGet(ctx, authed.Account, targetAccountID, limit, excludeReplies, maxID, minID, pinnedOnly, mediaOnly, publicOnly) +func (p *processor) AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) { + return p.accountProcessor.StatusesGet(ctx, authed.Account, targetAccountID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly) } func (p *processor) AccountFollowersGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) { diff --git a/internal/processing/account/account.go b/internal/processing/account/account.go @@ -47,12 +47,14 @@ type Processor interface { // Unlike Delete, it will propagate the deletion out across the federating API to other instances. DeleteLocal(ctx context.Context, account *gtsmodel.Account, form *apimodel.AccountDeleteRequest) gtserror.WithCode // Get processes the given request for account information. - Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, error) + Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, gtserror.WithCode) + // GetLocalByUsername processes the given request for account information targeting a local account by username. + GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode) // Update processes the update of an account with the given form Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error) // StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for // the account given in authed. - StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) + StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) // FollowersGet fetches a list of the target account's followers. FollowersGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) // FollowingGet fetches a list of the accounts that target account is following. diff --git a/internal/processing/account/delete.go b/internal/processing/account/delete.go @@ -143,7 +143,7 @@ func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origi var maxID string selectStatusesLoop: for { - statuses, err := p.db.GetAccountStatuses(ctx, account.ID, 20, false, maxID, "", false, false, false) + statuses, err := p.db.GetAccountStatuses(ctx, account.ID, 20, false, false, maxID, "", false, false, false) if err != nil { if err == db.ErrNoEntries { // no statuses left for this instance so we're done diff --git a/internal/processing/account/get.go b/internal/processing/account/get.go @@ -26,23 +26,41 @@ import ( apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, error) { +func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Account, gtserror.WithCode) { targetAccount, err := p.db.GetAccountByID(ctx, targetAccountID) if err != nil { if err == db.ErrNoEntries { - return nil, errors.New("account not found") + return nil, gtserror.NewErrorNotFound(errors.New("account not found")) } - return nil, fmt.Errorf("db error: %s", err) + return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error: %s", err)) } + return p.getAccountFor(ctx, requestingAccount, targetAccount) +} + +func (p *processor) GetLocalByUsername(ctx context.Context, requestingAccount *gtsmodel.Account, username string) (*apimodel.Account, gtserror.WithCode) { + targetAccount, err := p.db.GetLocalAccountByUsername(ctx, username) + if err != nil { + if err == db.ErrNoEntries { + return nil, gtserror.NewErrorNotFound(errors.New("account not found")) + } + return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error: %s", err)) + } + + return p.getAccountFor(ctx, requestingAccount, targetAccount) +} + +func (p *processor) getAccountFor(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (*apimodel.Account, gtserror.WithCode) { var blocked bool + var err error if requestingAccount != nil { - blocked, err = p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true) + blocked, err = p.db.IsBlocked(ctx, requestingAccount.ID, targetAccount.ID, true) if err != nil { - return nil, fmt.Errorf("error checking account block: %s", err) + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking account block: %s", err)) } } @@ -50,7 +68,7 @@ func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account if blocked { apiAccount, err = p.tc.AccountToAPIAccountBlocked(ctx, targetAccount) if err != nil { - return nil, fmt.Errorf("error converting account: %s", err) + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting account: %s", err)) } return apiAccount, nil } @@ -59,7 +77,7 @@ func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account if targetAccount.Domain != "" { targetAccountURI, err := url.Parse(targetAccount.URI) if err != nil { - return nil, fmt.Errorf("error parsing url %s: %s", targetAccount.URI, err) + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", targetAccount.URI, err)) } a, err := p.federator.GetRemoteAccount(ctx, requestingAccount.Username, targetAccountURI, true, false) @@ -74,7 +92,7 @@ func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account apiAccount, err = p.tc.AccountToAPIAccountPublic(ctx, targetAccount) } if err != nil { - return nil, fmt.Errorf("error converting account: %s", err) + return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting account: %s", err)) } return apiAccount, nil } diff --git a/internal/processing/account/getstatuses.go b/internal/processing/account/getstatuses.go @@ -28,16 +28,18 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) { - if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true); err != nil { - return nil, gtserror.NewErrorInternalError(err) - } else if blocked { - return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts")) +func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) { + if requestingAccount != nil { + if blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, targetAccountID, true); err != nil { + return nil, gtserror.NewErrorInternalError(err) + } else if blocked { + return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts")) + } } apiStatuses := []apimodel.Status{} - statuses, err := p.db.GetAccountStatuses(ctx, targetAccountID, limit, excludeReplies, maxID, minID, pinnedOnly, mediaOnly, publicOnly) + statuses, err := p.db.GetAccountStatuses(ctx, targetAccountID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly) if err != nil { if err == db.ErrNoEntries { return apiStatuses, nil diff --git a/internal/processing/federation/getoutbox.go b/internal/processing/federation/getoutbox.go @@ -89,7 +89,7 @@ func (p *processor) GetOutbox(ctx context.Context, requestedUsername string, pag // scenario 2 -- get the requested page // limit pages to 30 entries per page - publicStatuses, err := p.db.GetAccountStatuses(ctx, requestedAccount.ID, 30, true, maxID, minID, false, false, true) + publicStatuses, err := p.db.GetAccountStatuses(ctx, requestedAccount.ID, 30, true, true, maxID, minID, false, false, true) if err != nil && err != db.ErrNoEntries { return nil, gtserror.NewErrorInternalError(err) } diff --git a/internal/processing/federation/getuser.go b/internal/processing/federation/getuser.go @@ -38,17 +38,20 @@ func (p *processor) GetUser(ctx context.Context, requestedUsername string, reque } var requestedPerson vocab.ActivityStreamsPerson - switch { - case uris.IsPublicKeyPath(requestURL): + if uris.IsPublicKeyPath(requestURL) { // if it's a public key path, we don't need to authenticate but we'll only serve the bare minimum user profile needed for the public key requestedPerson, err = p.tc.AccountToASMinimal(ctx, requestedAccount) if err != nil { return nil, gtserror.NewErrorInternalError(err) } - case uris.IsUserPath(requestURL): - // if it's a user path, we want to fully authenticate the request before we serve any data, and then we can serve a more complete profile + } else { + // if it's any other path, we want to fully authenticate the request before we serve any data, and then we can serve a more complete profile requestingAccountURI, authenticated, err := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername) - if err != nil || !authenticated { + if err != nil { + return nil, gtserror.NewErrorNotAuthorized(err, "not authorized") + } + + if !authenticated { return nil, gtserror.NewErrorNotAuthorized(errors.New("not authorized"), "not authorized") } @@ -73,8 +76,6 @@ func (p *processor) GetUser(ctx context.Context, requestedUsername string, reque if err != nil { return nil, gtserror.NewErrorInternalError(err) } - default: - return nil, gtserror.NewErrorBadRequest(fmt.Errorf("path was not public key path or user path")) } data, err := streams.Serialize(requestedPerson) diff --git a/internal/processing/fromfederator_test.go b/internal/processing/fromfederator_test.go @@ -354,7 +354,7 @@ func (suite *FromFederatorTestSuite) TestProcessAccountDelete() { suite.False(zorkFollowsSatan) // no statuses from foss satan should be left in the database - dbStatuses, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, "", "", false, false, false) + dbStatuses, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, false, "", "", false, false, false) suite.ErrorIs(err, db.ErrNoEntries) suite.Empty(dbStatuses) diff --git a/internal/processing/processor.go b/internal/processing/processor.go @@ -76,12 +76,14 @@ type Processor interface { // AccountDeleteLocal processes the delete of a LOCAL account using the given form. AccountDeleteLocal(ctx context.Context, authed *oauth.Auth, form *apimodel.AccountDeleteRequest) gtserror.WithCode // AccountGet processes the given request for account information. - AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, error) + AccountGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) (*apimodel.Account, gtserror.WithCode) + // AccountGet processes the given request for account information. + AccountGetLocalByUsername(ctx context.Context, authed *oauth.Auth, username string) (*apimodel.Account, gtserror.WithCode) // AccountUpdate processes the update of an account with the given form AccountUpdate(ctx context.Context, authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error) // AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for // the account given in authed. - AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) + AccountStatusesGet(ctx context.Context, authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinned bool, mediaOnly bool, publicOnly bool) ([]apimodel.Status, gtserror.WithCode) // AccountFollowersGet fetches a list of the target account's followers. AccountFollowersGet(ctx context.Context, authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, gtserror.WithCode) // AccountFollowingGet fetches a list of the accounts that target account is following. diff --git a/internal/router/template.go b/internal/router/template.go @@ -67,6 +67,11 @@ func timestamp(stamp string) string { return t.Format("January 2, 2006, 15:04:05") } +func timestampShort(stamp string) string { + t, _ := time.Parse(time.RFC3339, stamp) + return t.Format("January, 2006") +} + type iconWithLabel struct { faIcon string label string @@ -98,5 +103,6 @@ func LoadTemplateFunctions(engine *gin.Engine) { "oddOrEven": oddOrEven, "visibilityIcon": visibilityIcon, "timestamp": timestamp, + "timestampShort": timestampShort, }) } diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go @@ -132,7 +132,7 @@ func (suite *InternalToASTestSuite) TestStatusesToASOutboxPage() { ctx := context.Background() // get public statuses from testaccount - statuses, err := suite.db.GetAccountStatuses(ctx, testAccount.ID, 30, true, "", "", false, false, true) + statuses, err := suite.db.GetAccountStatuses(ctx, testAccount.ID, 30, true, true, "", "", false, false, true) suite.NoError(err) page, err := suite.typeconverter.StatusesToASOutboxPage(ctx, testAccount.OutboxURI, "", "", statuses) diff --git a/internal/web/base.go b/internal/web/base.go @@ -20,8 +20,10 @@ package web import ( "fmt" + "io/ioutil" "net/http" "path/filepath" + "strings" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" @@ -36,18 +38,68 @@ import ( const ( confirmEmailPath = "/" + uris.ConfirmEmailPath tokenParam = "token" + usernameKey = "username" + statusIDKey = "status" + profilePath = "/@:" + usernameKey + statusPath = profilePath + "/statuses/:" + statusIDKey ) // Module implements the api.ClientModule interface for web pages. type Module struct { - processor processing.Processor + processor processing.Processor + assetsPath string + adminPath string + defaultAvatars []string } // New returns a new api.ClientModule for web pages. -func New(processor processing.Processor) api.ClientModule { - return &Module{ - processor: processor, +func New(processor processing.Processor) (api.ClientModule, error) { + assetsBaseDir := viper.GetString(config.Keys.WebAssetBaseDir) + if assetsBaseDir == "" { + return nil, fmt.Errorf("%s cannot be empty and must be a relative or absolute path", config.Keys.WebAssetBaseDir) + } + + assetsPath, err := filepath.Abs(assetsBaseDir) + if err != nil { + return nil, fmt.Errorf("error getting absolute path of %s: %s", assetsBaseDir, err) } + + defaultAvatarsPath := filepath.Join(assetsPath, "default_avatars") + defaultAvatarFiles, err := ioutil.ReadDir(defaultAvatarsPath) + if err != nil { + return nil, fmt.Errorf("error reading default avatars at %s: %s", defaultAvatarsPath, err) + } + + defaultAvatars := []string{} + for _, f := range defaultAvatarFiles { + // ignore directories + if f.IsDir() { + continue + } + + // ignore files bigger than 50kb + if f.Size() > 50000 { + continue + } + + extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(f.Name())), ".") + + // take only files with simple extensions + switch extension { + case "svg", "jpeg", "jpg", "gif", "png": + defaultAvatarPath := fmt.Sprintf("/assets/default_avatars/%s", f.Name()) + defaultAvatars = append(defaultAvatars, defaultAvatarPath) + default: + continue + } + } + + return &Module{ + processor: processor, + assetsPath: assetsPath, + adminPath: filepath.Join(assetsPath, "admin"), + defaultAvatars: defaultAvatars, + }, nil } func (m *Module) baseHandler(c *gin.Context) { @@ -88,20 +140,11 @@ func (m *Module) NotFoundHandler(c *gin.Context) { // Route satisfies the RESTAPIModule interface func (m *Module) Route(s router.Router) error { // serve static files from assets dir at /assets - assetBaseDir := viper.GetString(config.Keys.WebAssetBaseDir) - if assetBaseDir == "" { - return fmt.Errorf("%s cannot be empty and must be a relative or absolute path", config.Keys.WebAssetBaseDir) - } - assetPath, err := filepath.Abs(assetBaseDir) - if err != nil { - return fmt.Errorf("error getting absolute path of %s: %s", assetBaseDir, err) - } - s.AttachStaticFS("/assets", fileSystem{http.Dir(assetPath)}) + s.AttachStaticFS("/assets", fileSystem{http.Dir(m.assetsPath)}) // serve admin panel from within assets dir at /admin/ // and redirect /admin to /admin/ - adminPath := filepath.Join(assetPath, "admin") - s.AttachStaticFS("/admin/", fileSystem{http.Dir(adminPath)}) + s.AttachStaticFS("/admin/", fileSystem{http.Dir(m.adminPath)}) s.AttachHandler(http.MethodGet, "/admin", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "/admin/") }) @@ -109,8 +152,11 @@ func (m *Module) Route(s router.Router) error { // serve front-page s.AttachHandler(http.MethodGet, "/", m.baseHandler) + // serve profile pages at /@username + s.AttachHandler(http.MethodGet, profilePath, m.profileTemplateHandler) + // serve statuses - s.AttachHandler(http.MethodGet, "/:user/statuses/:id", m.threadTemplateHandler) + s.AttachHandler(http.MethodGet, statusPath, m.threadTemplateHandler) // serve email confirmation page at /confirm_email?token=whatever s.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler) diff --git a/internal/web/profile.go b/internal/web/profile.go @@ -0,0 +1,139 @@ +/* + GoToSocial + Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + + 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 web + +import ( + "context" + "encoding/json" + "fmt" + "math/rand" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "github.com/superseriousbusiness/gotosocial/internal/ap" + "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +func (m *Module) profileTemplateHandler(c *gin.Context) { + l := logrus.WithField("func", "profileTemplateHandler") + l.Trace("rendering profile template") + ctx := c.Request.Context() + + username := c.Param(usernameKey) + if username == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "no account username specified"}) + return + } + + authed, err := oauth.Authed(c, false, false, false, false) + if err != nil { + l.Errorf("error authing profile GET request: %s", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) + return + } + + instance, errWithCode := m.processor.InstanceGet(ctx, viper.GetString(config.Keys.Host)) + if errWithCode != nil { + l.Debugf("error getting instance from processor: %s", errWithCode.Error()) + c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) + return + } + + account, errWithCode := m.processor.AccountGetLocalByUsername(ctx, authed, username) + if errWithCode != nil { + l.Debugf("error getting account from processor: %s", errWithCode.Error()) + if errWithCode.Code() == http.StatusNotFound { + m.NotFoundHandler(c) + return + } + c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) + return + } + + // if we're getting an AP request on this endpoint we should render the account's AP representation instead + accept := c.NegotiateFormat(string(api.TextHTML), string(api.AppActivityJSON), string(api.AppActivityLDJSON)) + if accept == string(api.AppActivityJSON) || accept == string(api.AppActivityLDJSON) { + m.returnAPRepresentation(ctx, c, username, accept) + return + } + + // get latest 10 top-level public statuses; + // ie., exclude replies and boosts, public only, + // with or without media + statuses, errWithCode := m.processor.AccountStatusesGet(ctx, authed, account.ID, 10, true, true, "", "", false, false, true) + if errWithCode != nil { + l.Debugf("error getting statuses from processor: %s", errWithCode.Error()) + c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) + return + } + + // pick a random dummy avatar if this account avatar isn't set yet + if account.Avatar == "" && len(m.defaultAvatars) > 0 { + //nolint:gosec + randomIndex := rand.Intn(len(m.defaultAvatars)) + dummyAvatar := m.defaultAvatars[randomIndex] + account.Avatar = dummyAvatar + for _, s := range statuses { + s.Account.Avatar = dummyAvatar + } + } + + c.HTML(http.StatusOK, "profile.tmpl", gin.H{ + "instance": instance, + "account": account, + "statuses": statuses, + "stylesheets": []string{ + "/assets/Fork-Awesome/css/fork-awesome.min.css", + "/assets/status.css", + "/assets/profile.css", + }, + }) +} + +func (m *Module) returnAPRepresentation(ctx context.Context, c *gin.Context, username string, accept string) { + verifier, signed := c.Get(string(ap.ContextRequestingPublicKeyVerifier)) + if signed { + ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeyVerifier, verifier) + } + + signature, signed := c.Get(string(ap.ContextRequestingPublicKeySignature)) + if signed { + ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeySignature, signature) + } + + user, errWithCode := m.processor.GetFediUser(ctx, username, c.Request.URL) // GetFediUser handles auth as well + if errWithCode != nil { + logrus.Infof(errWithCode.Error()) + c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) + return + } + + b, mErr := json.Marshal(user) + if mErr != nil { + err := fmt.Errorf("could not marshal json: %s", mErr) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.Data(http.StatusOK, accept, b) +} diff --git a/internal/web/thread.go b/internal/web/thread.go @@ -20,6 +20,7 @@ package web import ( "net/http" + "strings" "github.com/sirupsen/logrus" "github.com/spf13/viper" @@ -29,21 +30,21 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/oauth" ) -type statusLink struct { - User string `uri:"user" binding:"required"` - ID string `uri:"id" binding:"required"` -} - func (m *Module) threadTemplateHandler(c *gin.Context) { l := logrus.WithField("func", "threadTemplateGET") l.Trace("rendering thread template") ctx := c.Request.Context() - var uriParts statusLink + username := c.Param(usernameKey) + if username == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "no account username specified"}) + return + } - if err := c.ShouldBindUri(&uriParts); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "status not found"}) + statusID := c.Param(statusIDKey) + if username == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "no status id specified"}) return } @@ -62,18 +63,18 @@ func (m *Module) threadTemplateHandler(c *gin.Context) { return } - status, err := m.processor.StatusGet(ctx, authed, uriParts.ID) + status, err := m.processor.StatusGet(ctx, authed, statusID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "status not found"}) return } - if uriParts.User[:1] != "@" || uriParts.User[1:] != status.Account.Username { + if !strings.EqualFold(username, status.Account.Username) { c.JSON(http.StatusBadRequest, gin.H{"error": "status not found"}) return } - context, err := m.processor.StatusGetContext(ctx, authed, uriParts.ID) + context, err := m.processor.StatusGetContext(ctx, authed, statusID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "status not found"}) return diff --git a/web/assets/base.css b/web/assets/base.css @@ -166,23 +166,24 @@ section.login form button { } section.error { - display: flex; - flex-direction: row; - align-items: center; + display: flex; + flex-direction: row; + align-items: center; } + section.error span { - font-size: 2em; -} -section.error pre { - border: 1px solid #ff000080; - margin-left: 1em; - padding: 0 0.7em; - border-radius: 0.5em; - background-color: #ff000010; - font-size: 1.3em; - white-space: pre-wrap; -} + font-size: 2em; + } +section.error pre { + border: 1px solid #ff000080; + margin-left: 1em; + padding: 0 0.7em; + border-radius: 0.5em; + background-color: #ff000010; + font-size: 1.3em; + white-space: pre-wrap; + } input, select, textarea { border: 1px solid #fafaff; diff --git a/web/assets/default_avatars/GoToSocial_icon1.svg b/web/assets/default_avatars/GoToSocial_icon1.svg @@ -0,0 +1,160 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1000px" + height="1000px" + viewBox="0 0 1000 1000" + version="1.1" + id="SVGRoot" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" + sodipodi:docname="GoToSocial_icon1.svg" + inkscape:export-xdpi="95.999992" + inkscape:export-ydpi="95.999992"> + <defs + id="defs5117"> + <inkscape:path-effect + effect="spiro" + id="path-effect5760" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5756" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5752" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5748" + is_visible="true" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.70710678" + inkscape:cx="460.72691" + inkscape:cy="522.20279" + inkscape:document-units="px" + inkscape:current-layer="layer3" + showgrid="false" + inkscape:window-width="1920" + inkscape:window-height="1057" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" /> + <metadata + id="metadata5120"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="background" + inkscape:groupmode="layer" + id="layer1" + style="display:inline"> + <rect + style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + id="rect5876" + width="1000.0042" + height="1000" + x="0" + y="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="sloth" + style="display:inline"> + <g + id="g5890" + transform="translate(-10)"> + <path + sodipodi:nodetypes="ssscccs" + inkscape:connector-curvature="0" + id="path5762" + d="M 861.29285,497.07031 C 861.65556,665.3247 774.21642,807.40548 511.60027,807.86794 270.63622,808.29226 154.54309,691.2756 155.19024,504.19228 155.7289,348.47535 251.17288,227.4551 422.3176,205.3802 c -35.32036,-75.85452 52.24232,-96.94648 73.77615,-32.00508 13.73451,-37.63439 108.24345,-49.1716 62.21106,24.77055 147.95052,3.75658 302.58353,111.28061 302.98804,298.92464 z" + style="display:inline;fill:#767676;fill-opacity:1;stroke:none;stroke-width:2.57058167;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="sssssss" + inkscape:connector-curvature="0" + id="path5780" + d="m 809.15213,517.31679 c -4.83374,150.52526 -109.85544,235.22815 -297.81171,235.31839 -179.6675,0.0863 -290.56109,-70.98245 -298.50223,-235.31839 -4.6366,-95.95095 54.62861,-181.84442 144.83016,-194.18834 80.92123,-11.07393 99.7402,21.01802 153.67207,21.01802 59.21658,0 83.64871,-35.09608 162.84221,-21.85479 87.78391,14.67763 137.90533,103.6017 134.9695,195.02511 z" + style="display:inline;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="scssscs" + inkscape:connector-curvature="0" + id="path5780-9" + d="m 809.15213,517.31679 c -1.32872,41.37724 -10.22787,77.78081 -26.33906,108.8204 -46.60931,-39.48031 -99.53509,-10.7281 -171.50115,-39.43334 -44.77145,-17.85808 -51.41659,-56.56453 -51.21999,-81.3542 0.54836,-69.14384 48.17003,-93.45758 95.53601,-97.60875 55.74677,-4.88566 124.5246,36.1482 151.01547,66.79433 2.11531,14.01083 2.97167,28.36512 2.50872,42.78156 z" + style="display:inline;fill:#a1a1a1;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <ellipse + ry="50.575684" + rx="37.800804" + cy="502.64291" + cx="646.85773" + id="path5816" + style="fill:#767676;fill-opacity:1;stroke:none;stroke-width:1.51185882;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="scssscs" + inkscape:connector-curvature="0" + id="path5780-9-1" + d="m 212.51463,517.3246 c 1.32872,41.37724 10.22787,77.78081 26.33906,108.8204 46.60931,-39.48031 99.57415,-10.73591 171.54021,-39.44115 44.77145,-17.85808 51.41659,-56.56453 51.21999,-81.3542 -0.54836,-69.14384 -48.20909,-93.44977 -95.57507,-97.60094 -55.74677,-4.88566 -124.5246,36.1482 -151.01547,66.79433 -2.11531,14.01083 -2.97167,28.36512 -2.50872,42.78156 z" + style="display:inline;fill:#a1a1a1;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <ellipse + transform="scale(-1,1)" + ry="50.575684" + rx="37.800804" + cy="502.64294" + cx="-374.84808" + id="path5816-0" + style="display:inline;fill:#767676;fill-opacity:1;stroke:none;stroke-width:1.51185882;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="sssss" + inkscape:connector-curvature="0" + id="path5862" + d="m 543.96613,556.96185 c 0,11.0622 -14.51648,20.02988 -32.42347,20.02988 -17.90698,0 -32.42347,-8.96769 -32.42347,-20.02988 0,-11.0622 14.14619,-15.58638 32.05318,-15.58638 17.90698,0 32.79376,4.52417 32.79376,15.58638 z" + style="display:inline;fill:#767676;fill-opacity:1;stroke:none;stroke-width:1.60515046;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="sssss" + inkscape:connector-curvature="0" + id="path5865" + d="m 552.00195,620.36132 c 7.06643,13.89391 -19.38375,21.24024 -40.2832,21.24024 -20.89945,0 -47.71708,-7.02219 -41.50391,-21.24024 5.71775,-13.08435 20.11619,0.73243 41.01563,0.73243 20.89944,0 34.43888,-13.1835 40.77148,-0.73243 z" + style="display:inline;fill:#767676;fill-opacity:1;stroke:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <ellipse + transform="rotate(-6.669407)" + ry="24.882849" + rx="19.511755" + cy="560.95673" + cx="600.24731" + id="path5818" + style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.53898752;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <ellipse + transform="rotate(-6.6694071)" + ry="24.882849" + rx="19.511755" + cy="529.32086" + cx="329.69714" + id="path5818-8" + style="display:inline;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.53898752;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + </g> + </g> +</svg> diff --git a/web/assets/default_avatars/GoToSocial_icon2.svg b/web/assets/default_avatars/GoToSocial_icon2.svg @@ -0,0 +1,137 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1000px" + height="1000px" + viewBox="0 0 1000 1000" + version="1.1" + id="SVGRoot" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" + sodipodi:docname="GoToSocial_icon2.svg" + inkscape:export-xdpi="95.999992" + inkscape:export-ydpi="95.999992"> + <defs + id="defs5117"> + <inkscape:path-effect + effect="spiro" + id="path-effect5760" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5756" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5752" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5748" + is_visible="true" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.35355339" + inkscape:cx="497.76221" + inkscape:cy="575.69254" + inkscape:document-units="px" + inkscape:current-layer="layer3" + showgrid="false" + inkscape:window-width="1920" + inkscape:window-height="1057" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" /> + <metadata + id="metadata5120"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="background" + inkscape:groupmode="layer" + id="layer1" + style="display:inline"> + <rect + style="fill:#d0d0d0;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + id="rect5876" + width="1000.0042" + height="1000" + x="0" + y="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="sloth" + style="display:inline"> + <g + id="g3978"> + <path + sodipodi:nodetypes="sccccccccccscssscsccs" + inkscape:connector-curvature="0" + id="rect5876-5" + d="M 230.01724,321.98276 C 349.98851,210.42521 448.21823,215.25781 531.04596,212.10691 c -43.53882,-55.72028 69.90321,-110.15948 114.22539,-8.84147 88.6448,-15.41997 77.60551,48.58211 55.52314,59.56776 116.1765,70.90805 150.01289,158.27624 164.97213,224.4903 78.29027,48.93425 33.19569,98.38368 21.79073,93.92084 57.78384,113.21004 8.74508,163.48051 -19.17636,145.16042 C 847.88417,891.22533 777.25,1000 777.25,1000 H 0 V 335 c -0.79197362,-2.0633 86.752294,193.09344 193.48008,463.67555 8.22828,-21.75326 15.34189,-32.85227 29.8481,-31.82047 12.36034,0.87917 27.76558,15.1443 13.3047,74.3212 0.95528,-0.34561 22.71708,-7.34064 29.09712,1.6646 12.30907,17.37387 -7.57175,23.95646 -1.82859,33.94605 6.04771,10.51933 23.2419,-4.11346 24.38025,-22.42448 0.95349,-15.33743 -7.68068,-33.34588 -31.29939,-31.7437 8.19555,-53.54698 -12.66475,-64.63281 -12.97883,-65.12097 -4.1124,-6.39167 -26.81347,-22.42708 -49.42662,1.72757 -15.70939,-47.21646 -51.98321,-128.67679 -77.05729,-192.68773 31.46671,-57.43102 24.70494,-162.9191 112.49771,-244.55486 z" + style="display:inline;fill:#777777;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="sssssss" + inkscape:connector-curvature="0" + id="path5899" + d="m 804.83231,554.45837 c 0,140.99987 -133.69246,255.30285 -298.61048,255.30285 -164.91801,0 -298.61047,-114.30298 -298.61047,-255.30285 0,-100.64813 59.92313,-198.68967 160.73026,-206.70182 60.18634,-4.78361 90.68629,22.30425 137.88021,22.81676 46.04207,0.5 70.12813,-25.6781 135.69855,-22.78348 115.9463,5.11848 162.91193,120.63643 162.91193,206.66854 z" + style="display:inline;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + style="display:inline;fill:#a1a1a1;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + d="m 804.83231,554.45837 c 0,13.4968 -1.22499,26.74899 -3.58509,39.67972 -71.95206,24.77201 -204.63976,33.96213 -235.31322,-11.60451 -31.67182,-47.04972 13.47247,-156.40488 88.33815,-154.51226 68.45478,1.73055 101.55237,13.1539 132.47817,31.57199 12.36622,31.25081 18.08199,64.85216 18.08199,94.86506 z" + id="path5937" + inkscape:connector-curvature="0" + sodipodi:nodetypes="scsscs" /> + <path + sodipodi:nodetypes="scsscs" + inkscape:connector-curvature="0" + id="path5953" + d="m 207.65971,554.45837 c 0,13.4968 1.22499,26.74899 3.58509,39.67972 71.95206,24.77201 204.63976,33.96213 235.31322,-11.60451 31.67182,-47.04972 -13.47247,-156.40488 -88.33815,-154.51226 -68.45478,1.73055 -101.55237,13.1539 -132.47817,31.57199 -12.36622,31.25081 -18.08199,64.85216 -18.08199,94.86506 z" + style="display:inline;fill:#a1a1a1;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="sssss" + inkscape:connector-curvature="0" + id="path5955" + d="m 540.5,587.08433 c 0,11.71361 -14.7746,21.20938 -33,21.20938 -18.2254,0 -33,-9.49577 -33,-21.20938 0,-11.71362 14.0675,-13.51846 32.29289,-13.51846 18.2254,0 33.70711,1.80484 33.70711,13.51846 z" + style="fill:#777777;fill-opacity:1;stroke:none;stroke-width:0.34833181;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <ellipse + ry="44.492474" + rx="35.992474" + cy="533" + cx="641.5" + id="path5939" + style="display:inline;fill:#777777;fill-opacity:1;stroke:none;stroke-width:0.34905314;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <ellipse + transform="scale(-1,1)" + style="display:inline;fill:#777777;fill-opacity:1;stroke:none;stroke-width:0.34905314;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + id="ellipse5951" + cx="-370.992" + cy="533" + rx="35.992474" + ry="44.492474" /> + </g> + </g> +</svg> diff --git a/web/assets/default_avatars/GoToSocial_icon3.svg b/web/assets/default_avatars/GoToSocial_icon3.svg @@ -0,0 +1,137 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1000px" + height="1000px" + viewBox="0 0 1000 1000" + version="1.1" + id="SVGRoot" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" + sodipodi:docname="GoToSocial_icon3.svg" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> + <defs + id="defs5117"> + <inkscape:path-effect + effect="spiro" + id="path-effect5760" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5756" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5752" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5748" + is_visible="true" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.5" + inkscape:cx="332.72586" + inkscape:cy="487.44283" + inkscape:document-units="px" + inkscape:current-layer="layer3" + showgrid="false" + inkscape:window-width="1920" + inkscape:window-height="1057" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" /> + <metadata + id="metadata5120"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="background" + inkscape:groupmode="layer" + id="layer1" + style="display:inline"> + <rect + style="fill:#dbe1ed;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + id="rect5876" + width="1000.0042" + height="1000" + x="0" + y="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="sloth" + style="display:inline"> + <g + id="g3968"> + <path + sodipodi:nodetypes="sccccccccccscssscsccs" + inkscape:connector-curvature="0" + id="rect5876-5" + d="M 230.01724,321.98276 C 349.98851,210.42521 448.21823,215.25781 531.04596,212.10691 c -43.53882,-55.72028 69.90321,-110.15948 114.22539,-8.84147 88.6448,-15.41997 77.60551,48.58211 55.52314,59.56776 116.1765,70.90805 150.01289,158.27624 164.97213,224.4903 78.29027,48.93425 33.19569,98.38368 21.79073,93.92084 57.78384,113.21004 8.74508,163.48051 -19.17636,145.16042 C 847.88417,891.22533 777.25,1000 777.25,1000 H 0 V 335 c -0.79197362,-2.0633 86.752294,193.09344 193.48008,463.67555 8.22828,-21.75326 15.34189,-32.85227 29.8481,-31.82047 12.36034,0.87917 27.76558,15.1443 13.3047,74.3212 0.95528,-0.34561 22.71708,-7.34064 29.09712,1.6646 12.30907,17.37387 -7.57175,23.95646 -1.82859,33.94605 6.04771,10.51933 23.2419,-4.11346 24.38025,-22.42448 0.95349,-15.33743 -7.68068,-33.34588 -31.29939,-31.7437 8.19555,-53.54698 -12.66475,-64.63281 -12.97883,-65.12097 -4.1124,-6.39167 -26.81347,-22.42708 -49.42662,1.72757 -15.70939,-47.21646 -51.98321,-128.67679 -77.05729,-192.68773 31.46671,-57.43102 24.70494,-162.9191 112.49771,-244.55486 z" + style="display:inline;fill:#8a9bab;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="sssssss" + inkscape:connector-curvature="0" + id="path5899" + d="m 804.83231,554.45837 c 0,140.99987 -133.69246,255.30285 -298.61048,255.30285 -164.91801,0 -298.61047,-114.30298 -298.61047,-255.30285 0,-100.64813 59.92313,-198.68967 160.73026,-206.70182 60.18634,-4.78361 90.68629,22.30425 137.88021,22.81676 46.04207,0.5 70.12813,-25.6781 135.69855,-22.78348 115.9463,5.11848 162.91193,120.63643 162.91193,206.66854 z" + style="display:inline;fill:#ecf1f5;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + style="display:inline;fill:#b5becf;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + d="m 804.83231,554.45837 c 0,13.4968 -1.22499,26.74899 -3.58509,39.67972 -71.95206,24.77201 -204.63976,33.96213 -235.31322,-11.60451 -31.67182,-47.04972 13.47247,-156.40488 88.33815,-154.51226 68.45478,1.73055 101.55237,13.1539 132.47817,31.57199 12.36622,31.25081 18.08199,64.85216 18.08199,94.86506 z" + id="path5937" + inkscape:connector-curvature="0" + sodipodi:nodetypes="scsscs" /> + <path + sodipodi:nodetypes="scsscs" + inkscape:connector-curvature="0" + id="path5953" + d="m 207.65971,554.45837 c 0,13.4968 1.22499,26.74899 3.58509,39.67972 71.95206,24.77201 204.63976,33.96213 235.31322,-11.60451 31.67182,-47.04972 -13.47247,-156.40488 -88.33815,-154.51226 -68.45478,1.73055 -101.55237,13.1539 -132.47817,31.57199 -12.36622,31.25081 -18.08199,64.85216 -18.08199,94.86506 z" + style="display:inline;fill:#b5becf;fill-opacity:1;stroke:none;stroke-width:1.63446391;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="sssss" + inkscape:connector-curvature="0" + id="path5955" + d="m 540.5,587.08433 c 0,11.71361 -14.7746,21.20938 -33,21.20938 -18.2254,0 -33,-9.49577 -33,-21.20938 0,-11.71362 14.0675,-13.51846 32.29289,-13.51846 18.2254,0 33.70711,1.80484 33.70711,13.51846 z" + style="fill:#7a7d82;fill-opacity:1;stroke:none;stroke-width:0.34833181;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <ellipse + ry="44.492474" + rx="35.992474" + cy="533" + cx="641.5" + id="path5939" + style="display:inline;fill:#7a7d82;fill-opacity:1;stroke:none;stroke-width:0.34905314;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <ellipse + transform="scale(-1,1)" + style="display:inline;fill:#7a7d82;fill-opacity:1;stroke:none;stroke-width:0.34905314;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + id="ellipse5951" + cx="-370.992" + cy="533" + rx="35.992474" + ry="44.492474" /> + </g> + </g> +</svg> diff --git a/web/assets/default_avatars/GoToSocial_icon4.svg b/web/assets/default_avatars/GoToSocial_icon4.svg @@ -0,0 +1,152 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1000px" + height="1000px" + viewBox="0 0 1000 1000" + version="1.1" + id="SVGRoot" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" + sodipodi:docname="GoToSocial_icon4.svg" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> + <defs + id="defs5117"> + <inkscape:path-effect + is_visible="true" + id="path-effect6023" + effect="spiro" /> + <inkscape:path-effect + effect="spiro" + id="path-effect6019" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5760" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5756" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5752" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5748" + is_visible="true" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.5" + inkscape:cx="-238.86957" + inkscape:cy="384.20002" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="1920" + inkscape:window-height="1057" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" /> + <metadata + id="metadata5120"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="background" + inkscape:groupmode="layer" + id="layer1" + style="display:inline"> + <rect + style="fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.95512283;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + id="rect6025" + width="1000" + height="1000" + x="-1000" + y="-1000" + transform="scale(-1)" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="sloth" + style="display:inline"> + <g + id="g3958"> + <path + sodipodi:nodetypes="ccssccccscccscscccccccc" + inkscape:connector-curvature="0" + id="rect5876" + d="m 250.42136,685.9676 c 27.70546,-52.03568 68.96026,3.65306 45.50576,51.34931 31.74726,11.3972 32.63935,48.28596 10.44891,60.73784 -5.04807,2.83267 -8.35938,4.85246 -6.24616,9.92615 6.82828,16.39412 69.24558,-37.84791 12.03356,-75.57698 17.99005,-83.53997 -44.99856,-94.84265 -61.49124,-71.10901 C 197.50575,583.4007 147.54378,487.681 131.62217,450.28384 c 0.12558,-13.14689 10.55647,-15.91833 16.57496,-17.76346 -52.047785,-29.70807 -27.82707,-79.31533 17.95281,-96.29615 46.52036,-17.25548 75.09848,-100.71517 158.45465,-139.02174 -41.91643,-28.95924 12.41179,-76.2933 69.63812,-60.09295 49.56384,-86.701637 128.76235,-51.997593 106.48829,-6.82033 160.08649,-21.81784 193.18485,37.08464 252.21617,29.88994 126.67808,-15.43944 115.11456,62.82898 97.64128,88.84198 21.70268,16.70276 30.36519,23.60378 61.89934,36.01667 40.08455,15.77862 59.1052,72.49434 0.74775,112.50038 40.62846,38.95859 78.93877,96.58176 32.79494,114.2793 51.96757,64.16898 32.13903,145.87433 -6.38744,145.34448 8.59645,23.3003 -5.32539,71.52714 -35.88758,62.06313 C 884.05327,852.44822 817.94577,1000 817.94577,1000 H 0 V 142.98659 c 0,0 89.549546,320.73624 250.42136,542.98101 z" + style="display:inline;fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="sssssss" + inkscape:connector-curvature="0" + id="path5899" + d="m 839.83231,465.22371 c 0,130.63606 -133.69246,236.53751 -298.61048,236.53751 -164.91801,0 -298.61047,-105.90145 -298.61047,-236.53751 0,-93.25027 59.92313,-184.08554 160.73026,-191.50878 60.18634,-4.432 90.68629,20.66484 137.88021,21.13968 46.04207,0.46325 70.12813,-23.7907 135.69855,-21.10884 115.9463,4.74226 162.91193,111.76938 162.91193,191.47794 z" + style="display:inline;fill:#f6e7e0;fill-opacity:0.98113209;stroke:none;stroke-width:1.57324922;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + style="display:inline;fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.57324922;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + d="m 839.83231,465.22371 c 0,19.38025 -2.94239,38.21613 -8.4926,56.24262 -85.83868,33.0678 -208.68682,26.13484 -229.51106,-25.32493 -26.07758,-64.44157 31.43036,-103.22868 75.8166,-116.93012 51.17758,-15.79784 106.51724,0.003 150.63827,15.41887 7.86068,23.77511 11.54879,48.25697 11.54879,70.59356 z" + id="path6003" + inkscape:connector-curvature="0" + sodipodi:nodetypes="scsscs" /> + <path + sodipodi:nodetypes="sssssss" + inkscape:connector-curvature="0" + id="path6005" + d="m 636.34279,468.2557 c -0.9957,7.73624 0.22345,23.8871 13.0952,23.55507 15.73663,-0.40594 8.01751,-24.25641 28.62411,-24.0854 20.42062,0.16948 12.59412,24.3415 27.18325,24.95261 15.02273,0.62928 16.26485,-17.17918 14.71283,-25.30616 -4.39441,-23.01076 -22.45132,-32.32534 -42.07286,-32.70368 -21.39109,-0.41246 -38.86298,12.76845 -41.54253,33.58756 z" + style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:0.33399999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="scsscs" + inkscape:connector-curvature="0" + id="path6008" + d="m 242.46966,465.22371 c 0,19.38025 2.94239,38.21613 8.4926,56.24262 85.83868,33.0678 208.68682,26.13484 229.51106,-25.32493 26.07758,-64.44157 -31.43036,-103.22868 -75.8166,-116.93012 -51.17758,-15.79784 -106.20788,0.1024 -150.32891,15.51831 -8.25843,23.68672 -11.85815,48.15753 -11.85815,70.49412 z" + style="display:inline;fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.57324922;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:0.33399999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + d="m 445.95918,468.2557 c 0.9957,7.73624 -0.22345,23.8871 -13.0952,23.55507 -15.73663,-0.40594 -8.01751,-24.25641 -28.62411,-24.0854 -20.42062,0.16948 -12.59412,24.3415 -27.18325,24.95261 -15.02273,0.62928 -16.26485,-17.17918 -14.71283,-25.30616 4.39441,-23.01076 22.45132,-32.32534 42.07286,-32.70368 21.39109,-0.41246 38.86298,12.76845 41.54253,33.58756 z" + id="path6010" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssssss" /> + <path + sodipodi:nodetypes="sssss" + inkscape:connector-curvature="0" + id="path6014" + d="m 572.40294,496.00963 c 0,10.44653 -14.00878,18.91511 -31.28948,18.91511 -17.2807,0 -31.28947,-8.46858 -31.28947,-18.91511 1e-5,-10.44652 13.65522,-13.96535 30.93592,-13.96535 17.28069,0 31.64302,3.51883 31.64303,13.96535 z" + style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:0.33399999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + transform="matrix(-1,0,0,1,1088.0012,0)" + sodipodi:nodetypes="sssscsssscs" + inkscape:original-d="m 561.38128,582.67417 c 4.16766,-0.91767 12.20351,2.22573 11.70451,-3.19194 -0.49904,-5.41767 3.22694,-10.0569 -0.43934,-12.36091 -3.71867,-2.33693 -4.66037,-0.70889 -10.62868,-3.03033 -6.7415,-2.62217 -12.89085,-5.33173 -18.0383,-9.27773 -5.14745,3.946 -11.25574,6.65556 -17.99724,9.27773 -5.96831,2.32144 -6.91001,0.6934 -10.62868,3.03033 -3.66628,2.30401 0.0597,6.94324 -0.43934,12.36091 -0.499,5.41767 7.53685,2.27427 11.70451,3.19194 4.16767,0.91767 12.6047,-2.8973 17.27236,-5.97963 4.66766,3.08233 13.32253,6.8973 17.4902,5.97963 z" + inkscape:path-effect="#path-effect6019" + inkscape:connector-curvature="0" + id="path6017" + d="m 561.38128,582.67417 c 2.08275,0.16617 4.19863,0.15523 6.23646,-0.30597 2.03783,-0.46119 4.00308,-1.39624 5.46805,-2.88597 1.62507,-1.65254 2.56385,-3.97206 2.49743,-6.2888 -0.0664,-2.31675 -1.15042,-4.59539 -2.93677,-6.07211 -1.43969,-1.19015 -3.25655,-1.83786 -5.08522,-2.21886 -1.82866,-0.38101 -3.69889,-0.51693 -5.54346,-0.81147 -6.78982,-1.08418 -13.20738,-4.38495 -18.0383,-9.27773 -4.81493,4.89182 -11.2195,8.19343 -17.99724,9.27773 -1.84447,0.29508 -3.7147,0.43095 -5.54339,0.8118 -1.82869,0.38084 -3.64567,1.02831 -5.08529,2.21853 -1.78611,1.47668 -2.86946,3.75542 -2.93562,6.07196 -0.0662,2.31655 0.87229,4.63564 2.49628,6.28895 1.46438,1.49081 3.42913,2.42785 5.46715,2.88979 2.03802,0.46195 4.15458,0.4723 6.23736,0.30215 6.13711,-0.50136 12.1389,-2.57916 17.27236,-5.97963 5.21036,3.40799 11.28399,5.48447 17.4902,5.97963 z" + style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + </g> +</svg> diff --git a/web/assets/default_avatars/GoToSocial_icon5.svg b/web/assets/default_avatars/GoToSocial_icon5.svg @@ -0,0 +1,152 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1000px" + height="1000px" + viewBox="0 0 1000 1000" + version="1.1" + id="SVGRoot" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" + sodipodi:docname="GoToSocial_icon5.svg" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> + <defs + id="defs5117"> + <inkscape:path-effect + is_visible="true" + id="path-effect6023" + effect="spiro" /> + <inkscape:path-effect + effect="spiro" + id="path-effect6019" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5760" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5756" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5752" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5748" + is_visible="true" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.5" + inkscape:cx="429.92366" + inkscape:cy="322.81254" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="1920" + inkscape:window-height="1057" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" /> + <metadata + id="metadata5120"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="background" + inkscape:groupmode="layer" + id="layer1" + style="display:inline"> + <rect + style="fill:#dbe1ed;fill-opacity:1;stroke:none;stroke-width:1.95512283;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + id="rect6025" + width="1000" + height="1000" + x="0" + y="-1000" + transform="scale(1,-1)" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="sloth" + style="display:inline"> + <g + id="g3356"> + <path + sodipodi:nodetypes="cccsssssscscccccccssscc" + inkscape:connector-curvature="0" + id="rect5876" + d="m 874.87783,542.28384 c -26.11086,-42.88953 -41.80521,-83.16959 -54.18766,-152.89697 26.06687,-16.2006 24.83566,-45.46406 -15.39052,-66.46433 33.93989,-31.34323 19.98697,-65.52994 -14.72432,-74.60734 -17.82314,-4.66096 -47.16558,-7.88098 -57.36123,-10.85855 C 682.77372,222.7259 665.18888,198.90619 620.74191,184.15446 466.35494,132.9142 270.81297,170.61723 187.49013,275.95719 c -75.42408,95.35405 -76.23332,151.45419 -93.03896,234.27568 -3.607596,17.77894 -13.074363,43.57065 -18.984879,48.69769 -15.458276,13.40918 -34.278507,59.92672 20.246637,58.01857 -4.911621,26.63049 -16.202612,42.03875 -21.825893,56.4483 -17.846537,45.7314 11.791821,64.75835 49.726775,55.12543 -3.31228,29.06008 -3.80965,68.00974 41.7245,59.75469 -0.009,19.23781 7.68254,28.70603 16.36237,33.35477 C 190.37816,933.25823 216.37467,1000 216.37467,1000 H 1000 V 437.85011 c 0,0 -223.38954,219.6287 -348,370.63648 -12.36429,-39.94931 -41.10548,-27.13457 -47.02031,2.51737 -0.45414,2.27666 -3.24992,9.15209 -9.86438,8.83489 -5.83023,-0.2796 -6.27407,-11.31136 -2.97641,-22.14792 6.46074,-21.2309 39.80291,-48.85947 63.84617,-17.09603 45.93757,-54.017 128.43819,-141.91623 218.89276,-238.31106 z" + style="display:inline;fill:#8a9bab;fill-opacity:1;stroke:none;stroke-width:1.51092136;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="sssssss" + inkscape:connector-curvature="0" + id="path5899" + d="m 164.41033,501.28694 c 0,134.96586 89.28037,248.33231 309.21473,247.20575 217.71706,-1.11521 307.80052,-112.23989 307.80052,-247.20575 0,-96.34096 -54.83816,-188.77266 -158.98644,-196.44194 -62.18116,-4.57889 -100.76307,28.42083 -149.52119,28.91141 -47.56808,0.4786 -79.52352,-31.65029 -147.26721,-28.87954 -119.78922,4.89943 -161.24041,114.05966 -161.24041,196.41007 z" + style="display:inline;fill:#ecf1f5;fill-opacity:1;stroke:none;stroke-width:1.62539303;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="cscsssc" + inkscape:connector-curvature="0" + id="path6029" + d="m 167.12764,541.06247 c -1.8472,-12.95996 -2.88472,-26.24998 -2.88472,-39.77553 0,-22.09219 2.88362,-43.97881 8.58421,-64.68492 40.44162,-29.67651 108.08468,-52.00242 167.32555,-41.22117 35.02394,6.37401 90.30164,43.89694 76.99739,113.45676 -7.83112,40.94415 -61.06671,49.31438 -94.96833,50.10049 -38.95967,0.9034 -90.29699,-2.69765 -155.0541,-17.87563 z" + style="display:inline;fill:#abb7c5;fill-opacity:1;stroke:none;stroke-width:1.62539303;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="sssssss" + inkscape:connector-curvature="0" + id="path6005" + d="m 384.49599,483.06729 c 1.26483,9.82737 -0.28385,30.34383 -16.63487,29.92206 -19.99028,-0.51567 -10.18467,-30.81298 -36.36126,-30.59574 -25.94038,0.21528 -15.99836,30.92106 -34.53097,31.69735 -19.08341,0.79938 -20.66128,-21.82275 -18.68974,-32.14647 5.58223,-29.23062 28.51996,-41.06296 53.44527,-41.54356 27.17314,-0.52397 49.36773,16.21978 52.77157,42.66636 z" + style="fill:#797c81;fill-opacity:1;stroke:none;stroke-width:0.424281;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + style="display:inline;fill:#abb7c5;fill-opacity:1;stroke:none;stroke-width:1.62539303;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + d="m 778.67367,541.05856 c 1.8472,-12.95996 2.75191,-26.24607 2.75191,-39.77162 0,-22.09219 -2.88362,-43.97881 -8.58421,-64.68492 -40.44162,-29.67651 -108.08468,-52.00242 -167.32555,-41.22117 -35.02394,6.37401 -90.30164,43.89694 -76.99739,113.45676 7.83112,40.94415 61.06671,49.31438 94.96833,50.10049 38.95967,0.9034 90.4298,-2.70156 155.18691,-17.87954 z" + id="path6027" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cscsssc" /> + <path + style="fill:#797c81;fill-opacity:1;stroke:none;stroke-width:0.424281;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + d="m 559.16559,483.06729 c -1.26484,9.82737 0.28385,30.34383 16.63486,29.92206 19.99028,-0.51567 10.18468,-30.81298 36.36127,-30.59574 25.94037,0.21528 15.99835,30.92106 34.53096,31.69735 19.08341,0.79938 20.66127,-21.82275 18.68974,-32.14647 -5.58223,-29.23062 -28.51996,-41.06296 -53.44525,-41.54356 -27.17316,-0.52397 -49.36774,16.21978 -52.77158,42.66636 z" + id="path6010" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssssss" /> + <path + sodipodi:nodetypes="sssss" + inkscape:connector-curvature="0" + id="path6014" + d="m 439.99627,540.8714 c 0,10.79277 14.47309,19.54203 32.32654,19.54203 17.85345,0 32.32653,-8.74926 32.32653,-19.54203 -10e-6,-10.79276 -14.10781,-14.42822 -31.96127,-14.42822 -17.85343,0 -32.69179,3.63546 -32.6918,14.42822 z" + style="fill:#7a7d82;fill-opacity:1;stroke:none;stroke-width:0.34507009;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + transform="matrix(1.3299533,0,0,1.0420847,-252.3233,16.933424)" + sodipodi:nodetypes="sssscsssscs" + inkscape:original-d="m 561.38128,582.67417 c 4.16766,-0.91767 12.20351,2.22573 11.70451,-3.19194 -0.49904,-5.41767 3.22694,-10.0569 -0.43934,-12.36091 -3.71867,-2.33693 -4.66037,-0.70889 -10.62868,-3.03033 -6.7415,-2.62217 -12.89085,-5.33173 -18.0383,-9.27773 -5.14745,3.946 -11.25574,6.65556 -17.99724,9.27773 -5.96831,2.32144 -6.91001,0.6934 -10.62868,3.03033 -3.66628,2.30401 0.0597,6.94324 -0.43934,12.36091 -0.499,5.41767 7.53685,2.27427 11.70451,3.19194 4.16767,0.91767 12.6047,-2.8973 17.27236,-5.97963 4.66766,3.08233 13.32253,6.8973 17.4902,5.97963 z" + inkscape:path-effect="#path-effect6019" + inkscape:connector-curvature="0" + id="path6017" + d="m 561.38128,582.67417 c 2.08275,0.16617 4.19863,0.15523 6.23646,-0.30597 2.03783,-0.46119 4.00308,-1.39624 5.46805,-2.88597 1.62507,-1.65254 2.56385,-3.97206 2.49743,-6.2888 -0.0664,-2.31675 -1.15042,-4.59539 -2.93677,-6.07211 -1.43969,-1.19015 -3.25655,-1.83786 -5.08522,-2.21886 -1.82866,-0.38101 -3.69889,-0.51693 -5.54346,-0.81147 -6.78982,-1.08418 -13.20738,-4.38495 -18.0383,-9.27773 -4.81493,4.89182 -11.2195,8.19343 -17.99724,9.27773 -1.84447,0.29508 -3.7147,0.43095 -5.54339,0.8118 -1.82869,0.38084 -3.64567,1.02831 -5.08529,2.21853 -1.78611,1.47668 -2.86946,3.75542 -2.93562,6.07196 -0.0662,2.31655 0.87229,4.63564 2.49628,6.28895 1.46438,1.49081 3.42913,2.42785 5.46715,2.88979 2.03802,0.46195 4.15458,0.4723 6.23736,0.30215 6.13711,-0.50136 12.1389,-2.57916 17.27236,-5.97963 5.21036,3.40799 11.28399,5.48447 17.4902,5.97963 z" + style="fill:#7a7d82;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + </g> +</svg> diff --git a/web/assets/default_avatars/GoToSocial_icon6.svg b/web/assets/default_avatars/GoToSocial_icon6.svg @@ -0,0 +1,160 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1000px" + height="1000px" + viewBox="0 0 1000 1000" + version="1.1" + id="SVGRoot" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" + sodipodi:docname="GoToSocial_icon6.svg" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> + <defs + id="defs5117"> + <inkscape:path-effect + is_visible="true" + id="path-effect6113" + effect="spiro" /> + <inkscape:path-effect + effect="spiro" + id="path-effect6099" + is_visible="true" /> + <inkscape:path-effect + is_visible="true" + id="path-effect6023" + effect="spiro" /> + <inkscape:path-effect + effect="spiro" + id="path-effect6019" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5760" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5756" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5752" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5748" + is_visible="true" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.35355339" + inkscape:cx="-343.35188" + inkscape:cy="451.98165" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="1920" + inkscape:window-height="1057" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" /> + <metadata + id="metadata5120"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="background" + inkscape:groupmode="layer" + id="layer1" + style="display:inline"> + <rect + style="fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.95512283;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + id="rect6025" + width="1000" + height="1000" + x="0" + y="-1000" + transform="scale(1,-1)" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="sloth" + style="display:inline"> + <g + id="g3345"> + <path + sodipodi:nodetypes="sssccsccssscccs" + inkscape:connector-curvature="0" + id="path6057" + d="m 858.9826,509 c 0,21.30135 0.61819,51.5972 6.701,73.24153 7.01258,24.95272 16.3214,50.02921 6.75447,64.21164 -8.7497,12.97093 -21.25315,18.56008 -44.58836,14.66624 3.93369,101.42215 -77.47601,125.19748 -96.07496,88.79767 C 674.80251,788.46301 600.2861,811.26681 511.5,810.9826 c -93.0785,-0.29795 -169.41132,-24.34907 -226.58487,-64.56 -18.96636,55.61282 -89.53019,15.93142 -94.48786,-66.57373 -18.83753,13.90334 -40.21111,9.34959 -48.24161,-1.87396 -9.87886,-13.80685 -0.11866,-44.10059 6.46153,-66.65226 C 158.90467,576.16815 164.0174,526.56854 164.0174,509 c 0,-144.1474 114.89352,-263.91342 269.97979,-293.76458 -11.39947,-30.68702 33.19193,-42.65736 68.23504,-27.65736 51.70913,-51.5 126.88111,-8.44549 91.59407,29.31477 C 745.54432,249.30521 858.9826,367.38679 858.9826,509 Z" + style="display:inline;fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1.56877995;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="sssssss" + inkscape:connector-curvature="0" + id="path5899" + d="m 221.40138,525.50517 c 0,127.7494 118.50857,234.80637 292.6814,233.98797 167.18781,-0.78557 291.3428,-106.23857 291.3428,-233.98797 0,-91.18972 -51.90603,-178.67921 -150.48563,-185.93843 -58.85641,-4.33406 -95.37539,26.90121 -141.52648,27.36556 -45.02467,0.45301 -75.27149,-29.95799 -139.39301,-27.33539 -113.38424,4.63746 -152.61908,107.96103 -152.61908,185.90826 z" + style="display:inline;fill:#f6e7e0;fill-opacity:0.99460916;stroke:none;stroke-width:1.53848529;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + style="display:inline;fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.53848529;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + d="m 789.95211,601.79814 c 10.07014,-23.96206 15.47347,-49.64233 15.47347,-76.29297 0,-24.59922 -3.77719,-48.92918 -11.22886,-71.48757 -113.05163,-72.24038 -211.71014,-23.66044 -216.29587,55.37861 -0.92941,43.30708 14.85596,73.14738 66.59091,84.14256 40.60772,8.63033 109.03729,1.65487 145.46035,8.25937 z" + id="path6060" + inkscape:connector-curvature="0" + sodipodi:nodetypes="csccsc" /> + <path + sodipodi:nodetypes="sssss" + inkscape:connector-curvature="0" + id="path6014" + d="m 489.46176,567.69891 c 0,9.26255 12.42107,16.77133 27.74324,16.77133 15.32216,0 27.74323,-7.50878 27.74323,-16.77133 -10e-6,-9.26255 -12.10759,-12.38257 -27.42976,-12.38257 -15.32215,0 -28.0567,3.12002 -28.05671,12.38257 z" + style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:0.29614559;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="aasaasa" + inkscape:connector-curvature="0" + id="path6062" + d="m 708.01455,512.02942 c 3.98124,8.47407 4.66411,23.87376 -3.82321,27.82668 -15.14244,7.05251 -13.43948,-27.87381 -42.39106,-26.726 -27.91154,1.10657 -23.83539,31.91957 -38.41145,26.49209 -8.14703,-3.03359 -9.71336,-17.14283 -6.5565,-25.2429 6.70872,-17.21364 23.33215,-30.04549 46.55894,-30.06845 21.25806,-0.021 37.1774,11.87004 44.62328,27.71858 z" + style="display:inline;fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1.49886632;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + sodipodi:nodetypes="csccsc" + inkscape:connector-curvature="0" + id="path6091" + d="m 236.44687,601.79814 c -10.07014,-23.96206 -15.47347,-49.64233 -15.47347,-76.29297 0,-24.59922 3.77719,-48.92918 11.22886,-71.48757 113.05163,-72.24038 211.71014,-23.66044 216.29587,55.37861 0.92941,43.30708 -14.85596,73.14738 -66.59091,84.14256 -40.60772,8.63033 -109.03729,1.65487 -145.46035,8.25937 z" + style="display:inline;fill:#e3ccbe;fill-opacity:1;stroke:none;stroke-width:1.53848529;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" /> + <path + style="display:inline;fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1.49886632;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" + d="m 318.38443,512.02942 c -3.98124,8.47407 -4.66411,23.87376 3.82321,27.82668 15.14244,7.05251 13.43948,-27.87381 42.39106,-26.726 27.91154,1.10657 23.83539,31.91957 38.41145,26.49209 8.14703,-3.03359 9.71336,-17.14283 6.5565,-25.2429 -6.70872,-17.21364 -23.33215,-30.04549 -46.55894,-30.06845 -21.25806,-0.021 -37.1774,11.87004 -44.62328,27.71858 z" + id="path6093" + inkscape:connector-curvature="0" + sodipodi:nodetypes="aasaasa" /> + <path + transform="matrix(0.96767168,0,0,1.0314138,16.012714,-19.869878)" + sodipodi:nodetypes="sscccsscs" + inkscape:original-d="m 581.94888,642.38075 c -0.11685,-5.42216 7.07007,-17.61775 2.82843,-16.79379 -4.24164,0.82396 -9.19339,2.47588 -16.26346,5.3033 -7.74,3.20278 -35.48656,-1.28199 -51.42842,-9.77919 -17.49196,7.64885 -42.65501,12.98197 -50.39501,9.77919 -7.07007,-2.82742 -12.02182,-4.47934 -16.26346,-5.3033 -4.24164,-0.82396 2.94528,11.37163 2.82843,16.79379 -0.11685,5.42215 41.7595,1.3486 63.7987,2.05671 22.03919,-0.70811 65.01164,3.36544 64.89479,-2.05671 z" + inkscape:path-effect="#path-effect6099" + inkscape:connector-curvature="0" + id="path6097" + d="m 581.94888,642.38075 c 2.38253,-2.24081 4.60569,-4.83839 5.47508,-7.99146 0.4347,-1.57654 0.5088,-3.26889 0.081,-4.84729 -0.42785,-1.57841 -1.37681,-3.0332 -2.7276,-3.95504 -1.26545,-0.8636 -2.83258,-1.23063 -4.36364,-1.17554 -1.53106,0.0551 -3.02872,0.51417 -4.40643,1.1843 -2.75543,1.34027 -5.02124,3.48424 -7.49339,5.29454 -7.89483,5.7812 -18.22631,8.11406 -27.83929,6.28614 -9.61298,-1.82792 -18.36718,-7.78995 -23.58913,-16.06533 -5.088,8.13656 -13.65079,14.01754 -23.07148,15.84563 -9.42069,1.82809 -19.56159,-0.42342 -27.32353,-6.06644 -2.47705,-1.80085 -4.7434,-3.94147 -7.49681,-5.28222 -1.3767,-0.67037 -2.8729,-1.13084 -4.40308,-1.18819 -1.53017,-0.0574 -3.09706,0.30649 -4.36357,1.16711 -1.35354,0.91976 -2.30564,2.37417 -2.73564,3.95314 -0.43,1.57896 -0.35707,3.27272 0.0776,4.8504 0.86942,3.15535 3.097,5.75356 5.48643,7.99025 8.48113,7.93899 19.90111,12.66409 31.51218,13.0384 11.61107,0.37431 23.31167,-3.60544 32.28652,-10.98169 9.10954,7.53977 21.01716,11.61649 32.83628,11.24191 11.81911,-0.37458 23.44469,-5.19714 32.05851,-13.29862 z" + style="fill:#c89172;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + </g> +</svg> diff --git a/web/assets/profile.css b/web/assets/profile.css @@ -0,0 +1,90 @@ +main { + background: transparent; +} + +.headerimage img { + width: 100%; + height: 15em; + object-fit: cover; + border-radius: 10px; + } + +.profile { + position: relative; + background: rgb(75, 84, 93); + padding: 2rem; + display: flex; + flex-wrap: wrap; + justify-content: space-around; + gap: 0.5rem; + margin-bottom: 0.2rem; +} + +.profile .basic { + display: flex; + flex-direction: column; + flex: 1 1 25em; + gap: 0.5rem; + } + +.profile .basic a { + position: relative; + z-index: 1; + color: inherit; + text-decoration: none; + } + +.profile .basic .avatar img { + height: 25em; + width: 25em; + object-fit: cover; + border-radius: 10px; + } + +.profile .basic .displayname { + font-weight: bold; + font-size: 1.6rem; + align-self: start; + } + +.profile .detailed { + display: flex; + flex-direction: column; + flex: 1 1 25em; + } + +.profile .detailed h2 { + margin-top: 0; + } + +.profile .detailed .bio { + margin: 0; + } + +.profile .detailed .bio a { + color: #de8957; + text-decoration: underline; + } + +.accountstats { + position: relative; + background: rgb(75, 84, 93); + padding: 0.5rem; + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + gap: 0.5rem; + margin-bottom: 0.2rem; +} + +.accountstats .entry { + background: rgb(89, 99, 110); + padding: 0.5rem; + flex-grow: 1; + text-align: center; + } + +footer + div { + /* something weird from the devstack.. */ + display: none; +} diff --git a/web/gotosocial-styling/templates/profile.css b/web/gotosocial-styling/templates/profile.css @@ -0,0 +1,94 @@ +main { + background: transparent; +} + +.headerimage { + img { + width: 100%; + height: 15em; + object-fit: cover; + border-radius: 10px; + } +} + +.profile { + position: relative; + background: color($bg lightness(-3%)); + padding: 2rem; + display: flex; + flex-wrap: wrap; + justify-content: space-around; + gap: 0.5rem; + margin-bottom: 0.2rem; + + .basic { + display: flex; + flex-direction: column; + flex: 1 1 25em; + gap: 0.5rem; + + a { + position: relative; + z-index: 1; + color: inherit; + text-decoration: none; + } + + .avatar { + img { + height: 25em; + width: 25em; + object-fit: cover; + border-radius: 10px; + } + } + + .displayname { + font-weight: bold; + font-size: 1.6rem; + align-self: start; + } + } + + .detailed { + display: flex; + flex-direction: column; + flex: 1 1 25em; + + h2 { + margin-top: 0; + } + + .bio { + margin: 0; + + a { + color: $acc1; + text-decoration: underline; + } + } + } +} + +.accountstats { + position: relative; + background: color($bg lightness(-3%)); + padding: 0.5rem; + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + gap: 0.5rem; + margin-bottom: 0.2rem; + + .entry { + background: color($bg lightness(+3%)); + padding: 0.5rem; + flex-grow: 1; + text-align: center; + } +} + +footer + div { + /* something weird from the devstack.. */ + display: none; +} diff --git a/web/template/profile.tmpl b/web/template/profile.tmpl @@ -0,0 +1,47 @@ +{{ template "header.tmpl" .}} +<main> + {{ if .account.Header }}<a href="{{.account.Header}}" class="headerimage"><img src="{{.account.Header}}"></a>{{ end }} + <div class="profile"> + <div class="basic"> + <a href="{{.account.URL}}" class="displayname">{{if .account.DisplayName}}{{.account.DisplayName}}{{else}}{{.account.Username}}{{end}}</a> + <a href="{{.account.URL}}" class="username">@{{.account.Username}}</a> + <a href="{{.account.Avatar}}" class="avatar"><img src="{{.account.Avatar}}"></a> + </div> + <div class="detailed"> + <h2>About @{{.account.Username}}</h2> + <div class="bio"> + {{ if .account.Note }}{{ .account.Note | noescape }}{{else}}This GoToSocial user hasn't written a bio yet!{{end}} + </div> + </div> + </div> + <div class="accountstats"> + <div class="entry">Joined {{.account.CreatedAt | timestampShort}}</div> + <div class="entry">Followed by {{.account.FollowersCount}}</div> + <div class="entry">Following {{.account.FollowingCount}}</div> + <div class="entry">Posted {{.account.StatusesCount}}</div> + </div> + <h2>Recent public posts by @{{.account.Username}}</h2> + <div class="thread"> + {{range .statuses}} + <div class="toot expanded"> + {{ template "status.tmpl" .}} + </div> + {{end}} + </div> +</main> +<script> + Array.from(document.getElementsByClassName("spoiler-label")).forEach((label) => { + let checkbox = document.getElementById(label.htmlFor); + function update() { + if(checkbox.checked) { + label.innerHTML = "Show more"; + } else { + label.innerHTML = "Show less"; + } + } + update(); + + label.addEventListener("click", () => {setTimeout(update, 1)}); + }); +</script> +{{ template "footer.tmpl" .}} +\ No newline at end of file