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

[chore] Refactor HTML templates and CSS #2480

Merged
merged 20 commits into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ builds:
- static_build
- kvformat
- timetzdata
- >-
{{ if and (index .Env "DEBUG") (.Env.DEBUG) }}debugenv{{ end }}
env:
- CGO_ENABLED=0
goos:
Expand Down
7 changes: 4 additions & 3 deletions internal/api/auth/authorize.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,15 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
// the authorize template will display a form to the user where they can get some information
// about the app that's trying to authorize, and the scope of the request.
// They can then approve it if it looks OK to them, which will POST to the AuthorizePOSTHandler
c.HTML(http.StatusOK, "authorize.tmpl", gin.H{
extra := map[string]any{
"appname": app.Name,
"appwebsite": app.Website,
"redirect": redirect,
"scope": scope,
"user": acct.Username,
"instance": instance,
})
}

apiutil.TemplatePage(c, "authorize.tmpl", instance, nil, nil, nil, extra)
}

// AuthorizePOSTHandler should be served as POST at https://example.org/oauth/authorize
Expand Down
16 changes: 10 additions & 6 deletions internal/api/auth/callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,13 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {
apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1)
return
}
c.HTML(http.StatusOK, "finalize.tmpl", gin.H{
"instance": instance,

extra := map[string]any{
"name": claims.Name,
"preferredUsername": claims.PreferredUsername,
})
}

apiutil.TemplatePage(c, "finalize.tmpl", instance, nil, nil, nil, extra)
return
}
s.Set(sessionUserID, user.ID)
Expand Down Expand Up @@ -177,12 +179,14 @@ func (m *Module) FinalizePOSTHandler(c *gin.Context) {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
c.HTML(http.StatusOK, "finalize.tmpl", gin.H{
"instance": instance,

extra := map[string]any{
"name": form.Name,
"preferredUsername": form.Username,
"error": err,
})
}

apiutil.TemplatePage(c, "finalize.tmpl", instance, nil, nil, nil, extra)
}

// check if the username conforms to the spec
Expand Down
8 changes: 4 additions & 4 deletions internal/api/auth/oob.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"context"
"errors"
"fmt"
"net/http"

"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -101,10 +100,11 @@ func (m *Module) OobHandler(c *gin.Context) {
// we're done with the session now, so just clear it out
m.clearSession(s)

c.HTML(http.StatusOK, "oob.tmpl", gin.H{
"instance": instance,
extra := map[string]any{
"user": acct.Username,
"oobToken": oobToken,
"scope": scope,
})
}

apiutil.TemplatePage(c, "oob.tmpl", instance, nil, nil, nil, extra)
}
13 changes: 5 additions & 8 deletions internal/api/auth/signin.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import (
"golang.org/x/crypto/bcrypt"
)

// login just wraps a form-submitted username (we want an email) and password
type login struct {
// signIn just wraps a form-submitted username (we want an email) and password
type signIn struct {
Email string `form:"username"`
Password string `form:"password"`
}
Expand All @@ -55,10 +55,7 @@ func (m *Module) SignInGETHandler(c *gin.Context) {
return
}

// no idp provider, use our own funky little sign in page
c.HTML(http.StatusOK, "sign-in.tmpl", gin.H{
"instance": instance,
})
apiutil.TemplatePage(c, "sign-in.tmpl", instance, nil, nil, nil, nil)
return
}

