Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] User-selectable preset CSS themes for accounts #2777

Merged
merged 10 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/admin/themes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Themes

Users on your instance can select a theme for their profile from any css files present in the `web/assets/themes` directory.

GoToSocial comes with some theme files already, but you can add more yourself by doing the following:

1. Create a file in `web/assets/themes` called (for example) `new-theme.css`.
2. (Optional) Include the following comment at the top of your theme file to title and describe your theme:
```css
/*
theme-title: My New Theme
theme-description: This is an example theme
*/
```
You can use any text you like for these fields, but bear in mind whatever you write here will appear in the settings panel to help users when selecting a theme, so keep it short and sweet.
3. Fill out your custom CSS in the rest of the file. You can use one of the existing CSS files to guide you. Also see [this page](../user_guide/custom_css.md) for some rough guidelines about how to write accessible CSS.
4. Restart your instance so that the new CSS file is picked up.

!!! info
If you're using Docker for your deployment, you can mount theme files from the host machine into your GoToSocial `web/assets/themes` directory instead, by including entries for them in the `volumes` section of your Docker configuration.

For example, say you've created a theme on your host machine at `~/gotosocial/my-themes/new-theme.css`, you could mount that theme into the GoToSocial Docker container in the following way:

```yaml
volumes:
[.... some other volume entries ...]
- "~/gotosocial/my-themes/new-theme.css:/gotosocial/web/assets/themes/new-theme.css"
```

Bear in mind if you mount an entire directory to `/gotosocial/web/assets/themes` instead of mounting individual theme files, you'll override the default themes.
50 changes: 50 additions & 0 deletions docs/api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,10 @@ definitions:
description: Account has been suspended by our instance.
type: boolean
x-go-name: Suspended
theme:
description: Filename of user-selected CSS theme to include when rendering this account's profile or statuses. Eg., `blurple-light.css`.
type: string
x-go-name: Theme
url:
description: Web location of the account's profile page.
example: https://example.org/@some_user
Expand Down Expand Up @@ -2463,6 +2467,24 @@ definitions:
type: object
x-go-name: Tag
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
theme:
properties:
description:
description: User-facing description of this theme.
type: string
x-go-name: Description
file_name:
description: FileName of this theme in the themes directory.
type: string
x-go-name: FileName
title:
description: User-facing title of this theme.
type: string
x-go-name: Title
title: Theme represents one user-selectable preset CSS theme.
type: object
x-go-name: Theme
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
wellKnownResponse:
description: See https://webfinger.net/
properties:
Expand Down Expand Up @@ -3448,6 +3470,34 @@ paths:
summary: Search for accounts by username and/or display name.
tags:
- accounts
/api/v1/accounts/themes:
get:
operationId: accountThemes
produces:
- application/json
responses:
"200":
description: Array of themes.
schema:
items:
$ref: '#/definitions/theme'
type: array
"400":
description: bad request
"401":
description: unauthorized
"404":
description: not found
"406":
description: not acceptable
"500":
description: internal server error
security:
- OAuth2 Bearer:
- read:accounts
summary: See preset CSS themes available to accounts on this instance.
tags:
- accounts
/api/v1/accounts/update_credentials:
patch:
consumes:
Expand Down
Binary file modified docs/assets/user-settings-profile-info.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 16 additions & 2 deletions docs/user_guide/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ A preview of the image as it will appear on your profile will be shown. If you'r

If you navigate to your profile and refresh the page, your new avatar / header will be shown. It might take a bit longer for the update to federate out to remote instances.

### Select Theme

GoToSocial provides themes for you to choose from for the web view of your profile, to change your profile's appearance and vibe.

To choose a theme, just select it from the profile settings page, and click/tap "Save profile info" at the bottom of the page. When you look at your profile in the web view (you may need to refresh the page), you'll see the new theme applied, and so will anyone else visiting your profile.

!!! tip "Adding more themes"
Instance admins can add more themes by dropping css files into the `web/assets/themes` folder. See the [themes](../admin/themes.md) part of the admin docs for more information.

### Basic Information

#### Display Name
Expand Down Expand Up @@ -109,9 +118,14 @@ Turning on the discoverable flag may take a week or more to propagate; your acco

#### Custom CSS

If enabled on your instance by the instance administrator, [Custom CSS](./custom_css.md) allows you to theme the way your profile looks when visited through a browser.
If enabled on your instance by the instance administrator, custom CSS allows you to further customize the way your profile looks when visited through a browser.

When this setting is not enabled by the instance administrator, the text input box is read-only.
When this setting is not enabled by the instance administrator, the text input box is read-only and custom CSS will not be applied.

See the [Custom CSS](./custom_css.md) page for some tips on writing custom CSS for your profile.

!!! tip
Any custom CSS you add in this box will be applied *after* your selected theme, so you can pick a preset theme that you like and then make your own tweaks!

## Post Settings

Expand Down
4 changes: 4 additions & 0 deletions internal/api/client/accounts/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const (
VerifyPath = BasePath + "/verify_credentials"
MovePath = BasePath + "/move"
AliasPath = BasePath + "/alias"
ThemesPath = BasePath + "/themes"
)

