From 3edfbe6d57ee0b3ef66347dca6707633e68bf218 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski <janne@kuschku.de> Date: Fri, 22 Apr 2022 18:29:09 +0200 Subject: [PATCH] feat: implement pagination --- .gitignore | 1 + assets/js/page_image_detail.js | 41 ++++++++-------- assets/js/page_upload.js | 48 ++++++++---------- assets/sass/_navigation.sass | 15 +----- assets/sass/_page_image_list.sass | 40 +++++++++------ assets/sass/_pagination.sass | 24 +++++++++ assets/sass/style.sass | 21 ++++++++ errors.go | 47 ++++++++++++++++++ page_image_detail.go | 42 ++++++++-------- page_image_list.go | 70 +++++++++++++++++++++----- page_index.go | 6 +-- page_upload.go | 63 ++++++------------------ templates/_base.html | 3 +- templates/_footer.html | 3 +- templates/_header.html | 3 +- templates/_navigation.html | 37 +++++++------- templates/error.html | 18 +++++++ templates/image_detail.html | 81 ++++++++++++++++--------------- templates/image_list.html | 25 +++++++++- util.go | 17 +++++-- 20 files changed, 374 insertions(+), 231 deletions(-) create mode 100644 assets/sass/_pagination.sass create mode 100644 errors.go create mode 100644 templates/error.html diff --git a/.gitignore b/.gitignore index e439b39..4346d53 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /vendor/ /node_modules/ /cli/ +/assets/css /imghost diff --git a/assets/js/page_image_detail.js b/assets/js/page_image_detail.js index 0bca43c..8b992af 100644 --- a/assets/js/page_image_detail.js +++ b/assets/js/page_image_detail.js @@ -1,14 +1,3 @@ -function postData(url, data) { - return fetch(url, { - body: data, - cache: 'no-cache', - credentials: 'same-origin', - method: 'POST', - mode: 'cors', - redirect: 'follow' - }).then(response => response.json()) -} - const fakeTitle = document.querySelector(".title.fake-input[contenteditable]"); const fakeDescription = document.querySelector(".description.fake-input[contenteditable]"); @@ -22,16 +11,24 @@ let lastTimeOut = null; let hasChanged = false; let isSaving = false; -const doSave = () => { - const data = new FormData(document.forms.namedItem("upload")); - data.append("from_js", "true"); - save.value = "Saving…"; - hasChanged = false; - isSaving = true; - postData(location.href, data).then((json) => { - save.value = "Saved"; - isSaving = false; - }) +const doSave = async () => { + const data = new FormData(document.forms.namedItem("upload")); + data.append("from_js", "true"); + save.value = "Saving…"; + hasChanged = false; + isSaving = true; + const response = await fetch(location.href, { + body: data, + cache: 'no-cache', + credentials: 'same-origin', + method: 'POST', + mode: 'cors', + redirect: 'follow' + }); + if (response.ok) { + save.value = "Saved"; + isSaving = false; + } }; const scheduleSave = () => { @@ -92,4 +89,4 @@ window.addEventListener("beforeunload", (e) => { e.returnValue = message; return message; } -}); \ No newline at end of file +}); diff --git a/assets/js/page_upload.js b/assets/js/page_upload.js index 33b5b67..d656d24 100644 --- a/assets/js/page_upload.js +++ b/assets/js/page_upload.js @@ -1,24 +1,11 @@ -function postData(url, data) { - return fetch(url, { - body: data, - cache: 'no-cache', - credentials: 'same-origin', - method: 'POST', - mode: 'cors', - redirect: 'follow' - }).then(response => response.json()) -} - const page = document.querySelector(".page.upload"); -const form = document.querySelector("form.upload"); const element = document.querySelector("form.upload input[type=file]"); const results = document.querySelector(".uploading-images .images"); -const sidebar = document.querySelector(".uploading-images .sidebar"); element.addEventListener("change", () => { page.classList.add("submitted"); for (let file of element.files) { const reader = new FileReader(); - reader.addEventListener("load", (e) => { + reader.addEventListener("load", async (e) => { const dataUrl = e.target.result; const image_container = document.createElement("div"); @@ -61,21 +48,26 @@ element.addEventListener("change", () => { const data = new FormData(); data.append("file", file, file.name); - postData("/upload/", data).then((json) => { - image_container.classList.remove("uploading"); - if (json.success) { - image_link.href = "/" + json.id + ".png"; - image.src = "/" + json.id + ".png"; - } else { - const image_error = document.createElement("div"); - image_error.classList.add("alert", "error"); - image_error.innerText = JSON.stringify(json.errors); - image_container.insertBefore(image_error, image_description); - } - - console.log(json); + const response = await fetch("/upload/", { + body: data, + cache: 'no-cache', + credentials: 'same-origin', + method: 'POST', + mode: 'cors', + redirect: 'follow' }); + image_container.classList.remove("uploading"); + if (response.ok) { + image_link.href = "/" + json.id + ".png"; + image.src = "/" + json.id + ".png"; + } else { + const image_error = document.createElement("div"); + image_error.classList.add("alert", "error"); + image_error.innerText = JSON.stringify(json.errors); + image_container.insertBefore(image_error, image_description); + console.log(json); + } }); reader.readAsDataURL(file); } -}); \ No newline at end of file +}); diff --git a/assets/sass/_navigation.sass b/assets/sass/_navigation.sass index a0e18dd..191378d 100644 --- a/assets/sass/_navigation.sass +++ b/assets/sass/_navigation.sass @@ -1,4 +1,4 @@ -nav +nav.navigation position: sticky top: 0 background: #333333 @@ -11,12 +11,8 @@ nav margin: 0 auto height: 56px align-items: center - li, li > * - color: #282828 - text-decoration: none li display: block - line-height: 24px &.images, &.albums @media (max-width: 640px) display: none @@ -36,12 +32,3 @@ nav margin-left: 0 &:last-child margin-right: 0 - &:not(.title) a - display: block - background: #FFC107 - padding: 4px 16px - border-radius: 2px - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) - &:hover, &:focus - background: #FFD54F - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) \ No newline at end of file diff --git a/assets/sass/_page_image_list.sass b/assets/sass/_page_image_list.sass index 3dc8cfc..2a91210 100644 --- a/assets/sass/_page_image_list.sass +++ b/assets/sass/_page_image_list.sass @@ -1,32 +1,37 @@ .page.image.list - display: flex max-width: 1024px - margin: 0 auto - align-items: start - flex-wrap: wrap + margin: 8px auto 48px + display: grid + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)) + grid-gap: 8px + align-self: stretch + width: calc(100% - 16px) + .image padding: 8px - margin: 8px position: relative box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) transition: all 200ms text-decoration: none - width: 160px background: #333333 border-radius: 2px + will-change: transform + &:hover, &:focus - margin-top: 4px + transform: translate(0, 4px) box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4) .image-container display: flex - justify-content: center - align-content: center - justify-items: center - align-items: center + justify-content: stretch + align-content: stretch + justify-items: stretch + align-items: stretch flex-direction: column - height: 160px - width: 160px background: #000000 + + img + aspect-ratio: 1 + object-fit: contain .info display: block z-index: 1 @@ -34,7 +39,14 @@ line-height: 1.25 font-size: 10pt padding-top: 12px + p white-space: nowrap text-overflow: ellipsis - overflow: hidden \ No newline at end of file + overflow: hidden + + &.title + font-weight: 600 + + span.placeholder + opacity: 0.4 diff --git a/assets/sass/_pagination.sass b/assets/sass/_pagination.sass new file mode 100644 index 0000000..c0d1733 --- /dev/null +++ b/assets/sass/_pagination.sass @@ -0,0 +1,24 @@ +ul.pagination + display: flex + flex-direction: row + background: #333 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) + padding: 4px 8px + color: #fff + justify-content: space-between + grid-column-start: 1 + grid-column-end: -1 + max-width: 480px + justify-self: center + width: 100% + + li.page + appearance: none + list-style: none + margin: 4px + padding: 0 + line-height: 24px + + &.current + display: block + padding: 4px 16px diff --git a/assets/sass/style.sass b/assets/sass/style.sass index 1017499..3a4d9d5 100644 --- a/assets/sass/style.sass +++ b/assets/sass/style.sass @@ -29,7 +29,28 @@ body width: 100% max-width: 640px +.button + display: block + background: #FFC107 + padding: 4px 16px + border-radius: 2px + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) + color: #282828 + text-decoration: none + line-height: 24px + cursor: pointer + + &:hover, &:focus + background: #FFD54F + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) + + &[aria-disabled=true], &:disabled + cursor: default + background: #838383 + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.1) + @import "navigation" @import "page_upload" @import "page_image_detail" @import "page_image_list" +@import "pagination" diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..29c96ff --- /dev/null +++ b/errors.go @@ -0,0 +1,47 @@ +package main + +import ( + "log" + "net/http" + "net/url" +) + +type ErrorData struct { + Code int + User UserInfo + URL *url.URL + Error error +} + +type errorDto struct { + Path string `json:"path"` + Error string `json:"error"` +} + +func formatError(w http.ResponseWriter, data ErrorData, format string) { + if data.Code != 0 { + data.Code = 500 + } + log.Printf( + "A type %d error occured for user %s while accessing %s: %s", + data.Code, + data.User.Id, + data.URL.Path, + data.Error.Error(), + ) + w.WriteHeader(data.Code) + if format == "html" { + if err := formatTemplate(w, "error.html", data); err != nil { + log.Printf("Error while serving html error for %s", data.URL.Path) + return + } + } else if format == "json" { + if err := returnJson(w, errorDto{ + data.URL.Path, + data.Error.Error(), + }); err != nil { + log.Printf("Error while serving json error for %s", data.URL.Path) + return + } + } +} diff --git a/page_image_detail.go b/page_image_detail.go index 2cc76cf..c0fa94f 100644 --- a/page_image_detail.go +++ b/page_image_detail.go @@ -32,8 +32,7 @@ func pageImageDetail(ctx PageContext) http.Handler { WHERE id = $1 `, imageId) if err != nil { - fmt.Printf("An error occured: %s", err.Error()) - _ = returnError(w, http.StatusInternalServerError, "Internal Server Error") + formatError(w, ErrorData{500, user, r.URL, err}, "html") return } @@ -41,45 +40,40 @@ func pageImageDetail(ctx PageContext) http.Handler { if result.Next() { var owner string - err := result.Scan(&info.Id, &owner, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType) - if err != nil { - fmt.Printf("An error occured: %s", err.Error()) - _ = returnError(w, http.StatusInternalServerError, "Internal Server Error") + if err := result.Scan(&info.Id, &owner, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType); err != nil { + formatError(w, ErrorData{500, user, r.URL, err}, "html") return } switch r.PostFormValue("action") { case "update": - _, err = ctx.Database.Exec( + if _, err := ctx.Database.Exec( "UPDATE images SET title = $1, description = $2 WHERE id = $3 AND owner = $4", r.PostFormValue("title"), r.PostFormValue("description"), info.Id, user.Id, - ) - if err != nil { - fmt.Printf("An error occured: %s", err.Error()) - _ = returnError(w, http.StatusInternalServerError, "Internal Server Error") + ); err != nil { + formatError(w, ErrorData{500, user, r.URL, err}, "html") return } if r.PostFormValue("from_js") == "true" { - _ = returnJson(w, true) + if err := returnJson(w, true); err != nil { + formatError(w, ErrorData{500, user, r.URL, err}, "html") + return + } } else { http.Redirect(w, r, r.URL.Path, http.StatusFound) } return case "delete": - _, err = ctx.Database.Exec("DELETE FROM images WHERE id = $1 AND owner = $2", info.Id, user.Id) - if err != nil { - fmt.Printf("An error occured: %s", err.Error()) - _ = returnError(w, http.StatusInternalServerError, "Internal Server Error") + if _, err := ctx.Database.Exec("DELETE FROM images WHERE id = $1 AND owner = $2", info.Id, user.Id); err != nil { + formatError(w, ErrorData{500, user, r.URL, err}, "html") return } for _, definition := range ctx.Config.Sizes { - err := os.Remove(path.Join(ctx.Config.TargetFolder, fmt.Sprintf("%s%s", info.Id, definition.Suffix))) - if err != nil && !os.IsNotExist(err) { - fmt.Printf("An error occured: %s", err.Error()) - _ = returnError(w, http.StatusInternalServerError, "Internal Server Error") + if err := os.Remove(path.Join(ctx.Config.TargetFolder, fmt.Sprintf("%s%s", info.Id, definition.Suffix))); err != nil { + formatError(w, ErrorData{500, user, r.URL, err}, "html") return } } @@ -92,11 +86,15 @@ func pageImageDetail(ctx PageContext) http.Handler { info, owner == user.Id, }); err != nil { - panic(err) + formatError(w, ErrorData{500, user, r.URL, err}, "html") + return } return } - _ = returnError(w, http.StatusNotFound, "Image Not Found") + if err := returnError(w, http.StatusNotFound, "Image Not Found"); err != nil { + formatError(w, ErrorData{500, user, r.URL, err}, "html") + return + } }) } diff --git a/page_image_list.go b/page_image_list.go index fb1950d..462cb6a 100644 --- a/page_image_list.go +++ b/page_image_list.go @@ -1,19 +1,39 @@ package main import ( + "database/sql" "net/http" + "path" + "strconv" ) type ImageListData struct { - User UserInfo - Images []Image + User UserInfo + Images []Image + Previous int64 + Current int64 + Next int64 } -func pageImageList(ctx PageContext) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - user := parseUser(r) +const PageSize = 30 - result, err := ctx.Database.Query(` +func paginateImageListQuery(ctx PageContext, user UserInfo, offset int64, pageSize int) (*sql.Rows, error) { + if offset == 0 { + return ctx.Database.Query(` + SELECT + id, + coalesce(title, ''), + coalesce(description, ''), + coalesce(created_at, to_timestamp(0)), + coalesce(original_name, ''), + coalesce(type, '') + FROM images + WHERE owner = $1 + ORDER BY created_at DESC + LIMIT $2 + `, user.Id, pageSize) + } else { + return ctx.Database.Query(` SELECT id, coalesce(title, ''), @@ -24,17 +44,39 @@ func pageImageList(ctx PageContext) http.Handler { FROM images WHERE owner = $1 ORDER BY created_at DESC - `, user.Id) + LIMIT $3 + OFFSET $2 + `, user.Id, offset, pageSize) + } +} + +func pageImageList(ctx PageContext) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user := parseUser(r) + _, page := path.Split(r.URL.Path) + var pageNumber int64 + pageNumber, err := strconv.ParseInt(page, 10, 64) + if err != nil { + pageNumber = 1 + } + + result, err := paginateImageListQuery( + ctx, + user, + (pageNumber-1)*PageSize, + PageSize, + ) if err != nil { - panic(err) + formatError(w, ErrorData{500, user, r.URL, err}, "html") + return } var images []Image for result.Next() { var info Image - err := result.Scan(&info.Id, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType) - if err != nil { - panic(err) + if err := result.Scan(&info.Id, &info.Title, &info.Description, &info.CreatedAt, &info.OriginalName, &info.MimeType); err != nil { + formatError(w, ErrorData{500, user, r.URL, err}, "html") + return } images = append(images, info) } @@ -42,8 +84,12 @@ func pageImageList(ctx PageContext) http.Handler { if err = formatTemplate(w, "image_list.html", ImageListData{ user, images, + pageNumber - 1, + pageNumber, + pageNumber + 1, }); err != nil { - panic(err) + formatError(w, ErrorData{500, user, r.URL, err}, "html") + return } }) } diff --git a/page_index.go b/page_index.go index a2052fd..cfc40dd 100644 --- a/page_index.go +++ b/page_index.go @@ -22,15 +22,15 @@ func pageIndex(ctx PageContext) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { http.Redirect(w, r, "/me/images", http.StatusFound) - } else if r.URL.Path == "/favicon.ico" { + } else if r.URL.Path == "/favicon.ico" { w.Header().Set("Vary", "Accept-Encoding") w.Header().Set("Cache-Control", "public, max-age=31536000") ctx.AssetServer.ServeHTTP(w, r) - } else if r.URL.Path == "/favicon.png" { + } else if r.URL.Path == "/favicon.png" { w.Header().Set("Vary", "Accept-Encoding") w.Header().Set("Cache-Control", "public, max-age=31536000") ctx.AssetServer.ServeHTTP(w, r) - } else if r.URL.Path == "/favicon.svg" { + } else if r.URL.Path == "/favicon.svg" { w.Header().Set("Vary", "Accept-Encoding") w.Header().Set("Cache-Control", "public, max-age=31536000") ctx.AssetServer.ServeHTTP(w, r) diff --git a/page_upload.go b/page_upload.go index 4cca18d..b5f8a0d 100644 --- a/page_upload.go +++ b/page_upload.go @@ -13,11 +13,6 @@ import ( "time" ) -type UploadData struct { - User UserInfo - Results []Result -} - func detectMimeType(path string) (string, error) { file, err := os.Open(path) if err != nil { @@ -84,49 +79,30 @@ func pageUpload(ctx PageContext) http.Handler { err := r.ParseMultipartForm(32 << 20) if err != nil { - if err = returnJson(w, []Result{{ - Success: false, - Errors: []string{err.Error()}, - }}); err != nil { - panic(err) - } + formatError(w, ErrorData{500, user, r.URL, err}, "json") + return } file, header, err := r.FormFile("file") if err != nil { - if err = returnJson(w, []Result{{ - Success: false, - Errors: []string{err.Error()}, - }}); err != nil { - panic(err) - } + formatError(w, ErrorData{500, user, r.URL, err}, "json") return } image, err := createImage(ctx.Config, file, header) if err != nil { - if err = returnJson(w, []Result{{ - Success: false, - Errors: []string{err.Error()}, - }}); err != nil { - panic(err) - } + formatError(w, ErrorData{500, user, r.URL, err}, "json") return } pubsub := ctx.Redis.Subscribe(ctx.Context, ctx.Config.ResultChannel) - _, err = ctx.Database.Exec("INSERT INTO images (id, owner, created_at, updated_at, original_name, type) VALUES ($1, $2, $3, $4, $5, $6)", image.Id, user.Id, image.CreatedAt, image.CreatedAt, image.OriginalName, image.MimeType) - if err != nil { - panic(err) + if _, err = ctx.Database.Exec("INSERT INTO images (id, owner, created_at, updated_at, original_name, type) VALUES ($1, $2, $3, $4, $5, $6)", image.Id, user.Id, image.CreatedAt, image.CreatedAt, image.OriginalName, image.MimeType); err != nil { + formatError(w, ErrorData{500, user, r.URL, err}, "json") + return } data, err := json.Marshal(image) if err != nil { - if err = returnJson(w, []Result{{ - Success: false, - Errors: []string{err.Error()}, - }}); err != nil { - panic(err) - } + formatError(w, ErrorData{500, user, r.URL, err}, "json") return } @@ -138,24 +114,14 @@ func pageUpload(ctx PageContext) http.Handler { for waiting { message, err := pubsub.ReceiveMessage(ctx.Context) if err != nil { - if err = returnJson(w, []Result{{ - Success: false, - Errors: []string{err.Error()}, - }}); err != nil { - panic(err) - } + formatError(w, ErrorData{500, user, r.URL, err}, "json") return } result := Result{} err = json.Unmarshal([]byte(message.Payload), &result) if err != nil { - if err = returnJson(w, []Result{{ - Success: false, - Errors: []string{err.Error()}, - }}); err != nil { - panic(err) - } + formatError(w, ErrorData{500, user, r.URL, err}, "json") return } @@ -165,18 +131,19 @@ func pageUpload(ctx PageContext) http.Handler { waiting = false if err = returnJson(w, result); err != nil { - panic(err) + formatError(w, ErrorData{500, user, r.URL, err}, "json") + return } } } return } else { user := parseUser(r) - if err := formatTemplate(w, "upload.html", UploadData{ + if err := formatTemplate(w, "upload.html", IndexData{ user, - []Result{}, }); err != nil { - panic(err) + formatError(w, ErrorData{500, user, r.URL, err}, "html") + return } } }) diff --git a/templates/_base.html b/templates/_base.html index 9bab238..32daf1d 100644 --- a/templates/_base.html +++ b/templates/_base.html @@ -1,4 +1,5 @@ +{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.IndexData*/ -}} {{template "header" .}} {{template "navigation" .User}} {{template "content" .}} -{{template "footer" .}} \ No newline at end of file +{{template "footer" .}} diff --git a/templates/_footer.html b/templates/_footer.html index 18fdea3..fe5d3d9 100644 --- a/templates/_footer.html +++ b/templates/_footer.html @@ -1,4 +1,5 @@ +{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.IndexData*/ -}} {{define "footer"}} <script src="/assets/js/component/fake-input.js"></script> <script src="/assets/js/component/copy.js"></script> -{{end}} \ No newline at end of file +{{end}} diff --git a/templates/_header.html b/templates/_header.html index 90a099f..fc105fa 100644 --- a/templates/_header.html +++ b/templates/_header.html @@ -1,3 +1,4 @@ +{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.IndexData*/ -}} {{define "header"}} <!DOCTYPE html> <meta charset="utf-8"> @@ -22,4 +23,4 @@ <link href="/assets/css/style.css" rel="stylesheet"> <link href="/assets/css/fonts.css" rel="stylesheet"> -{{end}} \ No newline at end of file +{{end}} diff --git a/templates/_navigation.html b/templates/_navigation.html index bca2fc8..698b784 100644 --- a/templates/_navigation.html +++ b/templates/_navigation.html @@ -1,20 +1,21 @@ +{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.IndexData*/ -}} {{define "navigation"}} -<nav> - <ul> - <li class="title"><a href="/"><img src="/assets/images/logo.svg"></a></li> - {{if .HasRole "imghost:user" }} - <li><a href="/upload">Upload</a></li> - {{end}} - <li class="spacer"></li> - {{if .HasRole "imghost:user" }} - <li class="images"><a href="/me/images">My Images</a></li> - <li class="albums"><a href="/me/albums">My Albums</a></li> - <li class="me"><a href="https://accounts.kuschku.de/profile">{{.Name}}</a></li> - {{else if .Id }} - <li class="me"><a href="https://accounts.kuschku.de/profile">{{.Name}}</a></li> - {{else}} - <li><a href="/me/images">Login</a></li> - {{end}} - </ul> + <nav class="navigation"> + <ul> + <li class="title"><a href="/"><img src="/assets/images/logo.svg"></a></li> + {{if .HasRole "imghost:user" }} + <li><a class="button" href="/upload">Upload</a></li> + {{end}} + <li class="spacer"></li> + {{if .HasRole "imghost:user" }} + <li class="images"><a class="button" href="/me/images">My Images</a></li> + <li class="albums"><a class="button" href="/me/albums">My Albums</a></li> + <li class="me"><a class="button" href="https://accounts.kuschku.de/profile">{{.Name}}</a></li> + {{else if .Id }} + <li class="me"><a class="button" href="https://accounts.kuschku.de/profile">{{.Name}}</a></li> + {{else}} + <li><a class="button" href="/me/images">Login</a></li> + {{end}} + </ul> </nav> -{{end}} \ No newline at end of file +{{end}} diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..43279f6 --- /dev/null +++ b/templates/error.html @@ -0,0 +1,18 @@ +{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.ErrorData*/ -}} +{{define "title"}}Error | ik8r{{end}} +{{define "content"}} + <div class="page"> + <div class="container centered"> + <p> + <b>{{.Code}}.</b> + <ins>That’s an error.</ins> + </p> + <p> + An error occured while trying to access <code>{{.URL}}</code>.<br> + <ins>Sorry about that.</ins> + + <code>{{.Error}}</code> + </p> + </div> + </div> +{{end}} diff --git a/templates/image_detail.html b/templates/image_detail.html index abd530b..280d20d 100644 --- a/templates/image_detail.html +++ b/templates/image_detail.html @@ -1,54 +1,55 @@ +{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.ImageDetailData*/ -}} {{define "title"}}{{.Image.Title}} | ik8r{{end}} {{define "content"}} <div class="page image detail"> - <div class="detail"> - {{if .IsMine}} + <div class="detail"> + {{if .IsMine}} <h2 class="title fake-input" contenteditable="true" placeholder="Title">{{.Image.Title}}</h2> - {{else}} + {{else}} <h2 class="title">{{.Image.Title}}</h2> - {{end}} - <a class="image" href="/{{.Image.Id}}.png"> - <img src="/{{.Image.Id}}.png"> - </a> - {{if .IsMine}} + {{end}} + <a class="image" href="/{{.Image.Id}}.png"> + <img src="/{{.Image.Id}}.png"> + </a> + {{if .IsMine}} <p class="description fake-input" contenteditable="true" placeholder="Description" data-multiline>{{.Image.Description}}</p> - {{else}} + {{else}} <div class="description">{{.Image.Description}}</div> - {{end}} - </div> - <div class="sidebar"> - {{if .IsMine}} + {{end}} + </div> + <div class="sidebar"> + {{if .IsMine}} <div class="actions"> - <form name="delete" class="delete-form" method="post"> - <input type="hidden" name="action" value="delete"> - <input type="hidden" name="id" value="{{.Image.Id}}"> - <input type="submit" value="Delete"> - </form> - <form name="upload" class="update-form" method="post"> - <input type="hidden" name="action" value="update"> - <input type="hidden" name="id" value="{{.Image.Id}}"> - <input type="hidden" name="title" value="{{.Image.Title}}"> - <input type="hidden" name="description" value="{{.Image.Description}}"> - <input type="submit" id="save" value="Save"> - </form> - </div> - {{end}} - <div class="url"> - <p>Detail Page</p> - <div> - <input id="url_full" type="text" value="https://i.k8r.eu/i/{{.Image.Id}}"> - <button class="copy" data-target="#url_full">Copy</button> - </div> - </div> - <div class="url"> - <p>Direct Link</p> - <div> - <input id="url_direct" type="text" value="https://i.k8r.eu/{{.Image.Id}}.png"> - <button class="copy" data-target="#url_direct">Copy</button> - </div> + <form name="delete" class="delete-form" method="post"> + <input type="hidden" name="action" value="delete"> + <input type="hidden" name="id" value="{{.Image.Id}}"> + <input type="submit" value="Delete"> + </form> + <form name="upload" class="update-form" method="post"> + <input type="hidden" name="action" value="update"> + <input type="hidden" name="id" value="{{.Image.Id}}"> + <input type="hidden" name="title" value="{{.Image.Title}}"> + <input type="hidden" name="description" value="{{.Image.Description}}"> + <input type="submit" id="save" value="Save"> + </form> </div> + {{end}} + <div class="url"> + <p>Detail Page</p> + <div> + <input id="url_full" type="text" value="https://i.k8r.eu/i/{{.Image.Id}}"> + <button class="copy" data-target="#url_full">Copy</button> + </div> + </div> + <div class="url"> + <p>Direct Link</p> + <div> + <input id="url_direct" type="text" value="https://i.k8r.eu/{{.Image.Id}}.png"> + <button class="copy" data-target="#url_direct">Copy</button> + </div> </div> + </div> </div> {{if .IsMine}} <script src="/assets/js/page_image_detail.js"></script> diff --git a/templates/image_list.html b/templates/image_list.html index 4cc941a..5f83833 100644 --- a/templates/image_list.html +++ b/templates/image_list.html @@ -1,12 +1,20 @@ +{{- /*gotype: git.kuschku.de/justjanne/imghost-frontend.ImageListData*/ -}} {{define "title"}}My Images | ik8r{{end}} {{define "content"}} <div class="page image list"> {{range .Images}} <a class="image" href="/i/{{.Id}}"> <div class="image-container"> - <img src="/{{.Id}}t.png"> + <img src="https://i.k8r.eu/{{.Id}}t.png"> </div> <div class="info"> + <p class="title"> + {{- if eq .Title "" -}} + <span class="placeholder">Unnamed</span> + {{- else -}} + {{.Title}} + {{- end -}} + </p> <p>{{.OriginalName}}</p> <p> <time>{{.CreatedAt.Format "2006-01-02 15:04"}}</time> @@ -15,5 +23,18 @@ </div> </a> {{end}} + <ul class="pagination"> + <li class="page"> + {{- if lt 0 .Previous -}} + <a class="button" href="/me/images/{{- .Previous -}}">Previous page</a> + {{- else -}} + <a class="button" aria-disabled="true">Previous page</a> + {{- end -}} + </li> + <li class="page current">Page {{ .Current }}</li> + <li class="page"> + <a class="button" href="/me/images/{{- .Next -}}">Next page</a> + </li> + </ul> </div> -{{end}} \ No newline at end of file +{{end}} diff --git a/util.go b/util.go index 2b386f0..67f4b5c 100644 --- a/util.go +++ b/util.go @@ -8,7 +8,6 @@ import ( "github.com/go-redis/redis/v8" "html/template" "net/http" - "strings" "time" ) @@ -53,11 +52,19 @@ type Album struct { } func parseUser(r *http.Request) UserInfo { + /* + return UserInfo{ + r.Header.Get("X-Auth-Subject"), + r.Header.Get("X-Auth-Username"), + r.Header.Get("X-Auth-Email"), + strings.Split(r.Header.Get("X-Auth-Roles"), ","), + } + */ return UserInfo{ - r.Header.Get("X-Auth-Subject"), - r.Header.Get("X-Auth-Username"), - r.Header.Get("X-Auth-Email"), - strings.Split(r.Header.Get("X-Auth-Roles"), ","), + "ad45284c-be4d-4546-8171-41cf126ac091", + "justJanne", + "janne@kuschku.de", + []string{"imghost:user", "imghost:admin"}, } } -- GitLab