Project edit - editing owners with preview
This commit is contained in:
parent
f706709c34
commit
b591e5f6bd
|
@ -184,11 +184,12 @@ func ThreadToTemplate(t *models.Thread) Thread {
|
|||
}
|
||||
|
||||
func UserAvatarDefaultUrl(theme string) string {
|
||||
// TODO(redesign): Get rid of theme here
|
||||
return hmnurl.BuildTheme("empty-avatar.svg", theme, true)
|
||||
}
|
||||
|
||||
func UserAvatarUrl(u *models.User) string {
|
||||
avatar := ""
|
||||
avatar := UserAvatarDefaultUrl("light")
|
||||
if u != nil && u.AvatarAsset != nil {
|
||||
avatar = hmnurl.BuildS3Asset(u.AvatarAsset.S3Key)
|
||||
}
|
||||
|
|
|
@ -145,18 +145,24 @@
|
|||
<div id="owners_error" class="f6"></div>
|
||||
<div id="owner_list" class="pt3 flex flex-wrap g3">
|
||||
<template id="owner_row">
|
||||
<div class="owner_row flex flex-row items-center bg2 pa2" data-tmpl="root">
|
||||
<div class="owner_row flex flex-row items-center g2 bg3 pa2" data-tmpl="root">
|
||||
<input type="hidden" name="owners" data-tmpl="input" />
|
||||
<span data-tmpl="name"></span>
|
||||
<a class="remove_owner svgicon f7 link-normal pl2" href="javascript:;">{{ svg "close" }}</a>
|
||||
<div class="flex g1 items-center b">
|
||||
<img data-tmpl="avatar" class="avatar avatar-user avatar-small" src="" />
|
||||
<span data-tmpl="name"></span>
|
||||
</div>
|
||||
<a class="remove_owner svgicon f7 link-normal" href="javascript:;">{{ svg "close" }}</a>
|
||||
</div>
|
||||
</template>
|
||||
{{ range .ProjectSettings.Owners }}
|
||||
<div class="owner_row flex flex-row items-center bg2 pa2">
|
||||
<div class="owner_row flex flex-row items-center g2 bg3 pa2">
|
||||
<input type="hidden" name="owners" value="{{ .Username }}" />
|
||||
<span>{{ .Username }}</span>
|
||||
<div class="flex g1 items-center b">
|
||||
<img class="avatar avatar-user avatar-small" src="{{ .AvatarUrl }}" />
|
||||
<span title="{{ .Username }}">{{ .Name }}</span>
|
||||
</div>
|
||||
{{ if (or $.User.IsStaff (ne .ID $.User.ID)) }}
|
||||
<a class="remove_owner svgicon f7 link-normal pl2" href="javascript:;">{{ svg "close" }}</a>
|
||||
<a class="remove_owner svgicon f7 link-normal" href="javascript:;">{{ svg "close" }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
@ -267,11 +273,6 @@
|
|||
<div id="owners_preview_container">
|
||||
<hr class="mv3">
|
||||
<div id="owners_preview" class="flex flex-wrap g2">
|
||||
<!-- TODO(redesign): Actually preview owners -->
|
||||
<div class="flex g1 items-center b">
|
||||
<div class="avatar avatar-user avatar-small"></div>
|
||||
<span>Example User</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TODO(redesign): Preview badges -->
|
||||
|
@ -340,6 +341,7 @@
|
|||
let ownerList = document.querySelector("#owner_list");
|
||||
let ownerTemplate = makeTemplateCloner("owner_row");
|
||||
let ownerPreviewTemplate = makeTemplateCloner("owner_preview");
|
||||
let ownersPreviewContainer = document.querySelector("#owners_preview");
|
||||
|
||||
addOwnerInput.addEventListener("keypress", function(ev) {
|
||||
if (ev.which == 13) {
|
||||
|
@ -385,7 +387,7 @@
|
|||
let result = xhr.response;
|
||||
if (result) {
|
||||
if (result.found) {
|
||||
addOwner(result.canonical);
|
||||
addOwner(result.username, result.name, result.avatarUrl);
|
||||
addOwnerInput.value = "";
|
||||
} else {
|
||||
ownersError.textContent = "Username not found";
|
||||
|
@ -417,21 +419,41 @@
|
|||
updateAddOwnerStyles();
|
||||
}
|
||||
|
||||
function addOwner(username) {
|
||||
function addOwner(username, bestName, avatarUrl) {
|
||||
let ownerEl = ownerTemplate();
|
||||
ownerEl.input.value = username;
|
||||
ownerEl.name.textContent = username;
|
||||
ownerEl.name.textContent = bestName;
|
||||
ownerEl.title = username;
|
||||
ownerEl.avatar.src = avatarUrl;
|
||||
ownerList.appendChild(ownerEl.root);
|
||||
updateAddOwnerStyles();
|
||||
updateOwnersPreview();
|
||||
}
|
||||
|
||||
ownerList.addEventListener("click", function(ev) {
|
||||
if (ev.target.classList.contains("remove_owner")) {
|
||||
ev.target.parentElement.remove();
|
||||
if (ev.target.closest(".remove_owner")) {
|
||||
ev.target.closest(".owner_row").remove();
|
||||
}
|
||||
updateAddOwnerStyles();
|
||||
updateOwnersPreview();
|
||||
});
|
||||
|
||||
function updateOwnersPreview() {
|
||||
let ownerEls = ownerList.querySelectorAll(".owner_row");
|
||||
ownersPreviewContainer.innerHTML = "";
|
||||
for (let i = 0; i < ownerEls.length; ++i) {
|
||||
let avatarUrl = ownerEls[i].querySelector("img").src;
|
||||
let name = ownerEls[i].querySelector("span").textContent;
|
||||
let previewEl = ownerPreviewTemplate();
|
||||
previewEl.avatar.src = avatarUrl;
|
||||
previewEl.name.textContent = name;
|
||||
ownersPreviewContainer.appendChild(previewEl.root);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateOwnersPreview();
|
||||
|
||||
//////////////////////////////
|
||||
// Logo / header management //
|
||||
//////////////////////////////
|
||||
|
|
|
@ -8,50 +8,34 @@ import (
|
|||
"strings"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/db"
|
||||
"git.handmade.network/hmn/hmn/src/hmndata"
|
||||
"git.handmade.network/hmn/hmn/src/models"
|
||||
"git.handmade.network/hmn/hmn/src/oops"
|
||||
"git.handmade.network/hmn/hmn/src/templates"
|
||||
"git.handmade.network/hmn/hmn/src/utils"
|
||||
)
|
||||
|
||||
func APICheckUsername(c *RequestContext) ResponseData {
|
||||
c.Req.ParseForm()
|
||||
usernameArgs, hasUsername := c.Req.Form["username"]
|
||||
found := false
|
||||
canonicalUsername := ""
|
||||
var user *models.User
|
||||
var err error
|
||||
if hasUsername {
|
||||
requestedUsername := usernameArgs[0]
|
||||
found = true
|
||||
c.Perf.StartBlock("SQL", "Fetch user")
|
||||
user, err := db.QueryOne[models.User](c, c.Conn,
|
||||
`
|
||||
SELECT $columns
|
||||
FROM
|
||||
hmn_user
|
||||
WHERE
|
||||
LOWER(hmn_user.username) = LOWER($1)
|
||||
AND status = ANY ($2)
|
||||
`,
|
||||
requestedUsername,
|
||||
[]models.UserStatus{models.UserStatusConfirmed, models.UserStatusApproved},
|
||||
)
|
||||
c.Perf.EndBlock()
|
||||
if err != nil {
|
||||
if errors.Is(err, db.NotFound) {
|
||||
found = false
|
||||
} else {
|
||||
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch user: %s", requestedUsername))
|
||||
}
|
||||
} else {
|
||||
canonicalUsername = user.Username
|
||||
user, err = hmndata.FetchUserByUsername(c, c.Conn, c.CurrentUser, requestedUsername, hmndata.UsersQuery{})
|
||||
if err != nil && !errors.Is(err, db.NotFound) {
|
||||
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch user: %s", requestedUsername))
|
||||
}
|
||||
}
|
||||
|
||||
var res ResponseData
|
||||
addCORSHeaders(c, &res)
|
||||
if found {
|
||||
if user != nil {
|
||||
res.WriteJson(map[string]any{
|
||||
"found": true,
|
||||
"canonical": canonicalUsername,
|
||||
"username": user.Username,
|
||||
"name": user.BestName(),
|
||||
"avatarUrl": templates.UserAvatarUrl(user),
|
||||
}, nil)
|
||||
} else {
|
||||
res.WriteJson(map[string]any{
|
||||
|
|
Loading…
Reference in New Issue