type Module struct {
Expand Down Expand Up @@ -114,4 +115,7 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
// migration handlers
attachHandler(http.MethodPost, AliasPath, m.AccountAliasPOSTHandler)
attachHandler(http.MethodPost, MovePath, m.AccountMovePOSTHandler)

// account themes
attachHandler(http.MethodGet, ThemesPath, m.AccountThemesGETHandler)
}
1 change: 1 addition & 0 deletions internal/api/client/accounts/accountupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest,
form.Source.Language == nil &&
form.Source.StatusContentType == nil &&
form.FieldsAttributes == nil &&
form.Theme == nil &&
form.CustomCSS == nil &&
form.EnableRSS == nil) {
return nil, errors.New("empty form submitted")
Expand Down
2 changes: 1 addition & 1 deletion internal/api/client/accounts/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ import (
// '500':
// description: internal server error
func (m *Module) AccountListsGETHandler(c *gin.Context) {
authed, err := oauth.Authed(c, false, false, false, false)
authed, err := oauth.Authed(c, true, true, true, true)
if err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
return
Expand Down
2 changes: 1 addition & 1 deletion internal/api/client/accounts/statuses.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ import (
// '500':
// description: internal server error
func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
authed, err := oauth.Authed(c, false, false, false, false)
authed, err := oauth.Authed(c, true, true, true, true)
if err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
return
Expand Down
77 changes: 77 additions & 0 deletions internal/api/client/accounts/themesget.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package accounts

import (
"net/http"

"github.com/gin-gonic/gin"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)

// AccountThemesGETHandler swagger:operation GET /api/v1/accounts/themes accountThemes
//
// See preset CSS themes available to accounts on this instance.
//
// ---
// tags:
// - accounts
//
// produces:
// - application/json
//
// security:
// - OAuth2 Bearer:
// - read:accounts
//
// responses:
// '200':
// name: statuses
// description: Array of themes.
// schema:
// type: array
// items:
// "$ref": "#/definitions/theme"
// '400':
// description: bad request
// '401':
// description: unauthorized
// '404':
// description: not found
// '406':
// description: not acceptable
// '500':
// description: internal server error
func (m *Module) AccountThemesGETHandler(c *gin.Context) {
_, err := oauth.Authed(c, true, true, true, true)
if err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
return
}

if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
return
}

// Retrieve available themes.
themes := m.processor.Account().ThemesGet()
apiutil.JSON(c, http.StatusOK, themes)
}
6 changes: 6 additions & 0 deletions internal/api/model/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ type Account struct {
MuteExpiresAt string `json:"mute_expires_at,omitempty"`
// Extra profile information. Shown only if the requester owns the account being requested.
Source *Source `json:"source,omitempty"`
// Filename of user-selected CSS theme to include when rendering this account's profile or statuses. Eg., `blurple-light.css`.
Theme string `json:"theme,omitempty"`
// CustomCSS to include when rendering this account's profile or statuses.
CustomCSS string `json:"custom_css,omitempty"`
// Account has enabled RSS feed.
Expand Down Expand Up @@ -162,7 +164,11 @@ type UpdateCredentialsRequest struct {
FieldsAttributes *[]UpdateField `form:"fields_attributes" json:"-"`
// Profile metadata names and values, parsed from JSON.
JSONFieldsAttributes *map[string]UpdateField `form:"-" json:"fields_attributes"`
// Theme file name to be used when rendering this account's profile or statuses.
// Use empty string to unset.
Theme *string `form:"theme" json:"theme"`
// Custom CSS to be included when rendering this account's profile or statuses.
// Use empty string to unset.
CustomCSS *string `form:"custom_css" json:"custom_css"`
// Enable RSS feed of public toots for this account at /@[username]/feed.rss
EnableRSS *bool `form:"enable_rss" json:"enable_rss"`
Expand Down
32 changes: 32 additions & 0 deletions internal/api/model/theme.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package model

// Theme represents one user-selectable preset CSS theme.
//
// swagger:model theme
type Theme struct {
// User-facing title of this theme.
Title string `json:"title"`

// User-facing description of this theme.
Description string `json:"description"`

// FileName of this theme in the themes directory.
FileName string `json:"file_name"`
}
55 changes: 55 additions & 0 deletions internal/db/bundb/migrations/20240320114447_preset_css_themes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package migrations

import (
"context"
"strings"

"github.com/uptrace/bun"
)

func init() {
up := func(ctx context.Context, db *bun.DB) error {
// Add theme to account settings table.
_, err := db.ExecContext(ctx,
"ALTER TABLE ? ADD COLUMN ? TEXT",
bun.Ident("account_settings"), bun.Ident("theme"),
)
if err != nil {
e := err.Error()
if !(strings.Contains(e, "already exists") ||
strings.Contains(e, "duplicate column name") ||
strings.Contains(e, "SQLSTATE 42701")) {
return err
}
}

return nil
}

down := func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
return nil
})
}

if err := Migrations.Register(up, down); err != nil {
panic(err)
}
}
14 changes: 14 additions & 0 deletions internal/gtsmodel/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,17 @@ type Relationship struct {
Endorsed bool // Are you featuring this user on your profile?
Note string // Your note on this account.
}

// Theme represents a user-selected
// CSS theme for an account.
type Theme struct {
// User-facing title of this theme.
Title string

// User-facing description of this theme.
Description string

// FileName of this theme in the themes
// directory (eg., `light-blurple.css`).
FileName string
}
1 change: 1 addition & 0 deletions internal/gtsmodel/accountsettings.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type AccountSettings struct {
Sensitive *bool `bun:",nullzero,notnull,default:false"` // Set posts from this account to sensitive by default?
Language string `bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
StatusContentType string `bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts).
Theme string `bun:",nullzero"` // Preset CSS theme filename selected by this Account (empty string if nothing set).
tsmethurst marked this conversation as resolved.
Show resolved Hide resolved
CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
EnableRSS *bool `bun:",nullzero,notnull,default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed
HideCollections *bool `bun:",nullzero,notnull,default:false"` // Hide this account's followers/following collections.
Expand Down
Loading