Expand All @@ -83,7 +80,7 @@ func (m *Module) SignInGETHandler(c *gin.Context) {
func (m *Module) SignInPOSTHandler(c *gin.Context) {
s := sessions.Default(c)

form := &login{}
form := &signIn{}
if err := c.ShouldBind(form); err != nil {
m.clearSession(s)
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGetV1)
Expand Down Expand Up @@ -129,7 +126,7 @@ func (m *Module) ValidatePassword(ctx context.Context, email string, password st
}

if err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte(password)); err != nil {
err := fmt.Errorf("password hash didn't match for user %s during login attempt: %s", user.Email, err)
err := fmt.Errorf("password hash didn't match for user %s during sign in attempt: %s", user.Email, err)
return incorrectPassword(err)
}

Expand Down
6 changes: 6 additions & 0 deletions internal/api/model/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ type Status struct {
//
// swagger:ignore
WebPollOptions []WebPollOption `json:"-"`

// Status is from a local account.
// Always false for non-web statuses.
//
// swagger:ignore
Local bool `json:"-"`
}

/*
Expand Down
6 changes: 2 additions & 4 deletions internal/api/util/errorhandling.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ func NotFoundHandler(c *gin.Context, instanceGet func(ctx context.Context) (*api
panic(err)
}

c.HTML(http.StatusNotFound, "404.tmpl", gin.H{
tsmethurst marked this conversation as resolved.
Show resolved Hide resolved
"instance": instance,
TemplatePage(c, "error.tmpl", instance, nil, nil, nil, map[string]any{
"requestID": gtscontext.RequestID(ctx),
})
default:
Expand All @@ -73,8 +72,7 @@ func genericErrorHandler(c *gin.Context, instanceGet func(ctx context.Context) (
panic(err)
}

c.HTML(errWithCode.Code(), "error.tmpl", gin.H{
"instance": instance,
TemplatePage(c, "error.tmpl", instance, nil, nil, nil, map[string]any{
"code": errWithCode.Code(),
"error": errWithCode.Safe(),
"requestID": gtscontext.RequestID(ctx),
Expand Down
56 changes: 30 additions & 26 deletions internal/web/opengraph.go → internal/api/util/opengraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// 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
package util

import (
"html"
Expand All @@ -28,10 +28,10 @@ import (

const maxOGDescriptionLength = 300

// ogMeta represents supported OpenGraph Meta tags
// OGMeta represents supported OpenGraph Meta tags
//
// see eg https://ogp.me/
type ogMeta struct {
type OGMeta struct {
// vanilla og tags
Title string // og:title
Type string // og:type
Expand All @@ -56,23 +56,23 @@ type ogMeta struct {
ProfileUsername string // profile:username
}

// ogBase returns an *ogMeta suitable for serving at
// OGBase returns an *ogMeta suitable for serving at
// the base root of an instance. It also serves as a
// foundation for building account / status ogMeta on
// top of.
func ogBase(instance *apimodel.InstanceV1) *ogMeta {
func OGBase(instance *apimodel.InstanceV1) *OGMeta {
var locale string
if len(instance.Languages) > 0 {
locale = instance.Languages[0]
}

og := &ogMeta{
og := &OGMeta{
Title: text.SanitizeToPlaintext(instance.Title) + " - GoToSocial",
Type: "website",
Locale: locale,
URL: instance.URI,
SiteName: instance.AccountDomain,
Description: parseDescription(instance.ShortDescription),
Description: ParseDescription(instance.ShortDescription),

Image: instance.Thumbnail,
ImageAlt: instance.ThumbnailDescription,
Expand All @@ -81,15 +81,15 @@ func ogBase(instance *apimodel.InstanceV1) *ogMeta {
return og
}

// withAccount uses the given account to build an ogMeta
// WithAccount uses the given account to build an ogMeta
// struct specific to that account. It's suitable for serving
// at account profile pages.
func (og *ogMeta) withAccount(account *apimodel.Account) *ogMeta {
og.Title = parseTitle(account, og.SiteName)
func (og *OGMeta) WithAccount(account *apimodel.Account) *OGMeta {
og.Title = AccountTitle(account, og.SiteName)
og.Type = "profile"
og.URL = account.URL
if account.Note != "" {
og.Description = parseDescription(account.Note)
og.Description = ParseDescription(account.Note)
} else {
og.Description = `content="This GoToSocial user hasn't written a bio yet!"`
}
Expand All @@ -102,21 +102,21 @@ func (og *ogMeta) withAccount(account *apimodel.Account) *ogMeta {
return og
}

// withStatus uses the given status to build an ogMeta
// WithStatus uses the given status to build an ogMeta
// struct specific to that status. It's suitable for serving
// at status pages.
func (og *ogMeta) withStatus(status *apimodel.Status) *ogMeta {
og.Title = "Post by " + parseTitle(status.Account, og.SiteName)
func (og *OGMeta) WithStatus(status *apimodel.Status) *OGMeta {
og.Title = "Post by " + AccountTitle(status.Account, og.SiteName)
og.Type = "article"
if status.Language != nil {
og.Locale = *status.Language
}
og.URL = status.URL
switch {
case status.SpoilerText != "":
og.Description = parseDescription("CW: " + status.SpoilerText)
og.Description = ParseDescription("CW: " + status.SpoilerText)
case status.Text != "":
og.Description = parseDescription(status.Text)
og.Description = ParseDescription(status.Text)
default:
og.Description = og.Title
}
Expand Down Expand Up @@ -147,34 +147,38 @@ func (og *ogMeta) withStatus(status *apimodel.Status) *ogMeta {
return og
}

// parseTitle parses a page title from account and accountDomain
func parseTitle(account *apimodel.Account, accountDomain string) string {
// AccountTitle parses a page title from account and accountDomain
func AccountTitle(account *apimodel.Account, accountDomain string) string {
user := "@" + account.Acct + "@" + accountDomain

if len(account.DisplayName) == 0 {
return user
}

return account.DisplayName + " (" + user + ")"
return account.DisplayName + ", " + user
}

// parseDescription returns a string description which is
// ParseDescription returns a string description which is
// safe to use as a template.HTMLAttr inside templates.
func parseDescription(in string) string {
func ParseDescription(in string) string {
i := text.SanitizeToPlaintext(in)
i = strings.ReplaceAll(i, "\n", " ")
i = strings.Join(strings.Fields(i), " ")
i = html.EscapeString(i)
i = strings.ReplaceAll(i, `\`, "&bsol;")
i = trim(i, maxOGDescriptionLength)
i = truncate(i, maxOGDescriptionLength)
return `content="` + i + `"`
}

// trim strings trim s to specified length
func trim(s string, length int) string {
if len(s) < length {
// truncate trims given string to
// specified length (in runes).
func truncate(s string, l int) string {
r := []rune(s)
if len(r) < l {
// No need
// to trim.
return s
}

return s[:length]
return string(r[:l]) + "..."
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// 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
package util

import (
"fmt"
Expand All @@ -40,27 +40,27 @@ func (suite *OpenGraphTestSuite) TestParseDescription() {
for _, tt := range tests {
tt := tt
suite.Run(tt.name, func() {
suite.Equal(fmt.Sprintf("content=\"%s\"", tt.exp), parseDescription(tt.in))
suite.Equal(fmt.Sprintf("content=\"%s\"", tt.exp), ParseDescription(tt.in))
})
}
}

func (suite *OpenGraphTestSuite) TestWithAccountWithNote() {
baseMeta := ogBase(&apimodel.InstanceV1{
baseMeta := OGBase(&apimodel.InstanceV1{
AccountDomain: "example.org",
Languages: []string{"en"},
})

accountMeta := baseMeta.withAccount(&apimodel.Account{
accountMeta := baseMeta.WithAccount(&apimodel.Account{
Acct: "example_account",
DisplayName: "example person!!",
URL: "https://example.org/@example_account",
Note: "<p>This is my profile, read it and weep! Weep then!</p>",
Username: "example_account",
})

suite.EqualValues(ogMeta{
Title: "example person!! (@example_account@example.org)",
suite.EqualValues(OGMeta{
Title: "example person!!, @example_account@example.org",
Type: "profile",
Locale: "en",
URL: "https://example.org/@example_account",
Expand All @@ -79,21 +79,21 @@ func (suite *OpenGraphTestSuite) TestWithAccountWithNote() {
}

func (suite *OpenGraphTestSuite) TestWithAccountNoNote() {
baseMeta := ogBase(&apimodel.InstanceV1{
baseMeta := OGBase(&apimodel.InstanceV1{
AccountDomain: "example.org",
Languages: []string{"en"},
})

accountMeta := baseMeta.withAccount(&apimodel.Account{
accountMeta := baseMeta.WithAccount(&apimodel.Account{
Acct: "example_account",
DisplayName: "example person!!",
URL: "https://example.org/@example_account",
Note: "", // <- empty
Username: "example_account",
})

suite.EqualValues(ogMeta{
Title: "example person!! (@example_account@example.org)",
suite.EqualValues(OGMeta{
Title: "example person!!, @example_account@example.org",
Type: "profile",
Locale: "en",
URL: "https://example.org/@example_account",
Expand Down