[add] editing

This commit is contained in:
Evan Reichard 2023-09-23 14:14:57 -04:00
parent 3150c89303
commit 4b0bbda017
7 changed files with 526 additions and 88 deletions

View File

@ -25,7 +25,7 @@ type API struct {
func NewApi(db *database.DBManager, c *config.Config) *API {
api := &API{
HTMLPolicy: bluemonday.StripTagsPolicy(),
HTMLPolicy: bluemonday.StrictPolicy(),
Router: gin.Default(),
Config: c,
DB: db,
@ -97,6 +97,8 @@ func (api *API) registerWebAppRoutes() {
api.Router.GET("/activity", api.authWebAppMiddleware, api.createAppResourcesRoute("activity"))
api.Router.GET("/documents", api.authWebAppMiddleware, api.createAppResourcesRoute("documents"))
api.Router.GET("/documents/:document", api.authWebAppMiddleware, api.createAppResourcesRoute("document"))
api.Router.DELETE("/documents/:document", api.authWebAppMiddleware, api.deleteDocument)
api.Router.POST("/documents/:document/edit", api.authWebAppMiddleware, api.createAppResourcesRoute("document-edit"))
api.Router.GET("/documents/:document/file", api.authWebAppMiddleware, api.downloadDocumentFile)
api.Router.GET("/documents/:document/cover", api.authWebAppMiddleware, api.getDocumentCover)

View File

@ -2,9 +2,11 @@ package api
import (
"fmt"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/gin-gonic/gin"
@ -13,6 +15,13 @@ import (
"reichard.io/bbank/metadata"
)
type requestDocumentEdit struct {
Title *string `form:"title"`
Author *string `form:"author"`
Description *string `form:"description"`
Cover *multipart.FileHeader `form:"cover"`
}
func baseResourceRoute(template string, args ...map[string]any) func(c *gin.Context) {
variables := gin.H{"RouteName": template}
if len(args) > 0 {
@ -84,6 +93,46 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
}
templateVars["Data"] = document
} else if routeName == "document-edit" {
var rDocID requestDocumentID
if err := c.ShouldBindUri(&rDocID); err != nil {
log.Error("[createAppResourcesRoute] Invalid URI Bind")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
var rDocEdit requestDocumentEdit
if err := c.ShouldBind(&rDocEdit); err != nil {
log.Error("[createAppResourcesRoute] Invalid Form Bind")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
if rDocEdit.Author == nil && rDocEdit.Title == nil && rDocEdit.Description == nil && rDocEdit.Cover == nil {
log.Error("[createAppResourcesRoute] Missing Form Values")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
// TODO - Handle Cover
if rDocEdit.Cover != nil {
}
// Update Document
if _, err := api.DB.Queries.UpsertDocument(api.DB.Ctx, database.UpsertDocumentParams{
ID: rDocID.DocumentID,
Title: api.sanitizeInput(rDocEdit.Title),
Author: api.sanitizeInput(rDocEdit.Author),
Description: api.sanitizeInput(rDocEdit.Description),
}); err != nil {
log.Error("[createAppResourcesRoute] UpsertDocument DB Error:", err)
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
c.Redirect(http.StatusFound, "./")
return
} else if routeName == "activity" {
activityFilter := database.GetActivityParams{
UserID: rUser.(string),
@ -195,25 +244,27 @@ func (api *API) getDocumentCover(c *gin.Context) {
var coverFilePath string
// Identify Documents & Save Covers
bookMetadata := metadata.MetadataInfo{
metadataResults, err := metadata.GetMetadata(metadata.MetadataInfo{
Title: document.Title,
Author: document.Author,
}
err = metadata.GetMetadata(&bookMetadata)
if err == nil && bookMetadata.GBID != nil {
})
if err == nil && len(metadataResults) > 0 && metadataResults[0].GBID != nil {
firstResult := metadataResults[0]
// Derive & Sanitize File Name
fileName := "." + filepath.Clean(fmt.Sprintf("/%s.jpg", *bookMetadata.GBID))
fileName := "." + filepath.Clean(fmt.Sprintf("/%s.jpg", *firstResult.GBID))
// Generate Storage Path
coverFilePath = filepath.Join(api.Config.DataPath, "covers", fileName)
err := metadata.SaveCover(*bookMetadata.GBID, coverFilePath)
err := metadata.SaveCover(*firstResult.GBID, coverFilePath)
if err == nil {
coverID = *bookMetadata.GBID
log.Info("Title:", *bookMetadata.Title)
log.Info("Author:", *bookMetadata.Author)
log.Info("Description:", *bookMetadata.Description)
log.Info("IDs:", bookMetadata.ISBN)
coverID = *firstResult.GBID
log.Info("Title:", *firstResult.Title)
log.Info("Author:", *firstResult.Author)
log.Info("Description:", *firstResult.Description)
log.Info("IDs:", firstResult.ISBN)
}
}
@ -241,3 +292,60 @@ func (api *API) getDocumentCover(c *gin.Context) {
c.File(coverFilePath)
}
// DELETE /api/documents/:document
func (api *API) deleteDocument(c *gin.Context) {
var rDocID requestDocumentID
if err := c.ShouldBindUri(&rDocID); err != nil {
log.Error("[deleteDocument] Invalid URI Bind")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
// TODO
}
// POST /api/documents/:document/identify
func (api *API) identifyDocument(c *gin.Context) {
var rDocID requestDocumentID
if err := c.ShouldBindUri(&rDocID); err != nil {
log.Error("[identifyDocument] Invalid URI Bind")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
isbn := strings.TrimSpace(c.PostForm("ISBN"))
if isbn == "" {
log.Error("[identifyDocument] Invalid Form")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
return
}
metadataResults, err := metadata.GetMetadata(metadata.MetadataInfo{
ISBN: []*string{&isbn},
})
if err != nil || len(metadataResults) == 0 {
log.Error("[identifyDocument] Metadata Error")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Metadata Error"})
return
}
// TODO
firstResult := metadataResults[0]
if firstResult.Title != nil {
log.Info("Title:", *firstResult.Title)
}
if firstResult.Author != nil {
log.Info("Author:", *firstResult.Author)
}
if firstResult.Description != nil {
log.Info("Description:", *firstResult.Description)
}
for _, val := range firstResult.ISBN {
log.Info("ISBN:", *val)
}
c.Redirect(http.StatusFound, "/")
}

View File

@ -5,6 +5,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"html"
"io"
"net/http"
"os"
@ -609,12 +610,12 @@ func (api *API) sanitizeInput(val any) *string {
switch v := val.(type) {
case *string:
if v != nil {
newString := api.HTMLPolicy.Sanitize(string(*v))
newString := html.UnescapeString(api.HTMLPolicy.Sanitize(string(*v)))
return &newString
}
case string:
if v != "" {
newString := api.HTMLPolicy.Sanitize(string(v))
newString := html.UnescapeString(api.HTMLPolicy.Sanitize(string(v)))
return &newString
}
}

View File

@ -46,48 +46,56 @@ const GBOOKS_QUERY_URL string = "https://www.googleapis.com/books/v1/volumes?q=%
const GBOOKS_GBID_INFO_URL string = "https://www.googleapis.com/books/v1/volumes/%s"
const GBOOKS_GBID_COVER_URL string = "https://books.google.com/books/content/images/frontcover/%s?fife=w480-h690"
func GetMetadata(data *MetadataInfo) error {
var queryResult *gBooksQueryItem
if data.GBID != nil {
func GetMetadata(metadataSearch MetadataInfo) ([]MetadataInfo, error) {
var queryResults []gBooksQueryItem
if metadataSearch.GBID != nil {
// Use GBID
resp, err := performGBIDRequest(*data.GBID)
resp, err := performGBIDRequest(*metadataSearch.GBID)
if err != nil {
return err
return nil, err
}
queryResult = resp
} else if len(data.ISBN) > 0 {
searchQuery := "isbn:" + *data.ISBN[0]
queryResults = []gBooksQueryItem{*resp}
} else if len(metadataSearch.ISBN) > 0 {
searchQuery := "isbn:" + *metadataSearch.ISBN[0]
resp, err := performSearchRequest(searchQuery)
if err != nil {
return err
return nil, err
}
queryResult = &resp.Items[0]
} else if data.Title != nil && data.Author != nil {
searchQuery := url.QueryEscape(fmt.Sprintf("%s %s", *data.Title, *data.Author))
queryResults = resp.Items
} else if metadataSearch.Title != nil && metadataSearch.Author != nil {
searchQuery := url.QueryEscape(fmt.Sprintf("%s %s", *metadataSearch.Title, *metadataSearch.Author))
resp, err := performSearchRequest(searchQuery)
if err != nil {
return err
return nil, err
}
queryResult = &resp.Items[0]
queryResults = resp.Items
} else {
return errors.New("Invalid Data")
return nil, errors.New("Invalid Data")
}
// Merge Data
data.GBID = &queryResult.ID
data.Description = &queryResult.Info.Description
data.Title = &queryResult.Info.Title
if len(queryResult.Info.Authors) > 0 {
data.Author = &queryResult.Info.Authors[0]
// Normalize Data
allMetadata := []MetadataInfo{}
for _, item := range queryResults {
itemResult := MetadataInfo{
GBID: &item.ID,
Title: &item.Info.Title,
Description: &item.Info.Description,
}
for _, item := range queryResult.Info.Identifiers {
if len(item.Info.Authors) > 0 {
itemResult.Author = &item.Info.Authors[0]
}
for _, item := range item.Info.Identifiers {
if item.Type == "ISBN_10" || item.Type == "ISBN_13" {
data.ISBN = append(data.ISBN, &item.Identifier)
itemResult.ISBN = append(itemResult.ISBN, &item.Identifier)
}
}
return nil
allMetadata = append(allMetadata, itemResult)
}
return allMetadata, nil
}
func SaveCover(id string, safePath string) error {

View File

@ -0,0 +1,166 @@
{{template "base.html" .}}
{{define "title"}}Documents{{end}}
{{define "header"}}
<a href="../documents">Documents</a>
{{end}}
{{define "content"}}
<div class="h-full w-full overflow-scroll bg-white shadow-lg dark:bg-gray-700 rounded dark:text-white p-6">
<div class="flex flex-col gap-2 float-left mr-6 mb-6">
<img class="rounded w-40 md:w-60 lg:w-80 object-fill h-full" src="../documents/{{.Data.ID}}/cover"></img>
<div class="flex gap-2 justify-end text-gray-500 dark:text-gray-400">
<div class="relative">
<label for="delete-button">
<svg
width="24"
height="24"
class="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 6.52381C3 6.12932 3.32671 5.80952 3.72973 5.80952H8.51787C8.52437 4.9683 8.61554 3.81504 9.45037 3.01668C10.1074 2.38839 11.0081 2 12 2C12.9919 2 13.8926 2.38839 14.5496 3.01668C15.3844 3.81504 15.4756 4.9683 15.4821 5.80952H20.2703C20.6733 5.80952 21 6.12932 21 6.52381C21 6.9183 20.6733 7.2381 20.2703 7.2381H3.72973C3.32671 7.2381 3 6.9183 3 6.52381Z"
/>
<path
d="M11.6066 22H12.3935C15.101 22 16.4547 22 17.3349 21.1368C18.2151 20.2736 18.3052 18.8576 18.4853 16.0257L18.7448 11.9452C18.8425 10.4086 18.8913 9.64037 18.4498 9.15352C18.0082 8.66667 17.2625 8.66667 15.7712 8.66667H8.22884C6.7375 8.66667 5.99183 8.66667 5.55026 9.15352C5.1087 9.64037 5.15756 10.4086 5.25528 11.9452L5.51479 16.0257C5.69489 18.8576 5.78494 20.2736 6.66513 21.1368C7.54532 22 8.89906 22 11.6066 22Z"
/>
</svg>
</label>
<input type="checkbox" id="delete-button" class="hidden css-button"/>
<form
method="POST"
action="./{{ .Data.ID }}/delete"
class="absolute bottom-7 left-5 text-black bg-gray-200 transition-all duration-200 rounded shadow-inner shadow-lg shadow-gray-500 dark:text-white dark:shadow-gray-900 dark:bg-gray-600 text-sm p-3"
>
<p class="font-medium w-24 pb-2">Are you sure?</p>
<button class="font-medium w-full px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Delete</button>
</form>
</div>
<a href="../activity?document={{ .Data.ID }}">
<svg
width="24"
height="24"
class="hover:text-gray-800 dark:hover:text-gray-100"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M9.5 2C8.67157 2 8 2.67157 8 3.5V4.5C8 5.32843 8.67157 6 9.5 6H14.5C15.3284 6 16 5.32843 16 4.5V3.5C16 2.67157 15.3284 2 14.5 2H9.5Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 4.03662C5.24209 4.10719 4.44798 4.30764 3.87868 4.87694C3 5.75562 3 7.16983 3 9.99826V15.9983C3 18.8267 3 20.2409 3.87868 21.1196C4.75736 21.9983 6.17157 21.9983 9 21.9983H15C17.8284 21.9983 19.2426 21.9983 20.1213 21.1196C21 20.2409 21 18.8267 21 15.9983V9.99826C21 7.16983 21 5.75562 20.1213 4.87694C19.552 4.30764 18.7579 4.10719 17.5 4.03662V4.5C17.5 6.15685 16.1569 7.5 14.5 7.5H9.5C7.84315 7.5 6.5 6.15685 6.5 4.5V4.03662ZM7 9.75C6.58579 9.75 6.25 10.0858 6.25 10.5C6.25 10.9142 6.58579 11.25 7 11.25H7.5C7.91421 11.25 8.25 10.9142 8.25 10.5C8.25 10.0858 7.91421 9.75 7.5 9.75H7ZM10.5 9.75C10.0858 9.75 9.75 10.0858 9.75 10.5C9.75 10.9142 10.0858 11.25 10.5 11.25H17C17.4142 11.25 17.75 10.9142 17.75 10.5C17.75 10.0858 17.4142 9.75 17 9.75H10.5ZM7 13.25C6.58579 13.25 6.25 13.5858 6.25 14C6.25 14.4142 6.58579 14.75 7 14.75H7.5C7.91421 14.75 8.25 14.4142 8.25 14C8.25 13.5858 7.91421 13.25 7.5 13.25H7ZM10.5 13.25C10.0858 13.25 9.75 13.5858 9.75 14C9.75 14.4142 10.0858 14.75 10.5 14.75H17C17.4142 14.75 17.75 14.4142 17.75 14C17.75 13.5858 17.4142 13.25 17 13.25H10.5ZM7 16.75C6.58579 16.75 6.25 17.0858 6.25 17.5C6.25 17.9142 6.58579 18.25 7 18.25H7.5C7.91421 18.25 8.25 17.9142 8.25 17.5C8.25 17.0858 7.91421 16.75 7.5 16.75H7ZM10.5 16.75C10.0858 16.75 9.75 17.0858 9.75 17.5C9.75 17.9142 10.0858 18.25 10.5 18.25H17C17.4142 18.25 17.75 17.9142 17.75 17.5C17.75 17.0858 17.4142 16.75 17 16.75H10.5Z"/>
</svg>
</a>
<div class="relative">
<label for="edit-button">
<svg
width="24"
height="24"
class="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M21.1938 2.80624C22.2687 3.88124 22.2687 5.62415 21.1938 6.69914L20.6982 7.19469C20.5539 7.16345 20.3722 7.11589 20.1651 7.04404C19.6108 6.85172 18.8823 6.48827 18.197 5.803C17.5117 5.11774 17.1483 4.38923 16.956 3.8349C16.8841 3.62781 16.8366 3.44609 16.8053 3.30179L17.3009 2.80624C18.3759 1.73125 20.1188 1.73125 21.1938 2.80624Z"
/>
<path
d="M14.5801 13.3128C14.1761 13.7168 13.9741 13.9188 13.7513 14.0926C13.4886 14.2975 13.2043 14.4732 12.9035 14.6166C12.6485 14.7381 12.3775 14.8284 11.8354 15.0091L8.97709 15.9619C8.71035 16.0508 8.41626 15.9814 8.21744 15.7826C8.01862 15.5837 7.9492 15.2897 8.03811 15.0229L8.99089 12.1646C9.17157 11.6225 9.26191 11.3515 9.38344 11.0965C9.52679 10.7957 9.70249 10.5114 9.90743 10.2487C10.0812 10.0259 10.2832 9.82394 10.6872 9.41993L15.6033 4.50385C15.867 5.19804 16.3293 6.05663 17.1363 6.86366C17.9434 7.67069 18.802 8.13296 19.4962 8.39674L14.5801 13.3128Z"
/>
<path
d="M20.5355 20.5355C22 19.0711 22 16.714 22 12C22 10.4517 22 9.15774 21.9481 8.0661L15.586 14.4283C15.2347 14.7797 14.9708 15.0437 14.6738 15.2753C14.3252 15.5473 13.948 15.7804 13.5488 15.9706C13.2088 16.1327 12.8546 16.2506 12.3833 16.4076L9.45143 17.3849C8.64568 17.6535 7.75734 17.4438 7.15678 16.8432C6.55621 16.2427 6.34651 15.3543 6.61509 14.5486L7.59235 11.6167C7.74936 11.1454 7.86732 10.7912 8.02935 10.4512C8.21958 10.052 8.45272 9.6748 8.72466 9.32615C8.9563 9.02918 9.22032 8.76528 9.57173 8.41404L15.9339 2.05188C14.8423 2 13.5483 2 12 2C7.28595 2 4.92893 2 3.46447 3.46447C2 4.92893 2 7.28595 2 12C2 16.714 2 19.0711 3.46447 20.5355C4.92893 22 7.28595 22 12 22C16.714 22 19.0711 22 20.5355 20.5355Z"
/>
</svg>
</label>
<input type="checkbox" id="edit-button" class="hidden css-button"/>
<form
method="POST"
action="./{{ .Data.ID }}/edit"
class="absolute bottom-7 left-5 text-black bg-gray-200 transition-all duration-200 rounded shadow-inner shadow-lg shadow-gray-500 dark:text-white dark:shadow-gray-900 dark:bg-gray-600 text-sm p-3"
>
<label class="font-medium" for="isbn">ISBN</label>
<input class="mt-1 mb-2 p-1 bg-gray-400 text-black dark:bg-gray-700 dark:text-white" type="text" id="isbn" name="ISBN"><br>
<button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Search Metadata</button>
</form>
</div>
{{ if .Data.Filepath }}
<a href="./{{.Data.ID}}/file">
<svg
width="24"
height="24"
class="hover:text-gray-800 dark:hover:text-gray-100"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12ZM12 6.25C12.4142 6.25 12.75 6.58579 12.75 7V12.1893L14.4697 10.4697C14.7626 10.1768 15.2374 10.1768 15.5303 10.4697C15.8232 10.7626 15.8232 11.2374 15.5303 11.5303L12.5303 14.5303C12.3897 14.671 12.1989 14.75 12 14.75C11.8011 14.75 11.6103 14.671 11.4697 14.5303L8.46967 11.5303C8.17678 11.2374 8.17678 10.7626 8.46967 10.4697C8.76256 10.1768 9.23744 10.1768 9.53033 10.4697L11.25 12.1893V7C11.25 6.58579 11.5858 6.25 12 6.25ZM8 16.25C7.58579 16.25 7.25 16.5858 7.25 17C7.25 17.4142 7.58579 17.75 8 17.75H16C16.4142 17.75 16.75 17.4142 16.75 17C16.75 16.5858 16.4142 16.25 16 16.25H8Z"
/>
</svg>
</a>
{{ else }}
<svg
width="24"
height="24"
class="text-gray-200 dark:text-gray-600"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12ZM12 6.25C12.4142 6.25 12.75 6.58579 12.75 7V12.1893L14.4697 10.4697C14.7626 10.1768 15.2374 10.1768 15.5303 10.4697C15.8232 10.7626 15.8232 11.2374 15.5303 11.5303L12.5303 14.5303C12.3897 14.671 12.1989 14.75 12 14.75C11.8011 14.75 11.6103 14.671 11.4697 14.5303L8.46967 11.5303C8.17678 11.2374 8.17678 10.7626 8.46967 10.4697C8.76256 10.1768 9.23744 10.1768 9.53033 10.4697L11.25 12.1893V7C11.25 6.58579 11.5858 6.25 12 6.25ZM8 16.25C7.58579 16.25 7.25 16.5858 7.25 17C7.25 17.4142 7.58579 17.75 8 17.75H16C16.4142 17.75 16.75 17.4142 16.75 17C16.75 16.5858 16.4142 16.25 16 16.25H8Z"
/>
</svg>
{{ end }}
</div>
</div>
<div class="flex flex-wrap justify-between gap-6 pb-6">
<div>
<p class="text-gray-400">Title</p>
<!-- <input type="text" class="font-medium bg-transparent" value="{{ or .Data.Title "Unknown" }}"/> -->
<p class="font-medium text-lg">
{{ or .Data.Title "N/A" }}
</p>
</div>
<div>
<p class="text-gray-400">Author</p>
<p class="font-medium text-lg">
{{ or .Data.Author "N/A" }}
</p>
</div>
<div>
<p class="text-gray-400">Progress</p>
<p class="font-medium text-lg">
{{ .Data.CurrentPage }} / {{ .Data.TotalPages }} ({{ .Data.Percentage }}%)
</p>
</div>
<div>
<p class="text-gray-400">Time Read</p>
<p class="font-medium text-lg">
{{ .Data.TotalTimeMinutes }} Minutes
</p>
</div>
</div>
<p class="text-gray-400">Description</p>
<p class="font-medium text-justify hyphens-auto">
{{ or .Data.Description "N/A" }}
</p>
</div>
<style>
.css-button:checked + form {
visibility: visible;
opacity: 1;
}
.css-button + form {
visibility: hidden;
opacity: 0;
}
</style>
{{end}}

View File

@ -7,10 +7,28 @@
{{end}}
{{define "content"}}
<div class="h-full w-full overflow-scroll bg-white shadow-lg dark:bg-gray-700 rounded dark:text-white text-sm p-4">
<div class="flex flex-col gap-2 float-left mr-4">
<div class="h-full w-full overflow-scroll bg-white shadow-lg dark:bg-gray-700 rounded dark:text-white p-6">
<div class="flex flex-col gap-2 float-left mr-6 mb-6 relative">
<label class="z-20 cursor-pointer" for="edit-cover-button">
<img class="rounded w-40 md:w-60 lg:w-80 object-fill h-full" src="../documents/{{.Data.ID}}/cover"></img>
<div class="flex gap-2 justify-end text-gray-500 dark:text-gray-400">
</label>
<div class="flex relative z-40 gap-2 justify-end text-gray-500 dark:text-gray-400">
<input type="checkbox" id="edit-cover-button" class="hidden css-button"/>
<div class="absolute z-50 top-0 left-0 p-3 transition-all duration-200 bg-gray-200 rounded shadow-lg shadow-gray-500 dark:shadow-gray-900 dark:bg-gray-600">
<form
method="POST"
enctype="multipart/form-data"
action="./{{ .Data.ID }}/edit"
class="flex flex-col gap-2 w-72 text-black dark:text-white text-sm"
>
<input
type="file"
id="cover"
name="cover"
>
<button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Upload</button>
</form>
</div>
<div class="relative">
<label for="delete-button">
<svg
@ -30,15 +48,17 @@
</svg>
</label>
<input type="checkbox" id="delete-button" class="hidden css-button"/>
<div class="absolute z-50 bottom-7 left-5 p-3 transition-all duration-200 bg-gray-200 rounded shadow-lg shadow-gray-500 dark:shadow-gray-900 dark:bg-gray-600">
<form
method="POST"
action="./{{ .Data.ID }}/delete"
class="absolute bottom-7 left-5 text-black bg-gray-200 transition-all duration-200 rounded shadow-inner shadow-lg shadow-gray-500 dark:text-white dark:shadow-gray-900 dark:bg-gray-600 text-sm p-3"
class="text-black dark:text-white text-sm"
>
<p class="font-medium w-24 pb-2">Are you sure?</p>
<button class="font-medium w-full px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Delete</button>
</form>
</div>
</div>
<a href="../activity?document={{ .Data.ID }}">
<svg
width="24"
@ -53,7 +73,7 @@
</svg>
</a>
<div class="relative">
<label for="metadata-button">
<label for="edit-button">
<svg
width="24"
height="24"
@ -63,23 +83,34 @@
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22ZM12 7.75C11.3787 7.75 10.875 8.25368 10.875 8.875C10.875 9.28921 10.5392 9.625 10.125 9.625C9.71079 9.625 9.375 9.28921 9.375 8.875C9.375 7.42525 10.5503 6.25 12 6.25C13.4497 6.25 14.625 7.42525 14.625 8.875C14.625 9.58584 14.3415 10.232 13.883 10.704C13.7907 10.7989 13.7027 10.8869 13.6187 10.9708C13.4029 11.1864 13.2138 11.3753 13.0479 11.5885C12.8289 11.8699 12.75 12.0768 12.75 12.25V13C12.75 13.4142 12.4142 13.75 12 13.75C11.5858 13.75 11.25 13.4142 11.25 13V12.25C11.25 11.5948 11.555 11.0644 11.8642 10.6672C12.0929 10.3733 12.3804 10.0863 12.6138 9.85346C12.6842 9.78321 12.7496 9.71789 12.807 9.65877C13.0046 9.45543 13.125 9.18004 13.125 8.875C13.125 8.25368 12.6213 7.75 12 7.75ZM12 17C12.5523 17 13 16.5523 13 16C13 15.4477 12.5523 15 12 15C11.4477 15 11 15.4477 11 16C11 16.5523 11.4477 17 12 17Z"
d="M21.1938 2.80624C22.2687 3.88124 22.2687 5.62415 21.1938 6.69914L20.6982 7.19469C20.5539 7.16345 20.3722 7.11589 20.1651 7.04404C19.6108 6.85172 18.8823 6.48827 18.197 5.803C17.5117 5.11774 17.1483 4.38923 16.956 3.8349C16.8841 3.62781 16.8366 3.44609 16.8053 3.30179L17.3009 2.80624C18.3759 1.73125 20.1188 1.73125 21.1938 2.80624Z"
/>
<path
d="M14.5801 13.3128C14.1761 13.7168 13.9741 13.9188 13.7513 14.0926C13.4886 14.2975 13.2043 14.4732 12.9035 14.6166C12.6485 14.7381 12.3775 14.8284 11.8354 15.0091L8.97709 15.9619C8.71035 16.0508 8.41626 15.9814 8.21744 15.7826C8.01862 15.5837 7.9492 15.2897 8.03811 15.0229L8.99089 12.1646C9.17157 11.6225 9.26191 11.3515 9.38344 11.0965C9.52679 10.7957 9.70249 10.5114 9.90743 10.2487C10.0812 10.0259 10.2832 9.82394 10.6872 9.41993L15.6033 4.50385C15.867 5.19804 16.3293 6.05663 17.1363 6.86366C17.9434 7.67069 18.802 8.13296 19.4962 8.39674L14.5801 13.3128Z"
/>
<path
d="M20.5355 20.5355C22 19.0711 22 16.714 22 12C22 10.4517 22 9.15774 21.9481 8.0661L15.586 14.4283C15.2347 14.7797 14.9708 15.0437 14.6738 15.2753C14.3252 15.5473 13.948 15.7804 13.5488 15.9706C13.2088 16.1327 12.8546 16.2506 12.3833 16.4076L9.45143 17.3849C8.64568 17.6535 7.75734 17.4438 7.15678 16.8432C6.55621 16.2427 6.34651 15.3543 6.61509 14.5486L7.59235 11.6167C7.74936 11.1454 7.86732 10.7912 8.02935 10.4512C8.21958 10.052 8.45272 9.6748 8.72466 9.32615C8.9563 9.02918 9.22032 8.76528 9.57173 8.41404L15.9339 2.05188C14.8423 2 13.5483 2 12 2C7.28595 2 4.92893 2 3.46447 3.46447C2 4.92893 2 7.28595 2 12C2 16.714 2 19.0711 3.46447 20.5355C4.92893 22 7.28595 22 12 22C16.714 22 19.0711 22 20.5355 20.5355Z"
/>
</svg>
</label>
<input type="checkbox" id="metadata-button" class="hidden css-button"/>
<input type="checkbox" id="edit-button" class="hidden css-button"/>
<div class="absolute z-50 bottom-7 left-5 p-3 transition-all duration-200 bg-gray-200 rounded shadow-lg shadow-gray-500 dark:shadow-gray-900 dark:bg-gray-600">
<form
method="POST"
action="./{{ .Data.ID }}/identify"
class="absolute bottom-7 left-5 text-black bg-gray-200 transition-all duration-200 rounded shadow-inner shadow-lg shadow-gray-500 dark:text-white dark:shadow-gray-900 dark:bg-gray-600 text-sm p-3"
action="./{{ .Data.ID }}/edit"
class="text-black dark:text-white text-sm"
>
<label class="font-medium" for="isbn">ISBN</label>
<input class="mt-1 mb-2 p-1 bg-gray-400 text-black dark:bg-gray-700 dark:text-white" type="text" id="isbn" name="isbn"><br>
<input
type="text"
id="isbn"
name="ISBN"
class="mt-1 mb-2 p-1 bg-gray-400 text-black dark:bg-gray-700 dark:text-white"
><br>
<button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Search Metadata</button>
</form>
</div>
</div>
{{ if .Data.Filepath }}
<a href="./{{.Data.ID}}/file">
<svg
@ -115,45 +146,167 @@
{{ end }}
</div>
</div>
<div class="flex flex-wrap justify-between gap-4 pb-4">
<div>
<p class="text-gray-400">Title</p>
<!-- <input type="text" class="font-medium bg-transparent" value="{{ or .Data.Title "Unknown" }}"/> -->
<p class="font-medium">
<div class="flex flex-wrap justify-between gap-6 pb-6">
<div class="relative">
<div class="text-gray-400 inline-flex gap-2 relative">
<p>Title</p>
<label class="my-auto" for="edit-title-button">
<svg
width="18"
height="18"
class="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M21.1938 2.80624C22.2687 3.88124 22.2687 5.62415 21.1938 6.69914L20.6982 7.19469C20.5539 7.16345 20.3722 7.11589 20.1651 7.04404C19.6108 6.85172 18.8823 6.48827 18.197 5.803C17.5117 5.11774 17.1483 4.38923 16.956 3.8349C16.8841 3.62781 16.8366 3.44609 16.8053 3.30179L17.3009 2.80624C18.3759 1.73125 20.1188 1.73125 21.1938 2.80624Z"
/>
<path
d="M14.5801 13.3128C14.1761 13.7168 13.9741 13.9188 13.7513 14.0926C13.4886 14.2975 13.2043 14.4732 12.9035 14.6166C12.6485 14.7381 12.3775 14.8284 11.8354 15.0091L8.97709 15.9619C8.71035 16.0508 8.41626 15.9814 8.21744 15.7826C8.01862 15.5837 7.9492 15.2897 8.03811 15.0229L8.99089 12.1646C9.17157 11.6225 9.26191 11.3515 9.38344 11.0965C9.52679 10.7957 9.70249 10.5114 9.90743 10.2487C10.0812 10.0259 10.2832 9.82394 10.6872 9.41993L15.6033 4.50385C15.867 5.19804 16.3293 6.05663 17.1363 6.86366C17.9434 7.67069 18.802 8.13296 19.4962 8.39674L14.5801 13.3128Z"
/>
<path
d="M20.5355 20.5355C22 19.0711 22 16.714 22 12C22 10.4517 22 9.15774 21.9481 8.0661L15.586 14.4283C15.2347 14.7797 14.9708 15.0437 14.6738 15.2753C14.3252 15.5473 13.948 15.7804 13.5488 15.9706C13.2088 16.1327 12.8546 16.2506 12.3833 16.4076L9.45143 17.3849C8.64568 17.6535 7.75734 17.4438 7.15678 16.8432C6.55621 16.2427 6.34651 15.3543 6.61509 14.5486L7.59235 11.6167C7.74936 11.1454 7.86732 10.7912 8.02935 10.4512C8.21958 10.052 8.45272 9.6748 8.72466 9.32615C8.9563 9.02918 9.22032 8.76528 9.57173 8.41404L15.9339 2.05188C14.8423 2 13.5483 2 12 2C7.28595 2 4.92893 2 3.46447 3.46447C2 4.92893 2 7.28595 2 12C2 16.714 2 19.0711 3.46447 20.5355C4.92893 22 7.28595 22 12 22C16.714 22 19.0711 22 20.5355 20.5355Z"
/>
</svg>
</label>
<input type="checkbox" id="edit-title-button" class="hidden css-button"/>
<div class="absolute z-50 top-7 right-0 p-3 transition-all duration-200 bg-gray-200 rounded shadow-lg shadow-gray-500 dark:shadow-gray-900 dark:bg-gray-600">
<form
method="POST"
action="./{{ .Data.ID }}/edit"
class="flex flex-col gap-2 text-black dark:text-white text-sm"
>
<input
type="text"
id="title"
name="title"
value="{{ or .Data.Title "N/A" }}"
class="p-1 bg-gray-400 text-black dark:bg-gray-700 dark:text-white"
>
<button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Save</button>
</form>
</div>
</div>
<p class="font-medium text-lg">
{{ or .Data.Title "N/A" }}
</p>
</div>
<div>
<p class="text-gray-400">Author</p>
<p class="font-medium">
<div class="relative">
<div class="text-gray-400 inline-flex gap-2 relative">
<p>Author</p>
<label class="my-auto" for="edit-author-button">
<svg
width="18"
height="18"
class="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M21.1938 2.80624C22.2687 3.88124 22.2687 5.62415 21.1938 6.69914L20.6982 7.19469C20.5539 7.16345 20.3722 7.11589 20.1651 7.04404C19.6108 6.85172 18.8823 6.48827 18.197 5.803C17.5117 5.11774 17.1483 4.38923 16.956 3.8349C16.8841 3.62781 16.8366 3.44609 16.8053 3.30179L17.3009 2.80624C18.3759 1.73125 20.1188 1.73125 21.1938 2.80624Z"
/>
<path
d="M14.5801 13.3128C14.1761 13.7168 13.9741 13.9188 13.7513 14.0926C13.4886 14.2975 13.2043 14.4732 12.9035 14.6166C12.6485 14.7381 12.3775 14.8284 11.8354 15.0091L8.97709 15.9619C8.71035 16.0508 8.41626 15.9814 8.21744 15.7826C8.01862 15.5837 7.9492 15.2897 8.03811 15.0229L8.99089 12.1646C9.17157 11.6225 9.26191 11.3515 9.38344 11.0965C9.52679 10.7957 9.70249 10.5114 9.90743 10.2487C10.0812 10.0259 10.2832 9.82394 10.6872 9.41993L15.6033 4.50385C15.867 5.19804 16.3293 6.05663 17.1363 6.86366C17.9434 7.67069 18.802 8.13296 19.4962 8.39674L14.5801 13.3128Z"
/>
<path
d="M20.5355 20.5355C22 19.0711 22 16.714 22 12C22 10.4517 22 9.15774 21.9481 8.0661L15.586 14.4283C15.2347 14.7797 14.9708 15.0437 14.6738 15.2753C14.3252 15.5473 13.948 15.7804 13.5488 15.9706C13.2088 16.1327 12.8546 16.2506 12.3833 16.4076L9.45143 17.3849C8.64568 17.6535 7.75734 17.4438 7.15678 16.8432C6.55621 16.2427 6.34651 15.3543 6.61509 14.5486L7.59235 11.6167C7.74936 11.1454 7.86732 10.7912 8.02935 10.4512C8.21958 10.052 8.45272 9.6748 8.72466 9.32615C8.9563 9.02918 9.22032 8.76528 9.57173 8.41404L15.9339 2.05188C14.8423 2 13.5483 2 12 2C7.28595 2 4.92893 2 3.46447 3.46447C2 4.92893 2 7.28595 2 12C2 16.714 2 19.0711 3.46447 20.5355C4.92893 22 7.28595 22 12 22C16.714 22 19.0711 22 20.5355 20.5355Z"
/>
</svg>
</label>
<input type="checkbox" id="edit-author-button" class="hidden css-button"/>
<div class="absolute z-50 top-7 right-0 p-3 transition-all duration-200 bg-gray-200 rounded shadow-lg shadow-gray-500 dark:shadow-gray-900 dark:bg-gray-600">
<form
method="POST"
action="./{{ .Data.ID }}/edit"
class="flex flex-col gap-2 text-black dark:text-white text-sm"
>
<input
type="text"
id="author"
name="author"
value="{{ or .Data.Author "N/A" }}"
class="p-1 bg-gray-400 text-black dark:bg-gray-700 dark:text-white"
>
<button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Save</button>
</form>
</div>
</div>
<p class="font-medium text-lg">
{{ or .Data.Author "N/A" }}
</p>
</div>
<div>
<p class="text-gray-400">Progress</p>
<p class="font-medium">
<p class="font-medium text-lg">
{{ .Data.CurrentPage }} / {{ .Data.TotalPages }} ({{ .Data.Percentage }}%)
</p>
</div>
<div>
<p class="text-gray-400">Minutes Read</p>
<p class="font-medium">
<p class="text-gray-400">Time Read</p>
<p class="font-medium text-lg">
{{ .Data.TotalTimeMinutes }} Minutes
</p>
</div>
</div>
<p class="text-gray-400">Description</p>
<p class="font-medium text-justify hyphens-auto">
{{ or .Data.Description "N/A" }}
</p>
<div class="relative">
<div class="text-gray-400 inline-flex gap-2 relative">
<p>Description</p>
<label class="my-auto" for="edit-description-button">
<svg
width="18"
height="18"
class="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M21.1938 2.80624C22.2687 3.88124 22.2687 5.62415 21.1938 6.69914L20.6982 7.19469C20.5539 7.16345 20.3722 7.11589 20.1651 7.04404C19.6108 6.85172 18.8823 6.48827 18.197 5.803C17.5117 5.11774 17.1483 4.38923 16.956 3.8349C16.8841 3.62781 16.8366 3.44609 16.8053 3.30179L17.3009 2.80624C18.3759 1.73125 20.1188 1.73125 21.1938 2.80624Z"
/>
<path
d="M14.5801 13.3128C14.1761 13.7168 13.9741 13.9188 13.7513 14.0926C13.4886 14.2975 13.2043 14.4732 12.9035 14.6166C12.6485 14.7381 12.3775 14.8284 11.8354 15.0091L8.97709 15.9619C8.71035 16.0508 8.41626 15.9814 8.21744 15.7826C8.01862 15.5837 7.9492 15.2897 8.03811 15.0229L8.99089 12.1646C9.17157 11.6225 9.26191 11.3515 9.38344 11.0965C9.52679 10.7957 9.70249 10.5114 9.90743 10.2487C10.0812 10.0259 10.2832 9.82394 10.6872 9.41993L15.6033 4.50385C15.867 5.19804 16.3293 6.05663 17.1363 6.86366C17.9434 7.67069 18.802 8.13296 19.4962 8.39674L14.5801 13.3128Z"
/>
<path
d="M20.5355 20.5355C22 19.0711 22 16.714 22 12C22 10.4517 22 9.15774 21.9481 8.0661L15.586 14.4283C15.2347 14.7797 14.9708 15.0437 14.6738 15.2753C14.3252 15.5473 13.948 15.7804 13.5488 15.9706C13.2088 16.1327 12.8546 16.2506 12.3833 16.4076L9.45143 17.3849C8.64568 17.6535 7.75734 17.4438 7.15678 16.8432C6.55621 16.2427 6.34651 15.3543 6.61509 14.5486L7.59235 11.6167C7.74936 11.1454 7.86732 10.7912 8.02935 10.4512C8.21958 10.052 8.45272 9.6748 8.72466 9.32615C8.9563 9.02918 9.22032 8.76528 9.57173 8.41404L15.9339 2.05188C14.8423 2 13.5483 2 12 2C7.28595 2 4.92893 2 3.46447 3.46447C2 4.92893 2 7.28595 2 12C2 16.714 2 19.0711 3.46447 20.5355C4.92893 22 7.28595 22 12 22C16.714 22 19.0711 22 20.5355 20.5355Z"
/>
</svg>
</label>
</div>
</div>
<div class="relative font-medium text-justify hyphens-auto">
<input type="checkbox" id="edit-description-button" class="hidden css-button"/>
<div
class="absolute h-full w-full min-h-[10em] z-50 top-1 right-0 gap-6 flex transition-all duration-200"
>
<img class="hidden md:block invisible rounded w-40 md:w-60 lg:w-80 object-fill" src="../documents/{{.Data.ID}}/cover"></img>
<form
method="POST"
action="./{{ .Data.ID }}/edit"
class="flex flex-col gap-2 w-full text-black bg-gray-200 rounded shadow-lg shadow-gray-500 dark:text-white dark:shadow-gray-900 dark:bg-gray-600 text-sm p-3"
>
<textarea
type="text"
id="description"
name="description"
class="h-full w-full p-1 bg-gray-400 text-black dark:bg-gray-700 dark:text-white"
>{{ or .Data.Description "N/A" }}</textarea>
<button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Save</button>
</form>
</div>
<p>{{ or .Data.Description "N/A" }}</p>
</div>
</div>
<style>
.css-button:checked + form {
.css-button:checked + div {
visibility: visible;
opacity: 1;
}
.css-button + form {
.css-button + div {
visibility: hidden;
opacity: 0;
}

View File

@ -43,7 +43,7 @@
</div>
<div class="inline-flex shrink-0 items-center">
<div>
<p class="text-gray-400">Minutes Read</p>
<p class="text-gray-400">Time Read</p>
<p class="font-medium">
{{ $doc.TotalTimeMinutes }} Minutes
</p>