cleanup 1
This commit is contained in:
parent
99ccabed58
commit
01abea6bd6
@ -188,8 +188,6 @@ func (api *API) registerWebAppRoutes(router *gin.Engine) {
|
|||||||
// Search enabled configuration
|
// Search enabled configuration
|
||||||
if api.cfg.SearchEnabled {
|
if api.cfg.SearchEnabled {
|
||||||
router.GET("/search", api.authWebAppMiddleware, api.appGetSearchNew) // WIP
|
router.GET("/search", api.authWebAppMiddleware, api.appGetSearchNew) // WIP
|
||||||
|
|
||||||
router.GET("/search-old", api.authWebAppMiddleware, api.appGetSearch) // TODO
|
|
||||||
router.POST("/search", api.authWebAppMiddleware, api.appSaveNewDocument) // TODO
|
router.POST("/search", api.authWebAppMiddleware, api.appSaveNewDocument) // TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,10 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -23,7 +20,6 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"reichard.io/antholume/database"
|
"reichard.io/antholume/database"
|
||||||
"reichard.io/antholume/metadata"
|
"reichard.io/antholume/metadata"
|
||||||
"reichard.io/antholume/pkg/ptr"
|
|
||||||
"reichard.io/antholume/search"
|
"reichard.io/antholume/search"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -101,185 +97,6 @@ func (api *API) appDocumentReader(c *gin.Context) {
|
|||||||
c.FileFromFS("assets/reader/index.htm", http.FS(api.assets))
|
c.FileFromFS("assets/reader/index.htm", http.FS(api.assets))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) appGetDocuments(c *gin.Context) {
|
|
||||||
templateVars, auth := api.getBaseTemplateVars("documents", c)
|
|
||||||
qParams := bindQueryParams(c, 9)
|
|
||||||
|
|
||||||
var query *string
|
|
||||||
if qParams.Search != nil && *qParams.Search != "" {
|
|
||||||
search := "%" + *qParams.Search + "%"
|
|
||||||
query = &search
|
|
||||||
}
|
|
||||||
|
|
||||||
documents, err := api.db.Queries.GetDocumentsWithStats(c, database.GetDocumentsWithStatsParams{
|
|
||||||
UserID: auth.UserName,
|
|
||||||
Query: query,
|
|
||||||
Deleted: ptr.Of(false),
|
|
||||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
|
||||||
Limit: *qParams.Limit,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetDocumentsWithStats DB Error: ", err)
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentsWithStats DB Error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
length, err := api.db.Queries.GetDocumentsSize(c, query)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetDocumentsSize DB Error: ", err)
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentsSize DB Error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = api.getDocumentsWordCount(c, documents); err != nil {
|
|
||||||
log.Error("Unable to Get Word Counts: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
totalPages := int64(math.Ceil(float64(length) / float64(*qParams.Limit)))
|
|
||||||
nextPage := *qParams.Page + 1
|
|
||||||
previousPage := *qParams.Page - 1
|
|
||||||
|
|
||||||
if nextPage <= totalPages {
|
|
||||||
templateVars["NextPage"] = nextPage
|
|
||||||
}
|
|
||||||
|
|
||||||
if previousPage >= 0 {
|
|
||||||
templateVars["PreviousPage"] = previousPage
|
|
||||||
}
|
|
||||||
|
|
||||||
templateVars["PageLimit"] = *qParams.Limit
|
|
||||||
templateVars["Data"] = documents
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "page/documents", templateVars)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) appGetDocument(c *gin.Context) {
|
|
||||||
templateVars, auth := api.getBaseTemplateVars("document", c)
|
|
||||||
|
|
||||||
var rDocID requestDocumentID
|
|
||||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
|
||||||
log.Error("Invalid URI Bind")
|
|
||||||
appErrorPage(c, http.StatusNotFound, "Invalid document")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
document, err := api.db.GetDocument(c, rDocID.DocumentID, auth.UserName)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetDocument DB Error: ", err)
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocument DB Error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
templateVars["Data"] = document
|
|
||||||
templateVars["TotalTimeLeftSeconds"] = int64((100.0 - document.Percentage) * float64(document.SecondsPerPercent))
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "page/document", templateVars)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) appGetProgress(c *gin.Context) {
|
|
||||||
templateVars, auth := api.getBaseTemplateVars("progress", c)
|
|
||||||
|
|
||||||
qParams := bindQueryParams(c, 15)
|
|
||||||
|
|
||||||
progressFilter := database.GetProgressParams{
|
|
||||||
UserID: auth.UserName,
|
|
||||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
|
||||||
Limit: *qParams.Limit,
|
|
||||||
}
|
|
||||||
|
|
||||||
if qParams.Document != nil {
|
|
||||||
progressFilter.DocFilter = true
|
|
||||||
progressFilter.DocumentID = *qParams.Document
|
|
||||||
}
|
|
||||||
|
|
||||||
progress, err := api.db.Queries.GetProgress(c, progressFilter)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetProgress DB Error: ", err)
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetActivity DB Error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
templateVars["Data"] = progress
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "page/progress", templateVars)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) appGetActivity(c *gin.Context) {
|
|
||||||
templateVars, auth := api.getBaseTemplateVars("activity", c)
|
|
||||||
qParams := bindQueryParams(c, 15)
|
|
||||||
|
|
||||||
activityFilter := database.GetActivityParams{
|
|
||||||
UserID: auth.UserName,
|
|
||||||
Offset: (*qParams.Page - 1) * *qParams.Limit,
|
|
||||||
Limit: *qParams.Limit,
|
|
||||||
}
|
|
||||||
|
|
||||||
if qParams.Document != nil {
|
|
||||||
activityFilter.DocFilter = true
|
|
||||||
activityFilter.DocumentID = *qParams.Document
|
|
||||||
}
|
|
||||||
|
|
||||||
activity, err := api.db.Queries.GetActivity(c, activityFilter)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetActivity DB Error: ", err)
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetActivity DB Error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
templateVars["Data"] = activity
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "page/activity", templateVars)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) appGetHome(c *gin.Context) {
|
|
||||||
templateVars, auth := api.getBaseTemplateVars("home", c)
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
graphData, err := api.db.Queries.GetDailyReadStats(c, auth.UserName)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetDailyReadStats DB Error: ", err)
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDailyReadStats DB Error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Debug("GetDailyReadStats DB Performance: ", time.Since(start))
|
|
||||||
|
|
||||||
start = time.Now()
|
|
||||||
databaseInfo, err := api.db.Queries.GetDatabaseInfo(c, auth.UserName)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetDatabaseInfo DB Error: ", err)
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDatabaseInfo DB Error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Debug("GetDatabaseInfo DB Performance: ", time.Since(start))
|
|
||||||
|
|
||||||
start = time.Now()
|
|
||||||
streaks, err := api.db.Queries.GetUserStreaks(c, auth.UserName)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetUserStreaks DB Error: ", err)
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUserStreaks DB Error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Debug("GetUserStreaks DB Performance: ", time.Since(start))
|
|
||||||
|
|
||||||
start = time.Now()
|
|
||||||
userStatistics, err := api.db.Queries.GetUserStatistics(c)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetUserStatistics DB Error: ", err)
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUserStatistics DB Error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Debug("GetUserStatistics DB Performance: ", time.Since(start))
|
|
||||||
|
|
||||||
templateVars["Data"] = gin.H{
|
|
||||||
"Streaks": streaks,
|
|
||||||
"GraphData": graphData,
|
|
||||||
"DatabaseInfo": databaseInfo,
|
|
||||||
"UserStatistics": arrangeUserStatistics(userStatistics),
|
|
||||||
}
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "page/home", templateVars)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) appGetSettings(c *gin.Context) {
|
func (api *API) appGetSettings(c *gin.Context) {
|
||||||
templateVars, auth := api.getBaseTemplateVars("settings", c)
|
templateVars, auth := api.getBaseTemplateVars("settings", c)
|
||||||
|
|
||||||
@ -305,38 +122,6 @@ func (api *API) appGetSettings(c *gin.Context) {
|
|||||||
c.HTML(http.StatusOK, "page/settings", templateVars)
|
c.HTML(http.StatusOK, "page/settings", templateVars)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tabs:
|
|
||||||
// - General (Import, Backup & Restore, Version (githash?), Stats?)
|
|
||||||
// - Users
|
|
||||||
// - Metadata
|
|
||||||
func (api *API) appGetSearch(c *gin.Context) {
|
|
||||||
templateVars, _ := api.getBaseTemplateVars("search", c)
|
|
||||||
|
|
||||||
var sParams searchParams
|
|
||||||
err := c.BindQuery(&sParams)
|
|
||||||
if err != nil {
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Invalid Form Bind: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only Handle Query
|
|
||||||
if sParams.Query != nil && sParams.Source != nil {
|
|
||||||
// Search
|
|
||||||
searchResults, err := search.SearchBook(*sParams.Query, *sParams.Source)
|
|
||||||
if err != nil {
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("Search Error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
templateVars["Data"] = searchResults
|
|
||||||
templateVars["Source"] = *sParams.Source
|
|
||||||
} else if sParams.Query != nil || sParams.Source != nil {
|
|
||||||
templateVars["SearchErrorMessage"] = "Invalid Query"
|
|
||||||
}
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "page/search", templateVars)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) appGetLogin(c *gin.Context) {
|
func (api *API) appGetLogin(c *gin.Context) {
|
||||||
templateVars, _ := api.getBaseTemplateVars("login", c)
|
templateVars, _ := api.getBaseTemplateVars("login", c)
|
||||||
templateVars["RegistrationEnabled"] = api.cfg.RegistrationEnabled
|
templateVars["RegistrationEnabled"] = api.cfg.RegistrationEnabled
|
||||||
@ -617,85 +402,6 @@ func (api *API) appDeleteDocument(c *gin.Context) {
|
|||||||
c.Redirect(http.StatusFound, "../")
|
c.Redirect(http.StatusFound, "../")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) appIdentifyDocument(c *gin.Context) {
|
|
||||||
var rDocID requestDocumentID
|
|
||||||
if err := c.ShouldBindUri(&rDocID); err != nil {
|
|
||||||
log.Error("Invalid URI Bind")
|
|
||||||
appErrorPage(c, http.StatusNotFound, "Invalid document")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var rDocIdentify requestDocumentIdentify
|
|
||||||
if err := c.ShouldBind(&rDocIdentify); err != nil {
|
|
||||||
log.Error("Invalid Form Bind")
|
|
||||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disallow Empty Strings
|
|
||||||
if rDocIdentify.Title != nil && strings.TrimSpace(*rDocIdentify.Title) == "" {
|
|
||||||
rDocIdentify.Title = nil
|
|
||||||
}
|
|
||||||
if rDocIdentify.Author != nil && strings.TrimSpace(*rDocIdentify.Author) == "" {
|
|
||||||
rDocIdentify.Author = nil
|
|
||||||
}
|
|
||||||
if rDocIdentify.ISBN != nil && strings.TrimSpace(*rDocIdentify.ISBN) == "" {
|
|
||||||
rDocIdentify.ISBN = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate Values
|
|
||||||
if rDocIdentify.ISBN == nil && rDocIdentify.Title == nil && rDocIdentify.Author == nil {
|
|
||||||
log.Error("Invalid Form")
|
|
||||||
appErrorPage(c, http.StatusBadRequest, "Invalid or missing form values")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Template Variables
|
|
||||||
templateVars, auth := api.getBaseTemplateVars("document", c)
|
|
||||||
|
|
||||||
// Get Metadata
|
|
||||||
metadataResults, err := metadata.SearchMetadata(metadata.SourceGoogleBooks, metadata.MetadataInfo{
|
|
||||||
Title: rDocIdentify.Title,
|
|
||||||
Author: rDocIdentify.Author,
|
|
||||||
ISBN10: rDocIdentify.ISBN,
|
|
||||||
ISBN13: rDocIdentify.ISBN,
|
|
||||||
})
|
|
||||||
if err == nil && len(metadataResults) > 0 {
|
|
||||||
firstResult := metadataResults[0]
|
|
||||||
|
|
||||||
// Store First Metadata Result
|
|
||||||
if _, err = api.db.Queries.AddMetadata(c, database.AddMetadataParams{
|
|
||||||
DocumentID: rDocID.DocumentID,
|
|
||||||
Title: firstResult.Title,
|
|
||||||
Author: firstResult.Author,
|
|
||||||
Description: firstResult.Description,
|
|
||||||
Gbid: firstResult.SourceID,
|
|
||||||
Olid: nil,
|
|
||||||
Isbn10: firstResult.ISBN10,
|
|
||||||
Isbn13: firstResult.ISBN13,
|
|
||||||
}); err != nil {
|
|
||||||
log.Error("AddMetadata DB Error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
templateVars["Metadata"] = firstResult
|
|
||||||
} else {
|
|
||||||
log.Warn("Metadata Error")
|
|
||||||
templateVars["MetadataError"] = "No Metadata Found"
|
|
||||||
}
|
|
||||||
|
|
||||||
document, err := api.db.GetDocument(c, rDocID.DocumentID, auth.UserName)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("GetDocument DB Error: ", err)
|
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocument DB Error: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
templateVars["Data"] = document
|
|
||||||
templateVars["TotalTimeLeftSeconds"] = int64((100.0 - document.Percentage) * float64(document.SecondsPerPercent))
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "page/document", templateVars)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) appSaveNewDocument(c *gin.Context) {
|
func (api *API) appSaveNewDocument(c *gin.Context) {
|
||||||
var rDocAdd requestDocumentAdd
|
var rDocAdd requestDocumentAdd
|
||||||
if err := c.ShouldBind(&rDocAdd); err != nil {
|
if err := c.ShouldBind(&rDocAdd); err != nil {
|
||||||
@ -1018,80 +724,3 @@ func appErrorPage(c *gin.Context, errorCode int, errorMessage string) {
|
|||||||
"Message": errorMessage,
|
"Message": errorMessage,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func arrangeUserStatistics(userStatistics []database.GetUserStatisticsRow) gin.H {
|
|
||||||
// Item Sorter
|
|
||||||
sortItem := func(userStatistics []database.GetUserStatisticsRow, key string, less func(i int, j int) bool) []map[string]any {
|
|
||||||
sortedData := append([]database.GetUserStatisticsRow(nil), userStatistics...)
|
|
||||||
sort.SliceStable(sortedData, less)
|
|
||||||
|
|
||||||
newData := make([]map[string]any, 0)
|
|
||||||
for _, item := range sortedData {
|
|
||||||
v := reflect.Indirect(reflect.ValueOf(item))
|
|
||||||
|
|
||||||
var value string
|
|
||||||
if strings.Contains(key, "Wpm") {
|
|
||||||
rawVal := v.FieldByName(key).Float()
|
|
||||||
value = fmt.Sprintf("%.2f WPM", rawVal)
|
|
||||||
} else if strings.Contains(key, "Seconds") {
|
|
||||||
rawVal := v.FieldByName(key).Int()
|
|
||||||
value = niceSeconds(rawVal)
|
|
||||||
} else if strings.Contains(key, "Words") {
|
|
||||||
rawVal := v.FieldByName(key).Int()
|
|
||||||
value = niceNumbers(rawVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
newData = append(newData, map[string]any{
|
|
||||||
"UserID": item.UserID,
|
|
||||||
"Value": value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return newData
|
|
||||||
}
|
|
||||||
|
|
||||||
return gin.H{
|
|
||||||
"WPM": gin.H{
|
|
||||||
"All": sortItem(userStatistics, "TotalWpm", func(i, j int) bool {
|
|
||||||
return userStatistics[i].TotalWpm > userStatistics[j].TotalWpm
|
|
||||||
}),
|
|
||||||
"Year": sortItem(userStatistics, "YearlyWpm", func(i, j int) bool {
|
|
||||||
return userStatistics[i].YearlyWpm > userStatistics[j].YearlyWpm
|
|
||||||
}),
|
|
||||||
"Month": sortItem(userStatistics, "MonthlyWpm", func(i, j int) bool {
|
|
||||||
return userStatistics[i].MonthlyWpm > userStatistics[j].MonthlyWpm
|
|
||||||
}),
|
|
||||||
"Week": sortItem(userStatistics, "WeeklyWpm", func(i, j int) bool {
|
|
||||||
return userStatistics[i].WeeklyWpm > userStatistics[j].WeeklyWpm
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
"Duration": gin.H{
|
|
||||||
"All": sortItem(userStatistics, "TotalSeconds", func(i, j int) bool {
|
|
||||||
return userStatistics[i].TotalSeconds > userStatistics[j].TotalSeconds
|
|
||||||
}),
|
|
||||||
"Year": sortItem(userStatistics, "YearlySeconds", func(i, j int) bool {
|
|
||||||
return userStatistics[i].YearlySeconds > userStatistics[j].YearlySeconds
|
|
||||||
}),
|
|
||||||
"Month": sortItem(userStatistics, "MonthlySeconds", func(i, j int) bool {
|
|
||||||
return userStatistics[i].MonthlySeconds > userStatistics[j].MonthlySeconds
|
|
||||||
}),
|
|
||||||
"Week": sortItem(userStatistics, "WeeklySeconds", func(i, j int) bool {
|
|
||||||
return userStatistics[i].WeeklySeconds > userStatistics[j].WeeklySeconds
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
"Words": gin.H{
|
|
||||||
"All": sortItem(userStatistics, "TotalWordsRead", func(i, j int) bool {
|
|
||||||
return userStatistics[i].TotalWordsRead > userStatistics[j].TotalWordsRead
|
|
||||||
}),
|
|
||||||
"Year": sortItem(userStatistics, "YearlyWordsRead", func(i, j int) bool {
|
|
||||||
return userStatistics[i].YearlyWordsRead > userStatistics[j].YearlyWordsRead
|
|
||||||
}),
|
|
||||||
"Month": sortItem(userStatistics, "MonthlyWordsRead", func(i, j int) bool {
|
|
||||||
return userStatistics[i].MonthlyWordsRead > userStatistics[j].MonthlyWordsRead
|
|
||||||
}),
|
|
||||||
"Week": sortItem(userStatistics, "WeeklyWordsRead", func(i, j int) bool {
|
|
||||||
return userStatistics[i].WeeklyWordsRead > userStatistics[j].WeeklyWordsRead
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,28 +0,0 @@
|
|||||||
{{ template "base" . }}
|
|
||||||
{{ define "title" }}Activity{{ end }}
|
|
||||||
{{ define "header" }}<a href="./activity">Activity</a>{{ end }}
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<div class="inline-block min-w-full overflow-hidden rounded shadow">
|
|
||||||
<!-- Table Component - Utilizes Template "table-cell" -->
|
|
||||||
{{ template "component/table" (dict
|
|
||||||
"Columns" (slice "Document" "Time" "Duration" "Percent")
|
|
||||||
"Keys" (slice "Document" "StartTime" "Duration" "EndPercentage")
|
|
||||||
"Rows" .Data
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
<!-- Table Cell Definition -->
|
|
||||||
{{ define "table-cell" }}
|
|
||||||
{{ if eq .Name "Document" }}
|
|
||||||
<a href="./documents/{{ .Data.DocumentID }}"
|
|
||||||
>{{ .Data.Author }} - {{ .Data.Title }}</a
|
|
||||||
>
|
|
||||||
{{ else if eq .Name "EndPercentage" }}
|
|
||||||
{{ index (fields .Data) .Name }}%
|
|
||||||
{{ else }}
|
|
||||||
{{ index (fields .Data) .Name }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
@ -1,50 +0,0 @@
|
|||||||
<div class="w-full relative">
|
|
||||||
<div
|
|
||||||
class="flex gap-4 w-full h-full p-4 shadow-lg bg-white dark:bg-gray-700 rounded"
|
|
||||||
>
|
|
||||||
<div class="min-w-fit my-auto h-48 relative">
|
|
||||||
<a href="./documents/{{ .ID }}">
|
|
||||||
<img
|
|
||||||
class="rounded object-cover h-full"
|
|
||||||
src="./documents/{{ .ID }}/cover"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col justify-around dark:text-white w-full text-sm">
|
|
||||||
<div class="inline-flex shrink-0 items-center">
|
|
||||||
<div>
|
|
||||||
<p class="text-gray-400">Title</p>
|
|
||||||
<p class="font-medium">{{ or .Title "Unknown" }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="inline-flex shrink-0 items-center">
|
|
||||||
<div>
|
|
||||||
<p class="text-gray-400">Author</p>
|
|
||||||
<p class="font-medium">{{ or .Author "Unknown" }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="inline-flex shrink-0 items-center">
|
|
||||||
<div>
|
|
||||||
<p class="text-gray-400">Progress</p>
|
|
||||||
<p class="font-medium">{{ .Percentage }}%</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="inline-flex shrink-0 items-center">
|
|
||||||
<div>
|
|
||||||
<p class="text-gray-400">Time Read</p>
|
|
||||||
<p class="font-medium">{{ niceSeconds .TotalTimeSeconds }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="absolute flex flex-col gap-2 right-4 bottom-4 text-gray-500 dark:text-gray-400"
|
|
||||||
>
|
|
||||||
<a href="./activity?document={{ .ID }}">{{ template "svg/activity" }}</a>
|
|
||||||
{{ if .Filepath }}
|
|
||||||
<a href="./documents/{{ .ID }}/file">{{ template "svg/download" }}</a>
|
|
||||||
{{ else }}
|
|
||||||
{{ template "svg/download" (dict "Disabled" true) }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,12 +0,0 @@
|
|||||||
{{ if .Link }}<a href="{{ .Link }}" {{ else }} <div {{ end }}class="w-full">
|
|
||||||
<div class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded">
|
|
||||||
<div class="flex flex-col justify-around dark:text-white w-full text-sm">
|
|
||||||
<p class="text-2xl font-bold text-black dark:text-white">{{ .Size }}</p>
|
|
||||||
<p class="text-sm text-gray-400">{{ .Title }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ if .Link }}
|
|
||||||
</a>
|
|
||||||
{{ else }}
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
@ -1,32 +0,0 @@
|
|||||||
<div class="relative">
|
|
||||||
<div class="text-gray-500 inline-flex gap-2 relative">
|
|
||||||
<p>{{ .Title }}</p>
|
|
||||||
<label class="my-auto cursor-pointer" for="edit-{{ .FormValue }}-button">
|
|
||||||
{{ template "svg/edit" (dict "Size" 18) }}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="edit-{{ .FormValue }}-button"
|
|
||||||
class="hidden css-button"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="absolute z-30 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="{{ .URL }}"
|
|
||||||
class="flex flex-col gap-2 text-black dark:text-white text-sm"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="{{ .FormValue }}"
|
|
||||||
name="{{ .FormValue }}"
|
|
||||||
value="{{ or .Value "N/A" }}"
|
|
||||||
class="p-2 bg-gray-300 text-black dark:bg-gray-700 dark:text-white"
|
|
||||||
/>
|
|
||||||
{{ template "component/button" (dict "Title" "Save") }}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="font-medium text-lg">{{ or .Value "N/A" }}</p>
|
|
||||||
</div>
|
|
@ -1,64 +0,0 @@
|
|||||||
<div class="w-full">
|
|
||||||
<div class="flex flex-col justify-between h-full w-full px-4 py-6 bg-white shadow-lg dark:bg-gray-700 rounded">
|
|
||||||
<div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<p class="text-sm font-semibold text-gray-700 border-b border-gray-200 w-max dark:text-white dark:border-gray-500">
|
|
||||||
{{ .Name }} Leaderboard
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-2 text-xs text-gray-400 items-center">
|
|
||||||
<label for="all-{{ .Name }}"
|
|
||||||
class="cursor-pointer hover:text-black dark:hover:text-white">all</label>
|
|
||||||
<label for="year-{{ .Name }}"
|
|
||||||
class="cursor-pointer hover:text-black dark:hover:text-white">year</label>
|
|
||||||
<label for="month-{{ .Name }}"
|
|
||||||
class="cursor-pointer hover:text-black dark:hover:text-white">month</label>
|
|
||||||
<label for="week-{{ .Name }}"
|
|
||||||
class="cursor-pointer hover:text-black dark:hover:text-white">week</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="radio"
|
|
||||||
name="options-{{ .Name }}"
|
|
||||||
id="all-{{ .Name }}"
|
|
||||||
class="hidden peer/All"
|
|
||||||
checked />
|
|
||||||
<input type="radio"
|
|
||||||
name="options-{{ .Name }}"
|
|
||||||
id="year-{{ .Name }}"
|
|
||||||
class="hidden peer/Year" />
|
|
||||||
<input type="radio"
|
|
||||||
name="options-{{ .Name }}"
|
|
||||||
id="month-{{ .Name }}"
|
|
||||||
class="hidden peer/Month" />
|
|
||||||
<input type="radio"
|
|
||||||
name="options-{{ .Name }}"
|
|
||||||
id="week-{{ .Name }}"
|
|
||||||
class="hidden peer/Week" />
|
|
||||||
{{ range $key, $data := .Data }}
|
|
||||||
<div class="flex items-end my-6 space-x-2 hidden peer-checked/{{ $key }}:block">
|
|
||||||
{{ $length := len $data }}
|
|
||||||
{{ if eq $length 0 }}
|
|
||||||
<p class="text-5xl font-bold text-black dark:text-white">N/A</p>
|
|
||||||
{{ else }}
|
|
||||||
<p class="text-5xl font-bold text-black dark:text-white">{{ (index $data 0).UserID }}</p>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
<div class="hidden dark:text-white peer-checked/{{ $key }}:block">
|
|
||||||
{{ range $index, $item := $data }}
|
|
||||||
{{ if lt $index 3 }}
|
|
||||||
{{ if eq $index 0 }}
|
|
||||||
<div class="flex items-center justify-between pt-2 pb-2 text-sm">
|
|
||||||
{{ else }}
|
|
||||||
<div class="flex items-center justify-between pt-2 pb-2 text-sm border-t border-gray-200">
|
|
||||||
{{ end }}
|
|
||||||
<div>
|
|
||||||
<p>{{ $item.UserID }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-end font-bold">{{ $item.Value }}</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
{{ end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,147 +0,0 @@
|
|||||||
{{ if .Error }}
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full z-50">
|
|
||||||
<div class="fixed top-0 left-0 bg-black opacity-50 w-screen h-screen"></div>
|
|
||||||
<div
|
|
||||||
class="relative flex flex-col gap-4 p-4 max-h-[95%] bg-white dark:bg-gray-800 overflow-scroll -translate-x-2/4 -translate-y-2/4 top-1/2 left-1/2 w-5/6 overflow-hidden shadow rounded"
|
|
||||||
>
|
|
||||||
<div class="text-center">
|
|
||||||
<h3 class="text-lg font-bold leading-6 dark:text-gray-300">
|
|
||||||
No Metadata Results Found
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
{{ template "component/button" (dict
|
|
||||||
"Title" "Back to Document"
|
|
||||||
"Type" "Link"
|
|
||||||
"URL" (printf "/documents/%s" .ID)
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
{{ if .Metadata }}
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full z-50">
|
|
||||||
<div class="fixed top-0 left-0 bg-black opacity-50 w-screen h-screen"></div>
|
|
||||||
<div
|
|
||||||
class="relative max-h-[95%] bg-white dark:bg-gray-800 overflow-scroll -translate-x-2/4 -translate-y-2/4 top-1/2 left-1/2 w-5/6 overflow-hidden shadow rounded"
|
|
||||||
>
|
|
||||||
<div class="py-5 text-center">
|
|
||||||
<h3 class="text-lg font-bold leading-6 dark:text-gray-300">
|
|
||||||
Metadata Results
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<form
|
|
||||||
id="metadata-save"
|
|
||||||
method="POST"
|
|
||||||
action="/documents/{{ .ID }}/edit"
|
|
||||||
class="text-black dark:text-white border-b dark:border-black"
|
|
||||||
>
|
|
||||||
<dl>
|
|
||||||
<div
|
|
||||||
class="p-3 bg-gray-100 dark:bg-gray-900 grid grid-cols-3 gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<dt class="my-auto font-medium text-gray-500">Cover</dt>
|
|
||||||
<dd class="mt-1 text-sm sm:mt-0 sm:col-span-2">
|
|
||||||
<img
|
|
||||||
class="rounded object-fill h-32"
|
|
||||||
src="https://books.google.com/books/content/images/frontcover/{{ .Metadata.SourceID }}?fife=w480-h690"
|
|
||||||
/>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="p-3 bg-white dark:bg-gray-800 grid grid-cols-3 gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<dt class="my-auto font-medium text-gray-500">Title</dt>
|
|
||||||
<dd class="mt-1 text-sm sm:mt-0 sm:col-span-2">
|
|
||||||
{{ or .Metadata.Title "N/A" }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="p-3 bg-gray-100 dark:bg-gray-900 grid grid-cols-3 gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<dt class="my-auto font-medium text-gray-500">Author</dt>
|
|
||||||
<dd class="mt-1 text-sm sm:mt-0 sm:col-span-2">
|
|
||||||
{{ or .Metadata.Author "N/A" }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="p-3 bg-white dark:bg-gray-800 grid grid-cols-3 gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<dt class="my-auto font-medium text-gray-500">ISBN 10</dt>
|
|
||||||
<dd class="mt-1 text-sm sm:mt-0 sm:col-span-2">
|
|
||||||
{{ or .Metadata.ISBN10 "N/A" }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="p-3 bg-gray-100 dark:bg-gray-900 grid grid-cols-3 gap-4 sm:px-6"
|
|
||||||
>
|
|
||||||
<dt class="my-auto font-medium text-gray-500">ISBN 13</dt>
|
|
||||||
<dd class="mt-1 text-sm sm:mt-0 sm:col-span-2">
|
|
||||||
{{ or .Metadata.ISBN13 "N/A" }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="p-3 bg-white dark:bg-gray-800 sm:grid sm:grid-cols-3 sm:gap-4 px-6"
|
|
||||||
>
|
|
||||||
<dt class="my-auto font-medium text-gray-500">Description</dt>
|
|
||||||
<dd class="max-h-[10em] overflow-scroll mt-1 sm:mt-0 sm:col-span-2">
|
|
||||||
{{ or .Metadata.Description "N/A" }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
<div class="hidden">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="title"
|
|
||||||
name="title"
|
|
||||||
value="{{ .Metadata.Title }}"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="author"
|
|
||||||
name="author"
|
|
||||||
value="{{ .Metadata.Author }}"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="description"
|
|
||||||
name="description"
|
|
||||||
value="{{ .Metadata.Description }}"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="isbn_10"
|
|
||||||
name="isbn_10"
|
|
||||||
value="{{ .Metadata.ISBN10 }}"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="isbn_13"
|
|
||||||
name="isbn_13"
|
|
||||||
value="{{ .Metadata.ISBN13 }}"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="cover_gbid"
|
|
||||||
name="cover_gbid"
|
|
||||||
value="{{ .Metadata.SourceID }}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div class="flex justify-end">
|
|
||||||
<div class="flex gap-4 m-4 w-48">
|
|
||||||
{{ template "component/button" (dict
|
|
||||||
"Title" "Cancel"
|
|
||||||
"Type" "Link"
|
|
||||||
"URL" (printf "/documents/%s" .ID)
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
{{ template "component/button" (dict
|
|
||||||
"Title" "Save"
|
|
||||||
"FormName" "metadata-save"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
@ -1,54 +0,0 @@
|
|||||||
<div class="w-full">
|
|
||||||
<div
|
|
||||||
class="relative w-full px-4 py-6 bg-white shadow-lg dark:bg-gray-700 rounded"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
class="text-sm font-semibold text-gray-700 border-b border-gray-200 w-max dark:text-white dark:border-gray-500"
|
|
||||||
>
|
|
||||||
{{ if eq .Window "WEEK" }}
|
|
||||||
Weekly Read Streak
|
|
||||||
{{ else }}
|
|
||||||
Daily Read Streak
|
|
||||||
{{ end }}
|
|
||||||
</p>
|
|
||||||
<div class="flex items-end my-6 space-x-2">
|
|
||||||
<p class="text-5xl font-bold text-black dark:text-white">
|
|
||||||
{{ .CurrentStreak }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="dark:text-white">
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-between pb-2 mb-2 text-sm border-b border-gray-200"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
{{ if eq .Window "WEEK" }}
|
|
||||||
Current Weekly Streak
|
|
||||||
{{ else }}
|
|
||||||
Current Daily Streak
|
|
||||||
{{ end }}
|
|
||||||
</p>
|
|
||||||
<div class="flex items-end text-sm text-gray-400">
|
|
||||||
{{ .CurrentStreakStartDate }} ➞ {{ .CurrentStreakEndDate }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-end font-bold">{{ .CurrentStreak }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between pb-2 mb-2 text-sm">
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
{{ if eq .Window "WEEK" }}
|
|
||||||
Best Weekly Streak
|
|
||||||
{{ else }}
|
|
||||||
Best Daily Streak
|
|
||||||
{{ end }}
|
|
||||||
</p>
|
|
||||||
<div class="flex items-end text-sm text-gray-400">
|
|
||||||
{{ .MaxStreakStartDate }} ➞ {{ .MaxStreakEndDate }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-end font-bold">{{ .MaxStreak }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,32 +0,0 @@
|
|||||||
{{ $rows := .Rows }}
|
|
||||||
{{ $cols := .Columns }}
|
|
||||||
{{ $keys := .Keys }}
|
|
||||||
<table class="min-w-full leading-normal bg-white dark:bg-gray-700 text-sm">
|
|
||||||
<thead class="text-gray-800 dark:text-gray-400">
|
|
||||||
<tr>
|
|
||||||
{{ range $col := $cols }}
|
|
||||||
<th
|
|
||||||
class="p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
|
||||||
>
|
|
||||||
{{ $col }}
|
|
||||||
</th>
|
|
||||||
{{ end }}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="text-black dark:text-white">
|
|
||||||
{{ if not $rows }}
|
|
||||||
<tr>
|
|
||||||
<td class="text-center p-3" colspan="4">No Results</td>
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
{{ range $row := $rows }}
|
|
||||||
<tr>
|
|
||||||
{{ range $key := $keys }}
|
|
||||||
<td class="p-3 border-b border-gray-200">
|
|
||||||
{{ template "table-cell" (dict "Data" $row "Name" $key ) }}
|
|
||||||
</td>
|
|
||||||
{{ end }}
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
@ -1,28 +0,0 @@
|
|||||||
{{ template "base" . }}
|
|
||||||
{{ define "title" }}Activity{{ end }}
|
|
||||||
{{ define "header" }}<a href="./activity">Activity</a>{{ end }}
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<div class="inline-block min-w-full overflow-hidden rounded shadow">
|
|
||||||
<!-- Table Component - Utilizes Template "table-cell" -->
|
|
||||||
{{ template "component/table" (dict
|
|
||||||
"Columns" (slice "Document" "Time" "Duration" "Percent")
|
|
||||||
"Keys" (slice "Document" "StartTime" "Duration" "EndPercentage")
|
|
||||||
"Rows" .Data
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
<!-- Table Cell Definition -->
|
|
||||||
{{ define "table-cell" }}
|
|
||||||
{{ if eq .Name "Document" }}
|
|
||||||
<a href="./documents/{{ .Data.DocumentID }}"
|
|
||||||
>{{ .Data.Author }} - {{ .Data.Title }}</a
|
|
||||||
>
|
|
||||||
{{ else if eq .Name "EndPercentage" }}
|
|
||||||
{{ index (fields .Data) .Name }}%
|
|
||||||
{{ else }}
|
|
||||||
{{ index (fields .Data) .Name }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
@ -1,254 +0,0 @@
|
|||||||
{{ template "base" . }}
|
|
||||||
{{ define "title" }}Documents{{ end }}
|
|
||||||
{{ define "header" }}<a href="/documents">Documents</a>{{ end }}
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="h-full w-full relative">
|
|
||||||
<!-- Document Info -->
|
|
||||||
<div
|
|
||||||
class="h-full w-full overflow-scroll bg-white shadow-lg dark:bg-gray-700 rounded dark:text-white p-4"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex flex-col gap-2 float-left w-44 md:w-60 lg:w-80 mr-4 mb-2 relative"
|
|
||||||
>
|
|
||||||
<label class="z-10 cursor-pointer" for="edit-cover-button">
|
|
||||||
<img
|
|
||||||
class="rounded object-fill w-full"
|
|
||||||
src="/documents/{{ .Data.ID }}/cover"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
{{ if .Data.Filepath }}
|
|
||||||
<a
|
|
||||||
href="/reader#id={{ .Data.ID }}&type=REMOTE"
|
|
||||||
class="z-10 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm text-center py-1 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
|
|
||||||
>Read</a
|
|
||||||
>
|
|
||||||
{{ end }}
|
|
||||||
<div class="flex flex-wrap-reverse justify-between z-20 gap-2 relative">
|
|
||||||
<div class="min-w-[50%] md:mr-2">
|
|
||||||
<div class="flex gap-1 text-sm">
|
|
||||||
<p class="text-gray-500">ISBN-10:</p>
|
|
||||||
<p class="font-medium">{{ or .Data.Isbn10 "N/A" }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-1 text-sm">
|
|
||||||
<p class="text-gray-500">ISBN-13:</p>
|
|
||||||
<p class="font-medium">{{ or .Data.Isbn13 "N/A" }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex grow justify-between my-auto text-gray-500 dark:text-gray-500"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="edit-cover-button"
|
|
||||||
class="hidden css-button"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="absolute z-30 flex flex-col gap-2 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_file" name="cover_file" />
|
|
||||||
{{ template "component/button" (dict "Title" "Upload Cover") }}
|
|
||||||
</form>
|
|
||||||
<form
|
|
||||||
method="POST"
|
|
||||||
action="./{{ .Data.ID }}/edit"
|
|
||||||
class="flex flex-col gap-2 w-72 text-black dark:text-white text-sm"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked
|
|
||||||
id="remove_cover"
|
|
||||||
name="remove_cover"
|
|
||||||
class="hidden"
|
|
||||||
/>
|
|
||||||
{{ template "component/button" (dict "Title" "Remove Cover") }}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="relative">
|
|
||||||
<label for="delete-button" class="cursor-pointer"
|
|
||||||
>{{ template "svg/delete" (dict "Size" 28) }}</label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="delete-button"
|
|
||||||
class="hidden css-button"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="absolute z-30 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="text-black dark:text-white text-sm w-24"
|
|
||||||
>
|
|
||||||
{{ template "component/button" (dict "Title" "Delete") }}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a href="../activity?document={{ .Data.ID }}"
|
|
||||||
>{{ template "svg/activity" (dict "Size" 28) }}</a
|
|
||||||
>
|
|
||||||
<div class="relative">
|
|
||||||
<label for="search-button"
|
|
||||||
>{{ template "svg/search" (dict "Size" 28) }}</label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="search-button"
|
|
||||||
class="hidden css-button"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="absolute z-30 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="flex flex-col gap-2 text-black dark:text-white text-sm"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="title"
|
|
||||||
name="title"
|
|
||||||
placeholder="Title"
|
|
||||||
value="{{ or .Data.Title nil }}"
|
|
||||||
class="p-2 bg-gray-300 text-black dark:bg-gray-700 dark:text-white"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="author"
|
|
||||||
name="author"
|
|
||||||
placeholder="Author"
|
|
||||||
value="{{ or .Data.Author nil }}"
|
|
||||||
class="p-2 bg-gray-300 text-black dark:bg-gray-700 dark:text-white"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="isbn"
|
|
||||||
name="isbn"
|
|
||||||
placeholder="ISBN 10 / ISBN 13"
|
|
||||||
value="{{ or .Data.Isbn13 (or .Data.Isbn10 nil) }}"
|
|
||||||
class="p-2 bg-gray-300 text-black dark:bg-gray-700 dark:text-white"
|
|
||||||
/>
|
|
||||||
{{ template "component/button" (dict "Title" "Identify") }}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ if .Data.Filepath }}
|
|
||||||
<a href="./{{ .Data.ID }}/file"
|
|
||||||
>{{ template "svg/download" (dict "Size" 28) }}</a
|
|
||||||
>
|
|
||||||
{{ else }}
|
|
||||||
{{ template "svg/download" (dict "Size" 28 "Disabled" true) }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid sm:grid-cols-2 justify-between gap-4 pb-4">
|
|
||||||
{{ template "component/key-val-edit" (dict
|
|
||||||
"Title" "Title"
|
|
||||||
"Value" .Data.Title
|
|
||||||
"URL" (printf "./%s/edit" .Data.ID)
|
|
||||||
"FormValue" "title"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
{{ template "component/key-val-edit" (dict
|
|
||||||
"Title" "Author"
|
|
||||||
"Value" .Data.Author
|
|
||||||
"URL" (printf "./%s/edit" .Data.ID)
|
|
||||||
"FormValue" "author"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
<div class="relative">
|
|
||||||
<div class="text-gray-500 inline-flex gap-2 relative">
|
|
||||||
<p>Time Read</p>
|
|
||||||
<label class="my-auto" for="progress-info-button"
|
|
||||||
>{{ template "svg/info" (dict "Size" 18) }}</label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="progress-info-button"
|
|
||||||
class="hidden css-button"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="absolute z-30 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"
|
|
||||||
>
|
|
||||||
<div class="text-xs flex">
|
|
||||||
<p class="text-gray-400 w-32">Seconds / Percent</p>
|
|
||||||
<p class="font-medium dark:text-white">
|
|
||||||
{{ .Data.SecondsPerPercent }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="text-xs flex">
|
|
||||||
<p class="text-gray-400 w-32">Words / Minute</p>
|
|
||||||
<p class="font-medium dark:text-white">{{ .Data.Wpm }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="text-xs flex">
|
|
||||||
<p class="text-gray-400 w-32">Est. Time Left</p>
|
|
||||||
<p class="font-medium dark:text-white whitespace-nowrap">
|
|
||||||
{{ niceSeconds .TotalTimeLeftSeconds }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="font-medium text-lg">
|
|
||||||
{{ niceSeconds .Data.TotalTimeSeconds }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="text-gray-500">Progress</p>
|
|
||||||
<p class="font-medium text-lg">{{ .Data.Percentage }}%</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="relative">
|
|
||||||
<div class="text-gray-500 inline-flex gap-2 relative">
|
|
||||||
<p>Description</p>
|
|
||||||
<label class="my-auto" for="edit-description-button"
|
|
||||||
>{{ template "svg/edit" (dict "Size" 18) }}</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-30 top-1 right-0 gap-4 flex transition-all duration-200"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="hidden md:block invisible rounded w-44 md:w-60 lg:w-80 object-fill"
|
|
||||||
src="/documents/{{ .Data.ID }}/cover"
|
|
||||||
/>
|
|
||||||
<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-2 bg-gray-300 text-black dark:bg-gray-700 dark:text-white"
|
|
||||||
>
|
|
||||||
{{ or .Data.Description "N/A" }}</textarea
|
|
||||||
>
|
|
||||||
{{ template "component/button" (dict "Title" "Save") }}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<p>{{ or .Data.Description "N/A" }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ template "component/metadata" (dict
|
|
||||||
"ID" .Data.ID
|
|
||||||
"Metadata" .Metadata
|
|
||||||
"Error" .MetadataError
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
@ -1,99 +0,0 @@
|
|||||||
{{ template "base" . }}
|
|
||||||
{{ define "title" }}Documents{{ end }}
|
|
||||||
{{ define "header" }}<a href="./documents">Documents</a>{{ end }}
|
|
||||||
{{ define "content" }}
|
|
||||||
<div
|
|
||||||
class="flex flex-col gap-2 grow p-4 mb-4 rounded shadow-lg bg-white dark:bg-gray-700 text-gray-500 dark:text-white"
|
|
||||||
>
|
|
||||||
<form
|
|
||||||
class="flex gap-4 flex-col lg:flex-row"
|
|
||||||
action="./documents"
|
|
||||||
method="GET"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col w-full grow">
|
|
||||||
<div class="flex relative">
|
|
||||||
<span
|
|
||||||
class="inline-flex items-center px-3 border-t bg-white border-l border-b border-gray-300 text-gray-500 shadow-sm text-sm"
|
|
||||||
>
|
|
||||||
{{ template "svg/search2" (dict "Size" 15) }}
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="search"
|
|
||||||
name="search"
|
|
||||||
class="flex-1 appearance-none rounded-none border border-gray-300 w-full py-2 px-2 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"
|
|
||||||
placeholder="Search Author / Title"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="lg:w-60">
|
|
||||||
{{ template "component/button" (dict
|
|
||||||
"Title" "Search"
|
|
||||||
"Variant" "Secondary"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
||||||
{{ range $doc := .Data }}
|
|
||||||
{{ template "component/document-card" $doc }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
<div class="w-full flex gap-4 justify-center mt-4 text-black dark:text-white">
|
|
||||||
{{ if .PreviousPage }}
|
|
||||||
<a
|
|
||||||
href="./documents?page={{ .PreviousPage }}&limit={{ .PageLimit }}"
|
|
||||||
class="bg-white shadow-lg dark:bg-gray-600 hover:bg-gray-400 font-medium rounded text-sm text-center p-2 w-24 dark:hover:bg-gray-700 focus:outline-none"
|
|
||||||
>◄</a
|
|
||||||
>
|
|
||||||
{{ end }}
|
|
||||||
{{ if .NextPage }}
|
|
||||||
<a
|
|
||||||
href="./documents?page={{ .NextPage }}&limit={{ .PageLimit }}"
|
|
||||||
class="bg-white shadow-lg dark:bg-gray-600 hover:bg-gray-400 font-medium rounded text-sm text-center p-2 w-24 dark:hover:bg-gray-700 focus:outline-none"
|
|
||||||
>►</a
|
|
||||||
>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="fixed bottom-6 right-6 rounded-full flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<input type="checkbox" id="upload-file-button" class="hidden css-button" />
|
|
||||||
<div
|
|
||||||
class="absolute right-0 z-10 bottom-0 rounded p-4 bg-gray-800 dark:bg-gray-200 text-white dark:text-black w-72 text-sm flex flex-col gap-2"
|
|
||||||
>
|
|
||||||
<form
|
|
||||||
method="POST"
|
|
||||||
enctype="multipart/form-data"
|
|
||||||
action="./documents"
|
|
||||||
class="flex flex-col gap-2"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept=".epub"
|
|
||||||
id="document_file"
|
|
||||||
name="document_file"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="font-medium px-2 py-1 text-gray-800 bg-gray-500 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Upload File
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<label for="upload-file-button">
|
|
||||||
<div
|
|
||||||
class="w-full text-center cursor-pointer font-medium mt-2 px-2 py-1 text-gray-800 bg-gray-500 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-800"
|
|
||||||
>
|
|
||||||
Cancel Upload
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<label
|
|
||||||
class="w-16 h-16 bg-gray-800 dark:bg-gray-200 rounded-full flex items-center justify-center opacity-30 hover:opacity-100 transition-all duration-200 cursor-pointer"
|
|
||||||
for="upload-file-button"
|
|
||||||
>{{ template "svg/upload" (dict "Size" 34) }}</label
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
@ -1,109 +0,0 @@
|
|||||||
{{ template "base" . }}
|
|
||||||
{{ define "title" }}Home{{ end }}
|
|
||||||
{{ define "header" }}<a href="./">Home</a>{{ end }}
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
<div class="w-full">
|
|
||||||
<div class="relative w-full bg-white shadow-lg dark:bg-gray-700 rounded">
|
|
||||||
<p
|
|
||||||
class="absolute top-3 left-5 text-sm font-semibold text-gray-700 border-b border-gray-200 w-max dark:text-white dark:border-gray-500"
|
|
||||||
>
|
|
||||||
Daily Read Totals
|
|
||||||
</p>
|
|
||||||
{{ $data := (getSVGGraphData .Data.GraphData 800 70 ) }}
|
|
||||||
<div class="relative">
|
|
||||||
<svg
|
|
||||||
viewBox="26 0 755 {{ $data.Height }}"
|
|
||||||
preserveAspectRatio="none"
|
|
||||||
width="100%"
|
|
||||||
height="6em"
|
|
||||||
>
|
|
||||||
<!-- Bezier Line Graph -->
|
|
||||||
<path
|
|
||||||
fill="#316BBE"
|
|
||||||
fill-opacity="0.5"
|
|
||||||
stroke="none"
|
|
||||||
d="{{ $data.BezierPath }} {{ $data.BezierFill }}"
|
|
||||||
/>
|
|
||||||
<path fill="none" stroke="#316BBE" d="{{ $data.BezierPath }}" />
|
|
||||||
</svg>
|
|
||||||
<div
|
|
||||||
class="flex absolute w-full h-full top-0"
|
|
||||||
style="width: calc(100%*31/30);
|
|
||||||
transform: translateX(-50%);
|
|
||||||
left: 50%"
|
|
||||||
>
|
|
||||||
{{ range $index, $item := $data.LinePoints }}
|
|
||||||
<!-- Required for iOS "Hover" Events (onclick) -->
|
|
||||||
<div
|
|
||||||
onclick
|
|
||||||
class="opacity-0 hover:opacity-100 w-full"
|
|
||||||
style="background: linear-gradient(rgba(128, 128, 128, 0.5), rgba(128, 128, 128, 0.5)) no-repeat center/2px 100%"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex flex-col items-center p-2 rounded absolute top-3 dark:text-white text-xs pointer-events-none"
|
|
||||||
style="transform: translateX(-50%);
|
|
||||||
background-color: rgba(128, 128, 128, 0.2);
|
|
||||||
left: 50%"
|
|
||||||
>
|
|
||||||
<span>{{ (index $.Data.GraphData $index).Date }}</span>
|
|
||||||
<span
|
|
||||||
>{{ (index $.Data.GraphData $index).MinutesRead }}
|
|
||||||
minutes</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-4 md:grid-cols-4">
|
|
||||||
{{ template "component/info-card" (dict
|
|
||||||
"Title" "Documents"
|
|
||||||
"Size" .Data.DatabaseInfo.DocumentsSize
|
|
||||||
"Link" "./documents"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
{{ template "component/info-card" (dict
|
|
||||||
"Title" "Activity Records"
|
|
||||||
"Size" .Data.DatabaseInfo.ActivitySize
|
|
||||||
"Link" "./activity"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
{{ template "component/info-card" (dict
|
|
||||||
"Title" "Progress Records"
|
|
||||||
"Size" .Data.DatabaseInfo.ProgressSize
|
|
||||||
"Link" "./progress"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
{{ template "component/info-card" (dict
|
|
||||||
"Title" "Devices"
|
|
||||||
"Size" .Data.DatabaseInfo.DevicesSize
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
||||||
{{ range $item := .Data.Streaks }}
|
|
||||||
{{ template "component/streak-card" $item }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
||||||
{{ template "component/leaderboard-card" (dict
|
|
||||||
"Name" "WPM"
|
|
||||||
"Data" .Data.UserStatistics.WPM
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
{{ template "component/leaderboard-card" (dict
|
|
||||||
"Name" "Duration"
|
|
||||||
"Data" .Data.UserStatistics.Duration
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
{{ template "component/leaderboard-card" (dict
|
|
||||||
"Name" "Words"
|
|
||||||
"Data" .Data.UserStatistics.Words
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
@ -1,28 +0,0 @@
|
|||||||
{{ template "base" . }}
|
|
||||||
{{ define "title" }}Progress{{ end }}
|
|
||||||
{{ define "header" }}<a href="./progress">Progress</a>{{ end }}
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<div class="inline-block min-w-full overflow-hidden rounded shadow">
|
|
||||||
<!-- Table Component - Utilizes Template "table-cell" -->
|
|
||||||
{{ template "component/table" (dict
|
|
||||||
"Columns" (slice "Document" "Device Name" "Percentage" "Created At")
|
|
||||||
"Keys" (slice "Document" "DeviceName" "Percentage" "CreatedAt")
|
|
||||||
"Rows" .Data
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
<!-- Table Cell Definition -->
|
|
||||||
{{ define "table-cell" }}
|
|
||||||
{{ if eq .Name "Document" }}
|
|
||||||
<a href="./documents/{{ .Data.DocumentID }}"
|
|
||||||
>{{ .Data.Author }} - {{ .Data.Title }}</a
|
|
||||||
>
|
|
||||||
{{ else if eq .Name "Percentage" }}
|
|
||||||
{{ index (fields .Data) .Name }}%
|
|
||||||
{{ else }}
|
|
||||||
{{ index (fields .Data) .Name }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
@ -1,157 +0,0 @@
|
|||||||
{{ template "base" . }}
|
|
||||||
{{ define "title" }}Search{{ end }}
|
|
||||||
{{ define "header" }}<a href="./search">Search</a>{{ end }}
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="w-full flex flex-col md:flex-row gap-4">
|
|
||||||
<div class="flex flex-col gap-4 grow">
|
|
||||||
<div
|
|
||||||
class="flex flex-col gap-2 grow p-4 rounded shadow-lg bg-white dark:bg-gray-700 text-gray-500 dark:text-white"
|
|
||||||
>
|
|
||||||
<form class="flex gap-4 flex-col lg:flex-row" action="./search">
|
|
||||||
<div class="flex flex-col w-full grow">
|
|
||||||
<div class="flex relative">
|
|
||||||
<span
|
|
||||||
class="inline-flex items-center px-3 border-t bg-white border-l border-b border-gray-300 text-gray-500 shadow-sm text-sm"
|
|
||||||
>
|
|
||||||
{{ template "svg/search2" (dict "Size" 15) }}
|
|
||||||
</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="query"
|
|
||||||
name="query"
|
|
||||||
class="flex-1 appearance-none rounded-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"
|
|
||||||
placeholder="Query"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex relative min-w-[12em]">
|
|
||||||
<span
|
|
||||||
class="inline-flex items-center px-3 border-t bg-white border-l border-b border-gray-300 text-gray-500 shadow-sm text-sm"
|
|
||||||
>
|
|
||||||
{{ template "svg/documents" (dict "Size" 15) }}
|
|
||||||
</span>
|
|
||||||
<select
|
|
||||||
class="flex-1 appearance-none rounded-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-purple-600 focus:border-transparent"
|
|
||||||
id="source"
|
|
||||||
name="source"
|
|
||||||
>
|
|
||||||
<option value="LibGen">Library Genesis</option>
|
|
||||||
<option value="Annas Archive">Annas Archive</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="lg:w-60">
|
|
||||||
{{ template "component/button" (dict
|
|
||||||
"Title" "Search"
|
|
||||||
"Variant" "Secondary"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{{ if .SearchErrorMessage }}
|
|
||||||
<span class="text-red-400 text-xs">{{ .SearchErrorMessage }}</span>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
<div class="inline-block min-w-full overflow-hidden rounded shadow">
|
|
||||||
<table
|
|
||||||
class="min-w-full leading-normal bg-white dark:bg-gray-700 text-sm md:text-sm"
|
|
||||||
>
|
|
||||||
<thead class="text-gray-800 dark:text-gray-400">
|
|
||||||
<tr>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="w-12 p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
|
||||||
></th>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
|
||||||
>
|
|
||||||
Document
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
|
||||||
>
|
|
||||||
Series
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
|
||||||
>
|
|
||||||
Type
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="p-3 font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
|
||||||
>
|
|
||||||
Size
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="p-3 hidden md:block font-normal text-left uppercase border-b border-gray-200 dark:border-gray-800"
|
|
||||||
>
|
|
||||||
Date
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="text-black dark:text-white">
|
|
||||||
{{ if not .Data }}
|
|
||||||
<tr>
|
|
||||||
<td class="text-center p-3" colspan="6">No Results</td>
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
{{ range $item := .Data }}
|
|
||||||
<tr>
|
|
||||||
<td
|
|
||||||
class="p-3 border-b border-gray-200 text-gray-500 dark:text-gray-500"
|
|
||||||
>
|
|
||||||
<form action="./search" method="POST">
|
|
||||||
<input
|
|
||||||
class="hidden"
|
|
||||||
type="text"
|
|
||||||
id="source"
|
|
||||||
name="source"
|
|
||||||
value="{{ $.Source }}"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
class="hidden"
|
|
||||||
type="text"
|
|
||||||
id="title"
|
|
||||||
name="title"
|
|
||||||
value="{{ $item.Title }}"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
class="hidden"
|
|
||||||
type="text"
|
|
||||||
id="author"
|
|
||||||
name="author"
|
|
||||||
value="{{ $item.Author }}"
|
|
||||||
/>
|
|
||||||
<button name="id" value="{{ $item.ID }}">
|
|
||||||
{{ template "svg/download" }}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
<td class="p-3 border-b border-gray-200">
|
|
||||||
{{ $item.Author }} -
|
|
||||||
{{ $item.Title }}
|
|
||||||
</td>
|
|
||||||
<td class="p-3 border-b border-gray-200">
|
|
||||||
<p>{{ or $item.Series "N/A" }}</p>
|
|
||||||
</td>
|
|
||||||
<td class="p-3 border-b border-gray-200">
|
|
||||||
<p>{{ or $item.FileType "N/A" }}</p>
|
|
||||||
</td>
|
|
||||||
<td class="p-3 border-b border-gray-200">
|
|
||||||
<p>{{ or $item.FileSize "N/A" }}</p>
|
|
||||||
</td>
|
|
||||||
<td class="hidden md:table-cell p-3 border-b border-gray-200">
|
|
||||||
<p>{{ or $item.UploadDate "N/A" }}</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
Loading…
x
Reference in New Issue
Block a user