Add profile edit page
This commit is contained in:
parent
8e3999fc3d
commit
81bdc7c705
|
@ -1,10 +1,14 @@
|
||||||
package mastodon
|
package mastodon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -34,6 +38,7 @@ type Account struct {
|
||||||
Moved *Account `json:"moved"`
|
Moved *Account `json:"moved"`
|
||||||
Fields []Field `json:"fields"`
|
Fields []Field `json:"fields"`
|
||||||
Bot bool `json:"bot"`
|
Bot bool `json:"bot"`
|
||||||
|
Source *AccountSource `json:"source"`
|
||||||
Pleroma *AccountPleroma `json:"pleroma"`
|
Pleroma *AccountPleroma `json:"pleroma"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,54 +100,115 @@ type Profile struct {
|
||||||
Source *AccountSource
|
Source *AccountSource
|
||||||
|
|
||||||
// Set the base64 encoded character string of the image.
|
// Set the base64 encoded character string of the image.
|
||||||
Avatar string
|
Avatar *multipart.FileHeader
|
||||||
Header string
|
Header *multipart.FileHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountUpdate updates the information of the current user.
|
// AccountUpdate updates the information of the current user.
|
||||||
func (c *Client) AccountUpdate(ctx context.Context, profile *Profile) (*Account, error) {
|
func (c *Client) AccountUpdate(ctx context.Context, profile *Profile) (*Account, error) {
|
||||||
params := url.Values{}
|
var buf bytes.Buffer
|
||||||
|
mw := multipart.NewWriter(&buf)
|
||||||
if profile.DisplayName != nil {
|
if profile.DisplayName != nil {
|
||||||
params.Set("display_name", *profile.DisplayName)
|
err := mw.WriteField("display_name", *profile.DisplayName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if profile.Note != nil {
|
if profile.Note != nil {
|
||||||
params.Set("note", *profile.Note)
|
err := mw.WriteField("note", *profile.Note)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if profile.Locked != nil {
|
if profile.Locked != nil {
|
||||||
params.Set("locked", strconv.FormatBool(*profile.Locked))
|
err := mw.WriteField("locked", strconv.FormatBool(*profile.Locked))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if profile.Fields != nil {
|
if profile.Fields != nil {
|
||||||
for idx, field := range *profile.Fields {
|
for idx, field := range *profile.Fields {
|
||||||
params.Set(fmt.Sprintf("fields_attributes[%d][name]", idx), field.Name)
|
err := mw.WriteField(fmt.Sprintf("fields_attributes[%d][name]", idx), field.Name)
|
||||||
params.Set(fmt.Sprintf("fields_attributes[%d][value]", idx), field.Value)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = mw.WriteField(fmt.Sprintf("fields_attributes[%d][value]", idx), field.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if profile.Source != nil {
|
|
||||||
if profile.Source.Privacy != nil {
|
|
||||||
params.Set("source[privacy]", *profile.Source.Privacy)
|
|
||||||
}
|
}
|
||||||
if profile.Source.Sensitive != nil {
|
if profile.Avatar != nil {
|
||||||
params.Set("source[sensitive]", strconv.FormatBool(*profile.Source.Sensitive))
|
f, err := profile.Avatar.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
if profile.Source.Language != nil {
|
fname := filepath.Base(profile.Avatar.Filename)
|
||||||
params.Set("source[language]", *profile.Source.Language)
|
part, err := mw.CreateFormFile("avatar", fname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(part, f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if profile.Avatar != "" {
|
if profile.Header != nil {
|
||||||
params.Set("avatar", profile.Avatar)
|
f, err := profile.Header.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
if profile.Header != "" {
|
fname := filepath.Base(profile.Header.Filename)
|
||||||
params.Set("header", profile.Header)
|
part, err := mw.CreateFormFile("header", fname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
_, err = io.Copy(part, f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := mw.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
params := &multipartRequest{Data: &buf, ContentType: mw.FormDataContentType()}
|
||||||
var account Account
|
var account Account
|
||||||
err := c.doAPI(ctx, http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account, nil)
|
err = c.doAPI(ctx, http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &account, nil
|
return &account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) accountDeleteField(ctx context.Context, field string) (*Account, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
mw := multipart.NewWriter(&buf)
|
||||||
|
_, err := mw.CreateFormField(field)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = mw.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
params := &multipartRequest{Data: &buf, ContentType: mw.FormDataContentType()}
|
||||||
|
var account Account
|
||||||
|
err = c.doAPI(ctx, http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AccountDeleteAvatar(ctx context.Context) (*Account, error) {
|
||||||
|
return c.accountDeleteField(ctx, "avatar")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AccountDeleteHeader(ctx context.Context) (*Account, error) {
|
||||||
|
return c.accountDeleteField(ctx, "header")
|
||||||
|
}
|
||||||
|
|
||||||
// GetAccountStatuses return statuses by specified accuont.
|
// GetAccountStatuses return statuses by specified accuont.
|
||||||
func (c *Client) GetAccountStatuses(ctx context.Context, id string, onlyMedia bool, pg *Pagination) ([]*Status, error) {
|
func (c *Client) GetAccountStatuses(ctx context.Context, id string, onlyMedia bool, pg *Pagination) ([]*Status, error) {
|
||||||
var statuses []*Status
|
var statuses []*Status
|
||||||
|
|
|
@ -33,6 +33,11 @@ type Client struct {
|
||||||
config *Config
|
config *Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type multipartRequest struct {
|
||||||
|
Data io.Reader
|
||||||
|
ContentType string
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) doAPI(ctx context.Context, method string, uri string, params interface{}, res interface{}, pg *Pagination) error {
|
func (c *Client) doAPI(ctx context.Context, method string, uri string, params interface{}, res interface{}, pg *Pagination) error {
|
||||||
u, err := url.Parse(c.config.Server)
|
u, err := url.Parse(c.config.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -133,6 +138,12 @@ func (c *Client) doAPI(ctx context.Context, method string, uri string, params in
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ct = mw.FormDataContentType()
|
ct = mw.FormDataContentType()
|
||||||
|
} else if mr, ok := params.(*multipartRequest); ok {
|
||||||
|
req, err = http.NewRequest(method, u.String(), mr.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ct = mr.ContentType
|
||||||
} else {
|
} else {
|
||||||
if method == http.MethodGet && pg != nil {
|
if method == http.MethodGet && pg != nil {
|
||||||
u.RawQuery = pg.toValues().Encode()
|
u.RawQuery = pg.toValues().Encode()
|
||||||
|
|
|
@ -156,6 +156,11 @@ type FiltersData struct {
|
||||||
Filters []*mastodon.Filter
|
Filters []*mastodon.Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProfileData struct {
|
||||||
|
*CommonData
|
||||||
|
User *mastodon.Account
|
||||||
|
}
|
||||||
|
|
||||||
type MuteData struct {
|
type MuteData struct {
|
||||||
*CommonData
|
*CommonData
|
||||||
User *mastodon.Account
|
User *mastodon.Account
|
||||||
|
|
|
@ -33,6 +33,7 @@ const (
|
||||||
SearchPage = "search.tmpl"
|
SearchPage = "search.tmpl"
|
||||||
SettingsPage = "settings.tmpl"
|
SettingsPage = "settings.tmpl"
|
||||||
FiltersPage = "filters.tmpl"
|
FiltersPage = "filters.tmpl"
|
||||||
|
ProfilePage = "profile.tmpl"
|
||||||
MutePage = "mute.tmpl"
|
MutePage = "mute.tmpl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -774,6 +774,55 @@ func (svc *service) FiltersPage(c *client) (err error) {
|
||||||
return svc.renderer.Render(c.rctx, c.w, renderer.FiltersPage, data)
|
return svc.renderer.Render(c.rctx, c.w, renderer.FiltersPage, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svc *service) ProfilePage(c *client) (err error) {
|
||||||
|
u, err := c.GetAccountCurrentUser(c.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Some instances allow more than 4 fields, but make sure that there are
|
||||||
|
// at least 4 fields in the slice because the template depends on it.
|
||||||
|
if u.Source.Fields == nil {
|
||||||
|
u.Source.Fields = new([]mastodon.Field)
|
||||||
|
}
|
||||||
|
for len(*u.Source.Fields) < 4 {
|
||||||
|
*u.Source.Fields = append(*u.Source.Fields, mastodon.Field{})
|
||||||
|
}
|
||||||
|
cdata := svc.cdata(c, "edit profile", 0, 0, "")
|
||||||
|
data := &renderer.ProfileData{
|
||||||
|
CommonData: cdata,
|
||||||
|
User: u,
|
||||||
|
}
|
||||||
|
return svc.renderer.Render(c.rctx, c.w, renderer.ProfilePage, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) ProfileUpdate(c *client, name, bio string, avatar, banner *multipart.FileHeader,
|
||||||
|
fields []mastodon.Field, locked bool) (err error) {
|
||||||
|
// Need to pass empty data to clear fields
|
||||||
|
if len(fields) == 0 {
|
||||||
|
fields = append(fields, mastodon.Field{})
|
||||||
|
}
|
||||||
|
p := &mastodon.Profile{
|
||||||
|
DisplayName: &name,
|
||||||
|
Note: &bio,
|
||||||
|
Avatar: avatar,
|
||||||
|
Header: banner,
|
||||||
|
Fields: &fields,
|
||||||
|
Locked: &locked,
|
||||||
|
}
|
||||||
|
_, err = c.AccountUpdate(c.ctx, p)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) ProfileDelAvatar(c *client) (err error) {
|
||||||
|
_, err = c.AccountDeleteAvatar(c.ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) ProfileDelBanner(c *client) (err error) {
|
||||||
|
_, err = c.AccountDeleteHeader(c.ctx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *service) SingleInstance() (instance string, ok bool) {
|
func (s *service) SingleInstance() (instance string, ok bool) {
|
||||||
if len(s.instance) > 0 {
|
if len(s.instance) > 0 {
|
||||||
instance = s.instance
|
instance = s.instance
|
||||||
|
|
|
@ -2,11 +2,14 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"bloat/mastodon"
|
||||||
"bloat/model"
|
"bloat/model"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
@ -202,6 +205,57 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler {
|
||||||
return s.FiltersPage(c)
|
return s.FiltersPage(c)
|
||||||
}, SESSION, HTML)
|
}, SESSION, HTML)
|
||||||
|
|
||||||
|
profilePage := handle(func(c *client) error {
|
||||||
|
return s.ProfilePage(c)
|
||||||
|
}, SESSION, HTML)
|
||||||
|
|
||||||
|
profileUpdate := handle(func(c *client) error {
|
||||||
|
name := c.r.FormValue("name")
|
||||||
|
bio := c.r.FormValue("bio")
|
||||||
|
var avatar, banner *multipart.FileHeader
|
||||||
|
if f := c.r.MultipartForm.File["avatar"]; len(f) > 0 {
|
||||||
|
avatar = f[0]
|
||||||
|
}
|
||||||
|
if f := c.r.MultipartForm.File["banner"]; len(f) > 0 {
|
||||||
|
banner = f[0]
|
||||||
|
}
|
||||||
|
var fields []mastodon.Field
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
n := c.r.FormValue(fmt.Sprintf("field-name-%d", i))
|
||||||
|
v := c.r.FormValue(fmt.Sprintf("field-value-%d", i))
|
||||||
|
if len(n) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f := mastodon.Field{Name: n, Value: v}
|
||||||
|
fields = append(fields, f)
|
||||||
|
}
|
||||||
|
locked := c.r.FormValue("locked") == "true"
|
||||||
|
err := s.ProfileUpdate(c, name, bio, avatar, banner, fields, locked)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.redirect("/")
|
||||||
|
return nil
|
||||||
|
}, CSRF, HTML)
|
||||||
|
|
||||||
|
profileDelAvatar := handle(func(c *client) error {
|
||||||
|
err := s.ProfileDelAvatar(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.redirect(c.r.FormValue("referrer"))
|
||||||
|
return nil
|
||||||
|
}, CSRF, HTML)
|
||||||
|
|
||||||
|
profileDelBanner := handle(func(c *client) error {
|
||||||
|
err := s.ProfileDelBanner(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.redirect(c.r.FormValue("referrer"))
|
||||||
|
return nil
|
||||||
|
}, CSRF, HTML)
|
||||||
|
|
||||||
signin := handle(func(c *client) error {
|
signin := handle(func(c *client) error {
|
||||||
instance := c.r.FormValue("instance")
|
instance := c.r.FormValue("instance")
|
||||||
url, sess, err := s.NewSession(c, instance)
|
url, sess, err := s.NewSession(c, instance)
|
||||||
|
@ -682,6 +736,10 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler {
|
||||||
r.HandleFunc("/search", searchPage).Methods(http.MethodGet)
|
r.HandleFunc("/search", searchPage).Methods(http.MethodGet)
|
||||||
r.HandleFunc("/settings", settingsPage).Methods(http.MethodGet)
|
r.HandleFunc("/settings", settingsPage).Methods(http.MethodGet)
|
||||||
r.HandleFunc("/filters", filtersPage).Methods(http.MethodGet)
|
r.HandleFunc("/filters", filtersPage).Methods(http.MethodGet)
|
||||||
|
r.HandleFunc("/profile", profilePage).Methods(http.MethodGet)
|
||||||
|
r.HandleFunc("/profile", profileUpdate).Methods(http.MethodPost)
|
||||||
|
r.HandleFunc("/profile/delavatar", profileDelAvatar).Methods(http.MethodPost)
|
||||||
|
r.HandleFunc("/profile/delbanner", profileDelBanner).Methods(http.MethodPost)
|
||||||
r.HandleFunc("/signin", signin).Methods(http.MethodPost)
|
r.HandleFunc("/signin", signin).Methods(http.MethodPost)
|
||||||
r.HandleFunc("/oauth_callback", oauthCallback).Methods(http.MethodGet)
|
r.HandleFunc("/oauth_callback", oauthCallback).Methods(http.MethodGet)
|
||||||
r.HandleFunc("/post", post).Methods(http.MethodPost)
|
r.HandleFunc("/post", post).Methods(http.MethodPost)
|
||||||
|
|
|
@ -163,15 +163,14 @@ textarea {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
font-family: initial;
|
font-family: initial;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-content {
|
.post-content {
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#css {
|
#css, #bio {
|
||||||
box-sizing: border-box;
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,6 +433,10 @@ img.emoji {
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-edit-link {
|
||||||
|
font-size: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
.user-list-item {
|
.user-list-item {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
margin: 0 0 4px 0;
|
margin: 0 0 4px 0;
|
||||||
|
@ -589,6 +592,41 @@ kbd {
|
||||||
color: #789922;
|
color: #789922;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-form {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-form-field {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar {
|
||||||
|
height: 96px;
|
||||||
|
width: 96px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-banner {
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-label,
|
||||||
|
.profile-delete,
|
||||||
|
.profile-field,
|
||||||
|
.profile-field input {
|
||||||
|
margin: 0 0 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-form input[type=text] {
|
||||||
|
width: 320px;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bio {
|
||||||
|
width: 644px;
|
||||||
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
background-color: #222222;
|
background-color: #222222;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
|
@ -599,7 +637,7 @@ kbd {
|
||||||
color: #81a2be;
|
color: #81a2be;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark textarea {
|
.dark .post-content {
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
border: 1px solid #444444;
|
border: 1px solid #444444;
|
||||||
color: #eaeaea;
|
color: #eaeaea;
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
<a class="nav-link" href="/user/{{.User.ID}}" accesskey="0" title="User profile (0)">
|
<a class="nav-link" href="/user/{{.User.ID}}" accesskey="0" title="User profile (0)">
|
||||||
<span class="status-uname">@{{.User.Acct}}</span>
|
<span class="status-uname">@{{.User.Acct}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="profile-edit-link" href="/profile" title="edit profile" target="_top">
|
||||||
|
edit
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-info-details-nav">
|
<div class="user-info-details-nav">
|
||||||
<a class="nav-link" href="/timeline/home" accesskey="1" title="Home timeline (1)">home</a>
|
<a class="nav-link" href="/timeline/home" accesskey="1" title="Home timeline (1)">home</a>
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
{{with .Data}}
|
||||||
|
{{template "header.tmpl" (WithContext .CommonData $.Ctx)}}
|
||||||
|
<div class="page-title"> Edit Profile </div>
|
||||||
|
|
||||||
|
<form class="profile-form" action="/profile" method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
|
||||||
|
<input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}">
|
||||||
|
<div class="profile-form-field">
|
||||||
|
<div class="block-label">
|
||||||
|
<label for="avatar">Avatar</label> -
|
||||||
|
<input class="btn-link" type="submit" formaction="/profile/delavatar" formmethod="POST" value="delete">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="{{.User.Avatar}}" target="_blank">
|
||||||
|
<img class="profile-avatar" src="{{.User.Avatar}}" alt="profile-avatar" height="96">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div><input id="avatar" name="avatar" type="file"></div>
|
||||||
|
</div>
|
||||||
|
<div class="profile-form-field">
|
||||||
|
<div class="block-label">
|
||||||
|
<label for="banner">Banner</label> -
|
||||||
|
<input class="btn-link" type="submit" formaction="/profile/delbanner" formmethod="POST" value="delete">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="{{.User.Header}}" target="_blank">
|
||||||
|
<img class="profile-banner" src="{{.User.Header}}" alt="profile-banner" height="120">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<input id="banner" name="banner" type="file">
|
||||||
|
</div>
|
||||||
|
<div class="profile-form-field">
|
||||||
|
<div class="block-label"><label for="name">Name</label></div>
|
||||||
|
<div><input id="name" name="name" type="text" value="{{.User.DisplayName}}"></div>
|
||||||
|
</div>
|
||||||
|
<div class="profile-form-field">
|
||||||
|
<div class="block-label"><label for="bio">Bio</label></div>
|
||||||
|
<textarea id="bio" name="bio" cols="80" rows="8">{{.User.Source.Note}}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="profile-form-field">
|
||||||
|
<div class="block-label"><label>Metadata</label></div>
|
||||||
|
{{range $i, $f := .User.Source.Fields}}
|
||||||
|
<div class="profile-field">
|
||||||
|
<input id="field-name-{{$i}}" name="field-name-{{$i}}" type="text" value="{{$f.Name}}" placeholder="name">
|
||||||
|
<input id="field-value-{{$i}}" name="field-value-{{$i}}" type="text" value="{{$f.Value}}" placeholder="value">
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="profile-form-field">
|
||||||
|
<input id="locked" name="locked" type="checkbox" value="true" {{if .User.Locked}}checked{{end}}>
|
||||||
|
<label for="locked">Require manual approval of follow requests</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit"> Save </button>
|
||||||
|
<button type="reset"> Reset </button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{template "footer.tmpl"}}
|
||||||
|
{{end}}
|
Loading…
Reference in New Issue