Compare commits
1 Commits
7c6acad689
...
e68dfc445f
Author | SHA1 | Date | |
---|---|---|---|
e68dfc445f |
@ -227,7 +227,6 @@ func (api *API) generateTemplates() *multitemplate.Renderer {
|
|||||||
render := multitemplate.NewRenderer()
|
render := multitemplate.NewRenderer()
|
||||||
helperFuncs := template.FuncMap{
|
helperFuncs := template.FuncMap{
|
||||||
"dict": dict,
|
"dict": dict,
|
||||||
"slice": slice,
|
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
"getSVGGraphData": getSVGGraphData,
|
"getSVGGraphData": getSVGGraphData,
|
||||||
"getTimeZones": getTimeZones,
|
"getTimeZones": getTimeZones,
|
||||||
|
@ -366,13 +366,13 @@ func (api *API) appPerformAdminImport(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get import directory
|
// Get import directory
|
||||||
importDirectory := filepath.Clean(rAdminImport.Directory)
|
baseDirectory := filepath.Clean(rAdminImport.Directory)
|
||||||
|
|
||||||
// Get data directory
|
// Get data directory
|
||||||
absoluteDataPath, _ := filepath.Abs(filepath.Join(api.cfg.DataPath, "documents"))
|
absoluteDataPath, _ := filepath.Abs(filepath.Join(api.cfg.DataPath, "documents"))
|
||||||
|
|
||||||
// Validate different path
|
// Validate different path
|
||||||
if absoluteDataPath == importDirectory {
|
if absoluteDataPath == baseDirectory {
|
||||||
appErrorPage(c, http.StatusBadRequest, "Directory is the same as data path")
|
appErrorPage(c, http.StatusBadRequest, "Directory is the same as data path")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -397,7 +397,7 @@ func (api *API) appPerformAdminImport(c *gin.Context) {
|
|||||||
importResults := make([]importResult, 0)
|
importResults := make([]importResult, 0)
|
||||||
|
|
||||||
// Walk Directory & Import
|
// Walk Directory & Import
|
||||||
err = filepath.WalkDir(importDirectory, func(importPath string, f fs.DirEntry, err error) error {
|
err = filepath.WalkDir(baseDirectory, func(currentPath string, f fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -407,8 +407,7 @@ func (api *API) appPerformAdminImport(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get relative path
|
// Get relative path
|
||||||
basePath := importDirectory
|
relFilePath, err := filepath.Rel(baseDirectory, currentPath)
|
||||||
relFilePath, err := filepath.Rel(importDirectory, importPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("path error: %v", err)
|
log.Warnf("path error: %v", err)
|
||||||
return nil
|
return nil
|
||||||
@ -424,7 +423,7 @@ func (api *API) appPerformAdminImport(c *gin.Context) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Get metadata
|
// Get metadata
|
||||||
fileMeta, err := metadata.GetMetadata(importPath)
|
fileMeta, err := metadata.GetMetadata(currentPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("metadata error: %v", err)
|
log.Errorf("metadata error: %v", err)
|
||||||
iResult.Error = err
|
iResult.Error = err
|
||||||
@ -441,41 +440,15 @@ func (api *API) appPerformAdminImport(c *gin.Context) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import Copy
|
// TODO - Import Copy
|
||||||
if rAdminImport.Type == importCopy {
|
// newName := deriveBaseFileName(fileMeta)
|
||||||
// Derive & Sanitize File Name
|
|
||||||
relFilePath = deriveBaseFileName(fileMeta)
|
|
||||||
safePath := filepath.Join(api.cfg.DataPath, "documents", relFilePath)
|
|
||||||
|
|
||||||
// Open Source File
|
// Open File on Disk
|
||||||
srcFile, err := os.Open(importPath)
|
// file, err := os.Open(currentPath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Errorf("unable to open current file: %v", err)
|
// return err
|
||||||
iResult.Error = err
|
// }
|
||||||
return nil
|
// defer file.Close()
|
||||||
}
|
|
||||||
defer srcFile.Close()
|
|
||||||
|
|
||||||
// Open Destination File
|
|
||||||
destFile, err := os.Create(safePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("unable to open destination file: %v", err)
|
|
||||||
iResult.Error = err
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer destFile.Close()
|
|
||||||
|
|
||||||
// Copy File
|
|
||||||
if _, err = io.Copy(destFile, srcFile); err != nil {
|
|
||||||
log.Errorf("unable to save file: %v", err)
|
|
||||||
iResult.Error = err
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Base & Path
|
|
||||||
basePath = filepath.Join(api.cfg.DataPath, "documents")
|
|
||||||
iResult.Path = relFilePath
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upsert document
|
// Upsert document
|
||||||
if _, err = qtx.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
if _, err = qtx.UpsertDocument(api.db.Ctx, database.UpsertDocumentParams{
|
||||||
@ -486,7 +459,7 @@ func (api *API) appPerformAdminImport(c *gin.Context) {
|
|||||||
Md5: fileMeta.MD5,
|
Md5: fileMeta.MD5,
|
||||||
Words: fileMeta.WordCount,
|
Words: fileMeta.WordCount,
|
||||||
Filepath: &relFilePath,
|
Filepath: &relFilePath,
|
||||||
Basepath: &basePath,
|
Basepath: &baseDirectory,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Errorf("UpsertDocument DB Error: %v", err)
|
log.Errorf("UpsertDocument DB Error: %v", err)
|
||||||
iResult.Error = err
|
iResult.Error = err
|
||||||
|
@ -373,9 +373,10 @@ func (api *API) appGetDocumentProgress(c *gin.Context) {
|
|||||||
DocumentID: rDoc.DocumentID,
|
DocumentID: rDoc.DocumentID,
|
||||||
UserID: auth.UserName,
|
UserID: auth.UserName,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
log.Error("GetDocumentProgress DB Error: ", err)
|
log.Error("UpsertDocument DB Error: ", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDocumentProgress DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("UpsertDocument DB Error: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,8 +465,7 @@ func (api *API) appUploadNewDocument(c *gin.Context) {
|
|||||||
|
|
||||||
// Derive & Sanitize File Name
|
// Derive & Sanitize File Name
|
||||||
fileName := deriveBaseFileName(metadataInfo)
|
fileName := deriveBaseFileName(metadataInfo)
|
||||||
basePath := filepath.Join(api.cfg.DataPath, "documents")
|
safePath := filepath.Join(api.cfg.DataPath, "documents", fileName)
|
||||||
safePath := filepath.Join(basePath, fileName)
|
|
||||||
|
|
||||||
// Open Destination File
|
// Open Destination File
|
||||||
destFile, err := os.Create(safePath)
|
destFile, err := os.Create(safePath)
|
||||||
@ -492,7 +492,9 @@ func (api *API) appUploadNewDocument(c *gin.Context) {
|
|||||||
Md5: metadataInfo.MD5,
|
Md5: metadataInfo.MD5,
|
||||||
Words: metadataInfo.WordCount,
|
Words: metadataInfo.WordCount,
|
||||||
Filepath: &fileName,
|
Filepath: &fileName,
|
||||||
Basepath: &basePath,
|
|
||||||
|
// TODO (BasePath):
|
||||||
|
// - Should be current config directory
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Errorf("UpsertDocument DB Error: %v", err)
|
log.Errorf("UpsertDocument DB Error: %v", err)
|
||||||
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("UpsertDocument DB Error: %v", err))
|
appErrorPage(c, http.StatusInternalServerError, fmt.Sprintf("UpsertDocument DB Error: %v", err))
|
||||||
@ -803,9 +805,7 @@ func (api *API) appSaveNewDocument(c *gin.Context) {
|
|||||||
defer sourceFile.Close()
|
defer sourceFile.Close()
|
||||||
|
|
||||||
// Generate Storage Path & Open File
|
// Generate Storage Path & Open File
|
||||||
basePath := filepath.Join(api.cfg.DataPath, "documents")
|
safePath := filepath.Join(api.cfg.DataPath, "documents", fileName)
|
||||||
safePath := filepath.Join(basePath, fileName)
|
|
||||||
|
|
||||||
destFile, err := os.Create(safePath)
|
destFile, err := os.Create(safePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Dest File Error: ", err)
|
log.Error("Dest File Error: ", err)
|
||||||
@ -852,9 +852,8 @@ func (api *API) appSaveNewDocument(c *gin.Context) {
|
|||||||
Title: rDocAdd.Title,
|
Title: rDocAdd.Title,
|
||||||
Author: rDocAdd.Author,
|
Author: rDocAdd.Author,
|
||||||
Md5: fileHash,
|
Md5: fileHash,
|
||||||
Words: wordCount,
|
|
||||||
Filepath: &fileName,
|
Filepath: &fileName,
|
||||||
Basepath: &basePath,
|
Words: wordCount,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Error("UpsertDocument DB Error: ", err)
|
log.Error("UpsertDocument DB Error: ", err)
|
||||||
sendDownloadMessage("Unable to save to database", gin.H{"Error": true})
|
sendDownloadMessage("Unable to save to database", gin.H{"Error": true})
|
||||||
|
@ -499,8 +499,7 @@ func (api *API) koUploadExistingDocument(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Generate Storage Path
|
// Generate Storage Path
|
||||||
basePath := filepath.Join(api.cfg.DataPath, "documents")
|
safePath := filepath.Join(api.cfg.DataPath, "documents", fileName)
|
||||||
safePath := filepath.Join(basePath, fileName)
|
|
||||||
|
|
||||||
// Save & Prevent Overwrites
|
// Save & Prevent Overwrites
|
||||||
_, err = os.Stat(safePath)
|
_, err = os.Stat(safePath)
|
||||||
@ -527,7 +526,6 @@ func (api *API) koUploadExistingDocument(c *gin.Context) {
|
|||||||
Md5: metadataInfo.MD5,
|
Md5: metadataInfo.MD5,
|
||||||
Words: metadataInfo.WordCount,
|
Words: metadataInfo.WordCount,
|
||||||
Filepath: &fileName,
|
Filepath: &fileName,
|
||||||
Basepath: &basePath,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Error("UpsertDocument DB Error:", err)
|
log.Error("UpsertDocument DB Error:", err)
|
||||||
apiErrorPage(c, http.StatusBadRequest, "Document Error")
|
apiErrorPage(c, http.StatusBadRequest, "Document Error")
|
||||||
|
12
api/utils.go
12
api/utils.go
@ -108,11 +108,11 @@ func getSVGGraphData(inputData []database.GetDailyReadStatsRow, svgWidth int, sv
|
|||||||
return graph.GetSVGGraphData(intData, svgWidth, svgHeight)
|
return graph.GetSVGGraphData(intData, svgWidth, svgHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dict(values ...any) (map[string]any, error) {
|
func dict(values ...interface{}) (map[string]interface{}, error) {
|
||||||
if len(values)%2 != 0 {
|
if len(values)%2 != 0 {
|
||||||
return nil, errors.New("invalid dict call")
|
return nil, errors.New("invalid dict call")
|
||||||
}
|
}
|
||||||
dict := make(map[string]any, len(values)/2)
|
dict := make(map[string]interface{}, len(values)/2)
|
||||||
for i := 0; i < len(values); i += 2 {
|
for i := 0; i < len(values); i += 2 {
|
||||||
key, ok := values[i].(string)
|
key, ok := values[i].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -123,12 +123,12 @@ func dict(values ...any) (map[string]any, error) {
|
|||||||
return dict, nil
|
return dict, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fields(value any) (map[string]any, error) {
|
func fields(value interface{}) (map[string]interface{}, error) {
|
||||||
v := reflect.Indirect(reflect.ValueOf(value))
|
v := reflect.Indirect(reflect.ValueOf(value))
|
||||||
if v.Kind() != reflect.Struct {
|
if v.Kind() != reflect.Struct {
|
||||||
return nil, fmt.Errorf("%T is not a struct", value)
|
return nil, fmt.Errorf("%T is not a struct", value)
|
||||||
}
|
}
|
||||||
m := make(map[string]any)
|
m := make(map[string]interface{})
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
sv := t.Field(i)
|
sv := t.Field(i)
|
||||||
@ -137,10 +137,6 @@ func fields(value any) (map[string]any, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func slice(elements ...any) []any {
|
|
||||||
return elements
|
|
||||||
}
|
|
||||||
|
|
||||||
func deriveBaseFileName(metadataInfo *metadata.MetadataInfo) string {
|
func deriveBaseFileName(metadataInfo *metadata.MetadataInfo) string {
|
||||||
// Derive New FileName
|
// Derive New FileName
|
||||||
var newFileName string
|
var newFileName string
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,14 +0,0 @@
|
|||||||
<!-- Variant -->
|
|
||||||
{{ $baseClass := "transition duration-100 ease-in font-medium w-full h-full px-2 py-1 text-white" }}
|
|
||||||
{{ if eq .Variant "Secondary" }}
|
|
||||||
{{ $baseClass = printf "bg-black shadow-md hover:text-black hover:bg-white %s" $baseClass }}
|
|
||||||
{{ else }}
|
|
||||||
{{ $baseClass = printf "bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100 %s" $baseClass }}
|
|
||||||
{{ end }}
|
|
||||||
<!-- Type -->
|
|
||||||
{{ if eq .Type "Link" }}
|
|
||||||
<a href="{{ .URL }}" class="text-center {{ $baseClass }}" type="submit">{{ .Title }}</a>
|
|
||||||
{{ else }}
|
|
||||||
<button class="{{ $baseClass }}" type="submit" {{ if .FormName }} form="{{ .FormName}}" {{ end }}>{{ .Title }}
|
|
||||||
</button>
|
|
||||||
{{ end }}
|
|
@ -1,43 +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,20 +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,102 +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.ID }}?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.ID }}" />
|
|
||||||
</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,39 +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,28 +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">
|
|
||||||
<p>{{ index (fields $row) $key }}</p>
|
|
||||||
</td>
|
|
||||||
{{ end }}
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
@ -17,12 +17,10 @@
|
|||||||
placeholder="JQ Filter" />
|
placeholder="JQ Filter" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:w-60">
|
<button type="submit"
|
||||||
{{ template "component/button" (dict
|
class="px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2">
|
||||||
"Title" "Filter"
|
<span class="w-full">Filter</span>
|
||||||
"Variant" "Secondary"
|
</button>
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<!-- Required for iOS "Hover" Events (onclick) -->
|
<!-- Required for iOS "Hover" Events (onclick) -->
|
||||||
|
@ -21,12 +21,10 @@
|
|||||||
<label for="backup_documents">Documents</label>
|
<label for="backup_documents">Documents</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-40 h-10">
|
<button type="submit"
|
||||||
{{ template "component/button" (dict
|
class="w-40 px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2">
|
||||||
"Title" "Backup"
|
<span class="w-full">Backup</span>
|
||||||
"Variant" "Secondary"
|
</button>
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
<form method="POST"
|
<form method="POST"
|
||||||
enctype="multipart/form-data"
|
enctype="multipart/form-data"
|
||||||
@ -36,12 +34,10 @@
|
|||||||
<div class="flex items-center w-1/2">
|
<div class="flex items-center w-1/2">
|
||||||
<input type="file" accept=".zip" name="restore_file" class="w-full" />
|
<input type="file" accept=".zip" name="restore_file" class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-40 h-10">
|
<button type="submit"
|
||||||
{{ template "component/button" (dict
|
class="w-40 px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2">
|
||||||
"Title" "Restore"
|
<span class="w-full">Restore</span>
|
||||||
"Variant" "Secondary"
|
</button>
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{{ if .PasswordErrorMessage }}
|
{{ if .PasswordErrorMessage }}
|
||||||
@ -61,12 +57,10 @@
|
|||||||
<td class="py-2 float-right">
|
<td class="py-2 float-right">
|
||||||
<form action="./admin" method="POST">
|
<form action="./admin" method="POST">
|
||||||
<input type="text" name="action" value="METADATA_MATCH" class="hidden" />
|
<input type="text" name="action" value="METADATA_MATCH" class="hidden" />
|
||||||
<div class="w-40 h-10 text-base">
|
<button type="submit"
|
||||||
{{ template "component/button" (dict
|
class="w-40 px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2">
|
||||||
"Title" "Run"
|
<span class="w-full">Run</span>
|
||||||
"Variant" "Secondary"
|
</button>
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -77,12 +71,10 @@
|
|||||||
<td class="py-2 float-right">
|
<td class="py-2 float-right">
|
||||||
<form action="./admin" method="POST">
|
<form action="./admin" method="POST">
|
||||||
<input type="text" name="action" value="CACHE_TABLES" class="hidden" />
|
<input type="text" name="action" value="CACHE_TABLES" class="hidden" />
|
||||||
<div class="w-40 h-10 text-base">
|
<button type="submit"
|
||||||
{{ template "component/button" (dict
|
class="w-40 px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2">
|
||||||
"Title" "Run"
|
<span class="w-full">Run</span>
|
||||||
"Variant" "Secondary"
|
</button>
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -33,7 +33,8 @@
|
|||||||
action="./{{ .Data.ID }}/edit"
|
action="./{{ .Data.ID }}/edit"
|
||||||
class="flex flex-col gap-2 w-72 text-black dark:text-white text-sm">
|
class="flex flex-col gap-2 w-72 text-black dark:text-white text-sm">
|
||||||
<input type="file" id="cover_file" name="cover_file">
|
<input type="file" id="cover_file" name="cover_file">
|
||||||
{{ template "component/button" (dict "Title" "Upload Cover") }}
|
<button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
|
||||||
|
type="submit">Upload Cover</button>
|
||||||
</form>
|
</form>
|
||||||
<form method="POST"
|
<form method="POST"
|
||||||
action="./{{ .Data.ID }}/edit"
|
action="./{{ .Data.ID }}/edit"
|
||||||
@ -43,7 +44,8 @@
|
|||||||
id="remove_cover"
|
id="remove_cover"
|
||||||
name="remove_cover"
|
name="remove_cover"
|
||||||
class="hidden" />
|
class="hidden" />
|
||||||
{{ template "component/button" (dict "Title" "Remove Cover") }}
|
<button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
|
||||||
|
type="submit">Remove Cover</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@ -52,8 +54,9 @@
|
|||||||
<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">
|
<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"
|
<form method="POST"
|
||||||
action="./{{ .Data.ID }}/delete"
|
action="./{{ .Data.ID }}/delete"
|
||||||
class="text-black dark:text-white text-sm w-24">
|
class="text-black dark:text-white text-sm">
|
||||||
{{ template "component/button" (dict "Title" "Delete") }}
|
<button class="font-medium w-24 px-2 py-1 text-white bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
|
||||||
|
type="submit">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -83,7 +86,8 @@
|
|||||||
placeholder="ISBN 10 / ISBN 13"
|
placeholder="ISBN 10 / ISBN 13"
|
||||||
value="{{ or .Data.Isbn13 (or .Data.Isbn10 nil) }}"
|
value="{{ or .Data.Isbn13 (or .Data.Isbn10 nil) }}"
|
||||||
class="p-2 bg-gray-300 text-black dark:bg-gray-700 dark:text-white">
|
class="p-2 bg-gray-300 text-black dark:bg-gray-700 dark:text-white">
|
||||||
{{ template "component/button" (dict "Title" "Identify") }}
|
<button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
|
||||||
|
type="submit">Identify</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -96,18 +100,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid sm:grid-cols-2 justify-between gap-4 pb-4">
|
<div class="grid sm:grid-cols-2 justify-between gap-4 pb-4">
|
||||||
{{ template "component/key-val-edit" (dict
|
<div class="relative">
|
||||||
"Title" "Title"
|
<div class="text-gray-500 inline-flex gap-2 relative">
|
||||||
"Value" .Data.Title
|
<p>Title</p>
|
||||||
"URL" (printf "./%s/edit" .Data.ID)
|
<label class="my-auto" for="edit-title-button">{{ template "svg/edit" (dict "Size" 18) }}</label>
|
||||||
"FormValue" "title"
|
<input type="checkbox" id="edit-title-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">
|
||||||
{{ template "component/key-val-edit" (dict
|
<form method="POST"
|
||||||
"Title" "Author"
|
action="./{{ .Data.ID }}/edit"
|
||||||
"Value" .Data.Author
|
class="flex flex-col gap-2 text-black dark:text-white text-sm">
|
||||||
"URL" (printf "./%s/edit" .Data.ID)
|
<input type="text" id="title" name="title" value="{{ or .Data.Title "N/A" }}" class="p-2 bg-gray-300 text-black dark:bg-gray-700 dark:text-white">
|
||||||
"FormValue" "author"
|
<button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 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 class="relative">
|
||||||
|
<div class="text-gray-500 inline-flex gap-2 relative">
|
||||||
|
<p>Author</p>
|
||||||
|
<label class="my-auto" for="edit-author-button">{{ template "svg/edit" (dict "Size" 18) }}</label>
|
||||||
|
<input type="checkbox" id="edit-author-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="./{{ .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-2 bg-gray-300 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 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 class="relative">
|
<div class="relative">
|
||||||
<div class="text-gray-500 inline-flex gap-2 relative">
|
<div class="text-gray-500 inline-flex gap-2 relative">
|
||||||
<p>Time Read</p>
|
<p>Time Read</p>
|
||||||
@ -155,16 +181,119 @@
|
|||||||
id="description"
|
id="description"
|
||||||
name="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>
|
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") }}
|
<button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
|
||||||
|
type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<p>{{ or .Data.Description "N/A" }}</p>
|
<p>{{ or .Data.Description "N/A" }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ template "component/metadata" (dict
|
{{ if .MetadataError }}
|
||||||
"ID" .Data.ID
|
<div class="absolute top-0 left-0 w-full h-full z-50">
|
||||||
"Metadata" .Metadata
|
<div class="fixed top-0 left-0 bg-black opacity-50 w-screen h-screen"></div>
|
||||||
"Error" .MetadataError
|
<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>
|
||||||
|
<a href="/documents/{{ .Data.ID }}"
|
||||||
|
class="w-full text-center font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
|
||||||
|
type="submit">Back to Document</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
<!-- Metadata Info -->
|
||||||
|
{{ 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/{{ .Data.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.ID }}?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.ID }}">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="flex justify-end gap-4 m-4">
|
||||||
|
<a href="/documents/{{ .Data.ID }}"
|
||||||
|
class="w-24 text-center font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
|
||||||
|
type="submit">Cancel</a>
|
||||||
|
<button form="metadata-save"
|
||||||
|
class="w-24 font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
|
||||||
|
type="submit">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
<style>
|
||||||
|
.css-button:checked+div {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.css-button+div {
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -18,17 +18,58 @@
|
|||||||
placeholder="Search Author / Title" />
|
placeholder="Search Author / Title" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:w-60">
|
<button type="submit"
|
||||||
{{ template "component/button" (dict
|
class="px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2">
|
||||||
"Title" "Search"
|
<span class="w-full">Search</span>
|
||||||
"Variant" "Secondary"
|
</button>
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{{ range $doc := .Data }}
|
{{ range $doc := .Data }}
|
||||||
{{ template "component/document-card" $doc }}
|
<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/{{$doc.ID}}">
|
||||||
|
<img class="rounded object-cover h-full"
|
||||||
|
src="./documents/{{$doc.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 $doc.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 $doc.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">{{ $doc.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 $doc.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={{ $doc.ID }}">{{ template "svg/activity" }}</a>
|
||||||
|
{{ if $doc.Filepath }}
|
||||||
|
<a href="./documents/{{$doc.ID}}/file">{{ template "svg/download" }}</a>
|
||||||
|
{{ else }}
|
||||||
|
{{ template "svg/download" (dict "Disabled" true) }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex gap-4 justify-center mt-4 text-black dark:text-white">
|
<div class="w-full flex gap-4 justify-center mt-4 text-black dark:text-white">
|
||||||
|
@ -41,29 +41,82 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-4 md:grid-cols-4">
|
<div class="grid grid-cols-2 gap-4 md:grid-cols-4">
|
||||||
{{ template "component/info-card" (dict
|
<a href="./documents" class="w-full">
|
||||||
"Title" "Documents"
|
<div class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded">
|
||||||
"Size" .Data.DatabaseInfo.DocumentsSize
|
<div class="flex flex-col justify-around dark:text-white w-full text-sm">
|
||||||
"Link" "./documents"
|
<p class="text-2xl font-bold text-black dark:text-white">{{ .Data.DatabaseInfo.DocumentsSize }}</p>
|
||||||
)}}
|
<p class="text-sm text-gray-400">Documents</p>
|
||||||
{{ template "component/info-card" (dict
|
</div>
|
||||||
"Title" "Activity Records"
|
</div>
|
||||||
"Size" .Data.DatabaseInfo.ActivitySize
|
</a>
|
||||||
"Link" "./activity"
|
<a href="./activity" class="w-full">
|
||||||
)}}
|
<div class="flex gap-4 w-full p-4 bg-white shadow-lg dark:bg-gray-700 rounded">
|
||||||
{{ template "component/info-card" (dict
|
<div class="flex flex-col justify-around dark:text-white w-full text-sm">
|
||||||
"Title" "Progress Records"
|
<p class="text-2xl font-bold text-black dark:text-white">{{ .Data.DatabaseInfo.ActivitySize }}</p>
|
||||||
"Size" .Data.DatabaseInfo.ProgressSize
|
<p class="text-sm text-gray-400">Activity Records</p>
|
||||||
"Link" "./progress"
|
</div>
|
||||||
)}}
|
</div>
|
||||||
{{ template "component/info-card" (dict
|
</a>
|
||||||
"Title" "Devices"
|
<a href="./progress" class="w-full">
|
||||||
"Size" .Data.DatabaseInfo.DevicesSize
|
<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">{{ .Data.DatabaseInfo.ProgressSize }}</p>
|
||||||
|
<p class="text-sm text-gray-400">Progress Records</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div 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">{{ .Data.DatabaseInfo.DevicesSize }}</p>
|
||||||
|
<p class="text-sm text-gray-400">Devices</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
{{ range $item := .Data.Streaks }}
|
{{ range $item := .Data.Streaks }}
|
||||||
{{ template "component/streak-card" $item }}
|
<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 $item.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">{{ $item.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 $item.Window "WEEK" }} Current Weekly Streak {{ else }}
|
||||||
|
Current Daily Streak {{ end }}
|
||||||
|
</p>
|
||||||
|
<div class="flex items-end text-sm text-gray-400">
|
||||||
|
{{ $item.CurrentStreakStartDate }} ➞ {{ $item.CurrentStreakEndDate }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-end font-bold">{{ $item.CurrentStreak }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between pb-2 mb-2 text-sm">
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{{ if eq $item.Window "WEEK" }}
|
||||||
|
Best Weekly Streak
|
||||||
|
{{ else }}
|
||||||
|
Best Daily Streak
|
||||||
|
{{ end }}
|
||||||
|
</p>
|
||||||
|
<div class="flex items-end text-sm text-gray-400">{{ $item.MaxStreakStartDate }} ➞ {{ $item.MaxStreakEndDate }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-end font-bold">{{ $item.MaxStreak }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
@ -39,13 +39,6 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<!--
|
|
||||||
{{ template "component/table" (dict
|
|
||||||
"Columns" (slice "Author" "Title" "Device Name" "Percentage" "Created At")
|
|
||||||
"Keys" (slice "Author" "Title" "DeviceName" "Percentage" "CreatedAt")
|
|
||||||
"Rows" .Data
|
|
||||||
)}}
|
|
||||||
-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -30,12 +30,10 @@
|
|||||||
<option value="LibGen Non-fiction">LibGen Non-fiction</option>
|
<option value="LibGen Non-fiction">LibGen Non-fiction</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:w-60">
|
<button type="submit"
|
||||||
{{ template "component/button" (dict
|
class="px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2">
|
||||||
"Title" "Search"
|
<span class="w-full">Search</span>
|
||||||
"Variant" "Secondary"
|
</button>
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
{{ if .SearchErrorMessage }}
|
{{ if .SearchErrorMessage }}
|
||||||
<span class="text-red-400 text-xs">{{ .SearchErrorMessage }}</span>
|
<span class="text-red-400 text-xs">{{ .SearchErrorMessage }}</span>
|
||||||
|
@ -39,12 +39,10 @@
|
|||||||
placeholder="New Password" />
|
placeholder="New Password" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:w-60">
|
<button type="submit"
|
||||||
{{ template "component/button" (dict
|
class="px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2">
|
||||||
"Title" "Submit"
|
<span class="w-full">Submit</span>
|
||||||
"Variant" "Secondary"
|
</button>
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
{{ if .PasswordErrorMessage }}
|
{{ if .PasswordErrorMessage }}
|
||||||
<span class="text-red-400 text-xs">{{ .PasswordErrorMessage }}</span>
|
<span class="text-red-400 text-xs">{{ .PasswordErrorMessage }}</span>
|
||||||
@ -71,12 +69,10 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="lg:w-60">
|
<button type="submit"
|
||||||
{{ template "component/button" (dict
|
class="px-10 py-2 text-base font-semibold text-center text-white transition duration-200 ease-in bg-black shadow-md hover:text-black hover:bg-white focus:outline-none focus:ring-2">
|
||||||
"Title" "Submit"
|
<span class="w-full">Submit</span>
|
||||||
"Variant" "Secondary"
|
</button>
|
||||||
) }}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
{{ if .TimeOffsetErrorMessage }}
|
{{ if .TimeOffsetErrorMessage }}
|
||||||
<span class="text-red-400 text-xs">{{ .TimeOffsetErrorMessage }}</span>
|
<span class="text-red-400 text-xs">{{ .TimeOffsetErrorMessage }}</span>
|
||||||
|
Loading…
Reference in New Issue
Block a user