From 0ff52b71f2c0e970b1f0d43793c019bbed93e112 Mon Sep 17 00:00:00 2001
From: tobi <31960611+tsmethurst@users.noreply.github.com>
Date: Wed, 27 Dec 2023 11:23:52 +0100
Subject: [chore] Refactor HTML templates and CSS (#2480)
* [chore] Refactor HTML templates and CSS
* eslint
* ignore "Local"
* rss tests
* fiddle with OG just a tiny bit
* dick around with polls a bit more so SR stops saying "clickable"
* remove break
* oh lord
* don't lazy load avatar
* fix ogmeta tests
* clean up some cruft
* catch remaining calls to c.HTML
* fix error rendering + stack overflow in tag
* allow templating attributes
* fix indent
* set aria-hidden on status complementary content, since it's already present in the label anyway
* tidy up templating calls a little
* try to make styling a bit more consistent + readable
* fix up some remaining CSS issues
* fix up reports
---
internal/api/auth/authorize.go | 30 +++---
internal/api/auth/callback.go | 34 ++++---
internal/api/auth/oob.go | 18 ++--
internal/api/auth/signin.go | 18 ++--
internal/api/model/status.go | 6 ++
internal/api/util/errorhandling.go | 20 ++--
internal/api/util/opengraph.go | 184 ++++++++++++++++++++++++++++++++++++
internal/api/util/opengraph_test.go | 116 +++++++++++++++++++++++
internal/api/util/template.go | 135 ++++++++++++++++++++++++++
9 files changed, 514 insertions(+), 47 deletions(-)
create mode 100644 internal/api/util/opengraph.go
create mode 100644 internal/api/util/opengraph_test.go
create mode 100644 internal/api/util/template.go
(limited to 'internal/api')
diff --git a/internal/api/auth/authorize.go b/internal/api/auth/authorize.go
index 4977ae4f2..e4694de57 100644
--- a/internal/api/auth/authorize.go
+++ b/internal/api/auth/authorize.go
@@ -144,17 +144,25 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
return
}
- // 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{
- "appname": app.Name,
- "appwebsite": app.Website,
- "redirect": redirect,
- "scope": scope,
- "user": acct.Username,
- "instance": instance,
- })
+ // The authorize template will display a form
+ // to the user where they can see some info
+ // 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.
+ page := apiutil.WebPage{
+ Template: "authorize.tmpl",
+ Instance: instance,
+ Extra: map[string]any{
+ "appname": app.Name,
+ "appwebsite": app.Website,
+ "redirect": redirect,
+ "scope": scope,
+ "user": acct.Username,
+ },
+ }
+
+ apiutil.TemplateWebPage(c, page)
}
// AuthorizePOSTHandler should be served as POST at https://example.org/oauth/authorize
diff --git a/internal/api/auth/callback.go b/internal/api/auth/callback.go
index 97b3ae279..d0fa78322 100644
--- a/internal/api/auth/callback.go
+++ b/internal/api/auth/callback.go
@@ -143,11 +143,17 @@ 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,
- "name": claims.Name,
- "preferredUsername": claims.PreferredUsername,
- })
+
+ page := apiutil.WebPage{
+ Template: "finalize.tmpl",
+ Instance: instance,
+ Extra: map[string]any{
+ "name": claims.Name,
+ "preferredUsername": claims.PreferredUsername,
+ },
+ }
+
+ apiutil.TemplateWebPage(c, page)
return
}
s.Set(sessionUserID, user.ID)
@@ -177,12 +183,18 @@ 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,
- "name": form.Name,
- "preferredUsername": form.Username,
- "error": err,
- })
+
+ page := apiutil.WebPage{
+ Template: "finalize.tmpl",
+ Instance: instance,
+ Extra: map[string]any{
+ "name": form.Name,
+ "preferredUsername": form.Username,
+ "error": err,
+ },
+ }
+
+ apiutil.TemplateWebPage(c, page)
}
// check if the username conforms to the spec
diff --git a/internal/api/auth/oob.go b/internal/api/auth/oob.go
index 5953524ab..8c7b1f2a5 100644
--- a/internal/api/auth/oob.go
+++ b/internal/api/auth/oob.go
@@ -21,7 +21,6 @@ import (
"context"
"errors"
"fmt"
- "net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
@@ -101,10 +100,15 @@ 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,
- "user": acct.Username,
- "oobToken": oobToken,
- "scope": scope,
- })
+ page := apiutil.WebPage{
+ Template: "oob.tmpl",
+ Instance: instance,
+ Extra: map[string]any{
+ "user": acct.Username,
+ "oobToken": oobToken,
+ "scope": scope,
+ },
+ }
+
+ apiutil.TemplateWebPage(c, page)
}
diff --git a/internal/api/auth/signin.go b/internal/api/auth/signin.go
index a6b503a83..a8713d05f 100644
--- a/internal/api/auth/signin.go
+++ b/internal/api/auth/signin.go
@@ -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"`
}
@@ -55,10 +55,12 @@ 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,
- })
+ page := apiutil.WebPage{
+ Template: "sign-in.tmpl",
+ Instance: instance,
+ }
+
+ apiutil.TemplateWebPage(c, page)
return
}
@@ -83,7 +85,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)
@@ -129,7 +131,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)
}
diff --git a/internal/api/model/status.go b/internal/api/model/status.go
index 128cd65bb..8ca41c767 100644
--- a/internal/api/model/status.go
+++ b/internal/api/model/status.go
@@ -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:"-"`
}
/*
diff --git a/internal/api/util/errorhandling.go b/internal/api/util/errorhandling.go
index 8bb251040..848beff5b 100644
--- a/internal/api/util/errorhandling.go
+++ b/internal/api/util/errorhandling.go
@@ -50,10 +50,10 @@ func NotFoundHandler(c *gin.Context, instanceGet func(ctx context.Context) (*api
panic(err)
}
- c.HTML(http.StatusNotFound, "404.tmpl", gin.H{
- "instance": instance,
- "requestID": gtscontext.RequestID(ctx),
- })
+ template404Page(c,
+ instance,
+ gtscontext.RequestID(ctx),
+ )
default:
JSON(c, http.StatusNotFound, map[string]string{
"error": errWithCode.Safe(),
@@ -73,12 +73,12 @@ func genericErrorHandler(c *gin.Context, instanceGet func(ctx context.Context) (
panic(err)
}
- c.HTML(errWithCode.Code(), "error.tmpl", gin.H{
- "instance": instance,
- "code": errWithCode.Code(),
- "error": errWithCode.Safe(),
- "requestID": gtscontext.RequestID(ctx),
- })
+ templateErrorPage(c,
+ instance,
+ errWithCode.Code(),
+ errWithCode.Safe(),
+ gtscontext.RequestID(ctx),
+ )
default:
JSON(c, errWithCode.Code(), map[string]string{
"error": errWithCode.Safe(),
diff --git a/internal/api/util/opengraph.go b/internal/api/util/opengraph.go
new file mode 100644
index 000000000..185dc8132
--- /dev/null
+++ b/internal/api/util/opengraph.go
@@ -0,0 +1,184 @@
+// 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
This is my profile, read it and weep! Weep then!
", + Username: "example_account", + }) + + suite.EqualValues(OGMeta{ + Title: "example person!!, @example_account@example.org", + Type: "profile", + Locale: "en", + URL: "https://example.org/@example_account", + SiteName: "example.org", + Description: "content=\"This is my profile, read it and weep! Weep then!\"", + Image: "", + ImageWidth: "", + ImageHeight: "", + ImageAlt: "Avatar for example_account", + ArticlePublisher: "", + ArticleAuthor: "", + ArticleModifiedTime: "", + ArticlePublishedTime: "", + ProfileUsername: "example_account", + }, *accountMeta) +} + +func (suite *OpenGraphTestSuite) TestWithAccountNoNote() { + baseMeta := OGBase(&apimodel.InstanceV1{ + AccountDomain: "example.org", + Languages: []string{"en"}, + }) + + 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", + Type: "profile", + Locale: "en", + URL: "https://example.org/@example_account", + SiteName: "example.org", + Description: "content=\"This GoToSocial user hasn't written a bio yet!\"", + Image: "", + ImageWidth: "", + ImageHeight: "", + ImageAlt: "Avatar for example_account", + ArticlePublisher: "", + ArticleAuthor: "", + ArticleModifiedTime: "", + ArticlePublishedTime: "", + ProfileUsername: "example_account", + }, *accountMeta) +} + +func TestOpenGraphTestSuite(t *testing.T) { + suite.Run(t, &OpenGraphTestSuite{}) +} diff --git a/internal/api/util/template.go b/internal/api/util/template.go new file mode 100644 index 000000000..b8c710c3c --- /dev/null +++ b/internal/api/util/template.go @@ -0,0 +1,135 @@ +// 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