[add] docker instructions, [add] metadata gathering, [add] screenshots
							
								
								
									
										38
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						@ -1,19 +1,35 @@
 | 
				
			|||||||
FROM alpine:edge AS build
 | 
					# Certificate Store
 | 
				
			||||||
RUN apk add --no-cache --update go gcc g++
 | 
					FROM alpine as certs
 | 
				
			||||||
WORKDIR /app
 | 
					RUN apk update && apk add ca-certificates
 | 
				
			||||||
COPY . /app
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy Resources
 | 
					# Build Image
 | 
				
			||||||
 | 
					FROM --platform=$BUILDPLATFORM golang:1.20 AS build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Install Dependencies
 | 
				
			||||||
 | 
					RUN apt-get update -y
 | 
				
			||||||
 | 
					RUN apt install -y gcc-x86-64-linux-gnu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create Package Directory
 | 
				
			||||||
 | 
					WORKDIR /src
 | 
				
			||||||
RUN mkdir -p /opt/bookmanager
 | 
					RUN mkdir -p /opt/bookmanager
 | 
				
			||||||
RUN cp -a ./templates /opt/bookmanager/templates
 | 
					 | 
				
			||||||
RUN cp -a ./assets /opt/bookmanager/assets
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Download Dependencies & Compile
 | 
					# Cache Dependencies & Compile
 | 
				
			||||||
RUN go mod download
 | 
					ARG TARGETOS
 | 
				
			||||||
RUN CGO_ENABLED=1 CGO_CFLAGS="-D_LARGEFILE64_SOURCE" go build -o /opt/bookmanager/server
 | 
					ARG TARGETARCH
 | 
				
			||||||
 | 
					RUN --mount=target=. \
 | 
				
			||||||
 | 
					    --mount=type=cache,target=/root/.cache/go-build \
 | 
				
			||||||
 | 
					    --mount=type=cache,target=/go/pkg \
 | 
				
			||||||
 | 
					    if [ "$TARGETARCH" = "amd64" ]; then \
 | 
				
			||||||
 | 
						GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=1 CGO_CFLAGS="-D_LARGEFILE64_SOURCE" CC=x86_64-linux-gnu-gcc go build -o /opt/bookmanager/server; \
 | 
				
			||||||
 | 
					    else \
 | 
				
			||||||
 | 
						GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=1 CGO_CFLAGS="-D_LARGEFILE64_SOURCE" go build -o /opt/bookmanager/server; \
 | 
				
			||||||
 | 
					    fi; \
 | 
				
			||||||
 | 
					    cp -a ./templates /opt/bookmanager/templates; \
 | 
				
			||||||
 | 
					    cp -a ./assets /opt/bookmanager/assets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Create Image
 | 
					# Create Image
 | 
				
			||||||
FROM alpine:3.18
 | 
					FROM busybox:1.36
 | 
				
			||||||
 | 
					COPY --from=certs /etc/ssl/certs /etc/ssl/certs
 | 
				
			||||||
COPY --from=build /opt/bookmanager /opt/bookmanager
 | 
					COPY --from=build /opt/bookmanager /opt/bookmanager
 | 
				
			||||||
WORKDIR /opt/bookmanager
 | 
					WORKDIR /opt/bookmanager
 | 
				
			||||||
EXPOSE 8585
 | 
					EXPOSE 8585
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						@ -14,9 +14,13 @@ docker_build_local:
 | 
				
			|||||||
docker_build_release_beta:
 | 
					docker_build_release_beta:
 | 
				
			||||||
	docker buildx build \
 | 
						docker buildx build \
 | 
				
			||||||
		--platform linux/amd64,linux/arm64 \
 | 
							--platform linux/amd64,linux/arm64 \
 | 
				
			||||||
		-t gitea.va.reichard.io/reichard/bookmanager:beta --push .
 | 
							-t gitea.va.reichard.io/reichard/bookmanager:beta \
 | 
				
			||||||
 | 
							-t gitea.va.reichard.io/evan/bookmanager:beta \
 | 
				
			||||||
 | 
							--push .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
docker_build_release_latest:
 | 
					docker_build_release_latest:
 | 
				
			||||||
	docker buildx build \
 | 
						docker buildx build \
 | 
				
			||||||
		--platform linux/amd64,linux/arm64 \
 | 
							--platform linux/amd64,linux/arm64 \
 | 
				
			||||||
		-t gitea.va.reichard.io/reichard/bookmanager:latest --push .
 | 
							-t gitea.va.reichard.io/reichard/bookmanager:latest \
 | 
				
			||||||
 | 
							-t gitea.va.reichard.io/evan/bookmanager:latest \
 | 
				
			||||||
 | 
							--push .
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										60
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@ -1,14 +1,29 @@
 | 
				
			|||||||
# Book Manager
 | 
					# Book Manager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<p align="center">
 | 
					<p align="center">
 | 
				
			||||||
    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web_login.png">
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/login.png">
 | 
				
			||||||
        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web_login.png" width="30%">
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/login.png" width="19%">
 | 
				
			||||||
    </a>
 | 
					    </a>
 | 
				
			||||||
    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web_home.png">
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/home.png">
 | 
				
			||||||
        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web_home.png" width="30%">
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/home.png" width="19%">
 | 
				
			||||||
    </a>
 | 
					    </a>
 | 
				
			||||||
    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web_documents.png">
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/documents.png">
 | 
				
			||||||
        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web_documents.png" width="30%">
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/documents.png" width="19%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/document.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/document.png" width="19%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/metadata.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/metadata.png" width="19%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p align="center">
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/src/branch/master/screenshots/web/README.md">
 | 
				
			||||||
 | 
					        --- WEB ---
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/src/branch/master/screenshots/pwa/README.md">
 | 
				
			||||||
 | 
					        --- PWA ---
 | 
				
			||||||
    </a>
 | 
					    </a>
 | 
				
			||||||
</p>
 | 
					</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -24,11 +39,30 @@ In additional to the compatible KOSync API's, we add:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
- Additional APIs to automatically upload reading statistics
 | 
					- Additional APIs to automatically upload reading statistics
 | 
				
			||||||
- Automatically upload documents to the server (can download in the "Documents" view)
 | 
					- Automatically upload documents to the server (can download in the "Documents" view)
 | 
				
			||||||
- Automatic book cover metadata scraping (Thanks [OpenLibrary](https://openlibrary.org/))
 | 
					- Book metadata scraping (Thanks [OpenLibrary](https://openlibrary.org/) & [Google Books API](https://developers.google.com/books/docs/v1/getting_started))
 | 
				
			||||||
- No JavaScript! All information is rendered server side.
 | 
					- No JavaScript! All information is rendered server side.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Server
 | 
					# Server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Docker Image: `docker pull gitea.va.reichard.io/evan/bookmanager:latest`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Quick Start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# Make Data Directory
 | 
				
			||||||
 | 
					mkdir -p bookmanager_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Run Server
 | 
				
			||||||
 | 
					docker run \
 | 
				
			||||||
 | 
					    -p 8585:8585 \
 | 
				
			||||||
 | 
					    -e REGISTRATION_ENABLED=true \
 | 
				
			||||||
 | 
					    -v ./bookmanager_data:/config \
 | 
				
			||||||
 | 
					    -v ./bookmanager_data:/data \
 | 
				
			||||||
 | 
					    gitea.va.reichard.io/evan/bookmanager:latest
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The service is now accessible at: `http://localhost:8585`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Configuration
 | 
					## Configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Environment Variable | Default Value | Description                                                          |
 | 
					| Environment Variable | Default Value | Description                                                          |
 | 
				
			||||||
@ -50,22 +84,22 @@ See documentation in the `client` subfolder: [SyncNinja](https://gitea.va.reicha
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
SQLC Generation (v1.21.0):
 | 
					SQLC Generation (v1.21.0):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```bash
 | 
				
			||||||
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
 | 
					go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
 | 
				
			||||||
~/go/bin/sqlc generate
 | 
					~/go/bin/sqlc generate
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Run Development:
 | 
					Run Development:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```bash
 | 
				
			||||||
CONFIG_PATH=./data DATA_PATH=./data go run cmd/main.go serve
 | 
					CONFIG_PATH=./data DATA_PATH=./data go run main.go serve
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Building
 | 
					# Building
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The `Dockerfile` and `Makefile` contain the build information:
 | 
					The `Dockerfile` and `Makefile` contain the build information:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```bash
 | 
				
			||||||
# Build Local Docker Image
 | 
					# Build Local Docker Image
 | 
				
			||||||
make docker_build_local
 | 
					make docker_build_local
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -75,12 +109,12 @@ make docker_build_release_latest
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
If manually building, you must enable CGO:
 | 
					If manually building, you must enable CGO:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```bash
 | 
				
			||||||
# Download Dependencies
 | 
					# Download Dependencies
 | 
				
			||||||
go mod download
 | 
					go mod download
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Compile (Binary `./bookmanager`)
 | 
					# Compile (Binary `./bookmanager`)
 | 
				
			||||||
CGO_ENABLED=1 CGO_CFLAGS="-D_LARGEFILE64_SOURCE" go build -o /bookmanager cmd/main.go
 | 
					CGO_ENABLED=1 CGO_CFLAGS="-D_LARGEFILE64_SOURCE" go build -o /bookmanager
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Notes
 | 
					## Notes
 | 
				
			||||||
 | 
				
			|||||||
@ -21,8 +21,11 @@ type requestDocumentEdit struct {
 | 
				
			|||||||
	Title       *string               `form:"title"`
 | 
						Title       *string               `form:"title"`
 | 
				
			||||||
	Author      *string               `form:"author"`
 | 
						Author      *string               `form:"author"`
 | 
				
			||||||
	Description *string               `form:"description"`
 | 
						Description *string               `form:"description"`
 | 
				
			||||||
 | 
						ISBN10      *string               `form:"isbn_10"`
 | 
				
			||||||
 | 
						ISBN13      *string               `form:"isbn_13"`
 | 
				
			||||||
	RemoveCover *string               `form:"remove_cover"`
 | 
						RemoveCover *string               `form:"remove_cover"`
 | 
				
			||||||
	CoverFile   *multipart.FileHeader `form:"cover"`
 | 
						CoverGBID   *string               `form:"cover_gbid"`
 | 
				
			||||||
 | 
						CoverFile   *multipart.FileHeader `form:"cover_file"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type requestDocumentIdentify struct {
 | 
					type requestDocumentIdentify struct {
 | 
				
			||||||
@ -101,6 +104,7 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
 | 
				
			|||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								templateVars["RelBase"] = "../"
 | 
				
			||||||
			templateVars["Data"] = document
 | 
								templateVars["Data"] = document
 | 
				
			||||||
		} else if routeName == "activity" {
 | 
							} else if routeName == "activity" {
 | 
				
			||||||
			activityFilter := database.GetActivityParams{
 | 
								activityFilter := database.GetActivityParams{
 | 
				
			||||||
@ -131,7 +135,7 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
 | 
				
			|||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Warn("[createAppResourcesRoute] GetUserWindowStreaks DB Error:", err)
 | 
									log.Warn("[createAppResourcesRoute] GetUserWindowStreaks DB Error:", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			log.Info("GetUserWindowStreaks - WEEK - ", time.Since(start_time))
 | 
								log.Debug("GetUserWindowStreaks - WEEK - ", time.Since(start_time))
 | 
				
			||||||
			start_time = time.Now()
 | 
								start_time = time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			daily_streak, err := api.DB.Queries.GetUserWindowStreaks(api.DB.Ctx, database.GetUserWindowStreaksParams{
 | 
								daily_streak, err := api.DB.Queries.GetUserWindowStreaks(api.DB.Ctx, database.GetUserWindowStreaksParams{
 | 
				
			||||||
@ -141,15 +145,15 @@ func (api *API) createAppResourcesRoute(routeName string, args ...map[string]any
 | 
				
			|||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Warn("[createAppResourcesRoute] GetUserWindowStreaks DB Error:", err)
 | 
									log.Warn("[createAppResourcesRoute] GetUserWindowStreaks DB Error:", err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			log.Info("GetUserWindowStreaks - DAY - ", time.Since(start_time))
 | 
								log.Debug("GetUserWindowStreaks - DAY - ", time.Since(start_time))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			start_time = time.Now()
 | 
								start_time = time.Now()
 | 
				
			||||||
			database_info, _ := api.DB.Queries.GetDatabaseInfo(api.DB.Ctx, rUser.(string))
 | 
								database_info, _ := api.DB.Queries.GetDatabaseInfo(api.DB.Ctx, rUser.(string))
 | 
				
			||||||
			log.Info("GetDatabaseInfo - ", time.Since(start_time))
 | 
								log.Debug("GetDatabaseInfo - ", time.Since(start_time))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			start_time = time.Now()
 | 
								start_time = time.Now()
 | 
				
			||||||
			read_graph_data, _ := api.DB.Queries.GetDailyReadStats(api.DB.Ctx, rUser.(string))
 | 
								read_graph_data, _ := api.DB.Queries.GetDailyReadStats(api.DB.Ctx, rUser.(string))
 | 
				
			||||||
			log.Info("GetDailyReadStats - ", time.Since(start_time))
 | 
								log.Debug("GetDailyReadStats - ", time.Since(start_time))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			templateVars["Data"] = gin.H{
 | 
								templateVars["Data"] = gin.H{
 | 
				
			||||||
				"DailyStreak":  daily_streak,
 | 
									"DailyStreak":  daily_streak,
 | 
				
			||||||
@ -218,7 +222,7 @@ func (api *API) getDocumentCover(c *gin.Context) {
 | 
				
			|||||||
		firstResult := metadataResults[0]
 | 
							firstResult := metadataResults[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Save Cover
 | 
							// Save Cover
 | 
				
			||||||
		fileName, err := metadata.SaveCover(*firstResult.GBID, coverDir, document.ID)
 | 
							fileName, err := metadata.SaveCover(*firstResult.GBID, coverDir, document.ID, false)
 | 
				
			||||||
		if err == nil {
 | 
							if err == nil {
 | 
				
			||||||
			coverFile = *fileName
 | 
								coverFile = *fileName
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -275,8 +279,11 @@ func (api *API) editDocument(c *gin.Context) {
 | 
				
			|||||||
	if rDocEdit.Author == nil &&
 | 
						if rDocEdit.Author == nil &&
 | 
				
			||||||
		rDocEdit.Title == nil &&
 | 
							rDocEdit.Title == nil &&
 | 
				
			||||||
		rDocEdit.Description == nil &&
 | 
							rDocEdit.Description == nil &&
 | 
				
			||||||
		rDocEdit.CoverFile == nil &&
 | 
							rDocEdit.ISBN10 == nil &&
 | 
				
			||||||
		rDocEdit.RemoveCover == nil {
 | 
							rDocEdit.ISBN13 == nil &&
 | 
				
			||||||
 | 
							rDocEdit.RemoveCover == nil &&
 | 
				
			||||||
 | 
							rDocEdit.CoverGBID == nil &&
 | 
				
			||||||
 | 
							rDocEdit.CoverFile == nil {
 | 
				
			||||||
		log.Error("[createAppResourcesRoute] Missing Form Values")
 | 
							log.Error("[createAppResourcesRoute] Missing Form Values")
 | 
				
			||||||
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
 | 
							c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@ -288,7 +295,6 @@ func (api *API) editDocument(c *gin.Context) {
 | 
				
			|||||||
		s := "UNKNOWN"
 | 
							s := "UNKNOWN"
 | 
				
			||||||
		coverFileName = &s
 | 
							coverFileName = &s
 | 
				
			||||||
	} else if rDocEdit.CoverFile != nil {
 | 
						} else if rDocEdit.CoverFile != nil {
 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Validate Type & Derive Extension on MIME
 | 
							// Validate Type & Derive Extension on MIME
 | 
				
			||||||
		uploadedFile, err := rDocEdit.CoverFile.Open()
 | 
							uploadedFile, err := rDocEdit.CoverFile.Open()
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@ -325,6 +331,14 @@ func (api *API) editDocument(c *gin.Context) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		coverFileName = &fileName
 | 
							coverFileName = &fileName
 | 
				
			||||||
 | 
						} else if rDocEdit.CoverGBID != nil {
 | 
				
			||||||
 | 
							// TODO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var coverDir string = filepath.Join(api.Config.DataPath, "covers")
 | 
				
			||||||
 | 
							fileName, err := metadata.SaveCover(*rDocEdit.CoverGBID, coverDir, rDocID.DocumentID, true)
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								coverFileName = fileName
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Update Document
 | 
						// Update Document
 | 
				
			||||||
@ -333,6 +347,8 @@ func (api *API) editDocument(c *gin.Context) {
 | 
				
			|||||||
		Title:       api.sanitizeInput(rDocEdit.Title),
 | 
							Title:       api.sanitizeInput(rDocEdit.Title),
 | 
				
			||||||
		Author:      api.sanitizeInput(rDocEdit.Author),
 | 
							Author:      api.sanitizeInput(rDocEdit.Author),
 | 
				
			||||||
		Description: api.sanitizeInput(rDocEdit.Description),
 | 
							Description: api.sanitizeInput(rDocEdit.Description),
 | 
				
			||||||
 | 
							Isbn10:      api.sanitizeInput(rDocEdit.ISBN10),
 | 
				
			||||||
 | 
							Isbn13:      api.sanitizeInput(rDocEdit.ISBN13),
 | 
				
			||||||
		Coverfile:   coverFileName,
 | 
							Coverfile:   coverFileName,
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		log.Error("[createAppResourcesRoute] UpsertDocument DB Error:", err)
 | 
							log.Error("[createAppResourcesRoute] UpsertDocument DB Error:", err)
 | 
				
			||||||
@ -367,6 +383,8 @@ func (api *API) deleteDocument(c *gin.Context) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (api *API) identifyDocument(c *gin.Context) {
 | 
					func (api *API) identifyDocument(c *gin.Context) {
 | 
				
			||||||
 | 
						rUser, _ := c.Get("AuthorizedUser")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var rDocID requestDocumentID
 | 
						var rDocID requestDocumentID
 | 
				
			||||||
	if err := c.ShouldBindUri(&rDocID); err != nil {
 | 
						if err := c.ShouldBindUri(&rDocID); err != nil {
 | 
				
			||||||
		log.Error("[identifyDocument] Invalid URI Bind")
 | 
							log.Error("[identifyDocument] Invalid URI Bind")
 | 
				
			||||||
@ -399,36 +417,52 @@ func (api *API) identifyDocument(c *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Template Variables
 | 
				
			||||||
 | 
						templateVars := gin.H{
 | 
				
			||||||
 | 
							"RelBase": "../../",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get Metadata
 | 
				
			||||||
	metadataResults, err := metadata.GetMetadata(metadata.MetadataInfo{
 | 
						metadataResults, err := metadata.GetMetadata(metadata.MetadataInfo{
 | 
				
			||||||
		Title:  rDocIdentify.Title,
 | 
							Title:  rDocIdentify.Title,
 | 
				
			||||||
		Author: rDocIdentify.Author,
 | 
							Author: rDocIdentify.Author,
 | 
				
			||||||
		ISBN10: rDocIdentify.ISBN,
 | 
							ISBN10: rDocIdentify.ISBN,
 | 
				
			||||||
		ISBN13: rDocIdentify.ISBN,
 | 
							ISBN13: rDocIdentify.ISBN,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil || len(metadataResults) == 0 {
 | 
						if err == nil && len(metadataResults) > 0 {
 | 
				
			||||||
		log.Error("[identifyDocument] Metadata Error")
 | 
							firstResult := metadataResults[0]
 | 
				
			||||||
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Metadata Error"})
 | 
					
 | 
				
			||||||
 | 
							// Store First Metadata Result
 | 
				
			||||||
 | 
							if _, err = api.DB.Queries.AddMetadata(api.DB.Ctx, database.AddMetadataParams{
 | 
				
			||||||
 | 
								DocumentID:  rDocID.DocumentID,
 | 
				
			||||||
 | 
								Title:       firstResult.Title,
 | 
				
			||||||
 | 
								Author:      firstResult.Author,
 | 
				
			||||||
 | 
								Description: firstResult.Description,
 | 
				
			||||||
 | 
								Gbid:        firstResult.GBID,
 | 
				
			||||||
 | 
								Olid:        firstResult.OLID,
 | 
				
			||||||
 | 
								Isbn10:      firstResult.ISBN10,
 | 
				
			||||||
 | 
								Isbn13:      firstResult.ISBN13,
 | 
				
			||||||
 | 
							}); err != nil {
 | 
				
			||||||
 | 
								log.Error("[identifyDocument] AddMetadata DB Error:", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							templateVars["Metadata"] = firstResult
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							log.Warn("[identifyDocument] Metadata Error")
 | 
				
			||||||
 | 
							templateVars["MetadataError"] = "No Metadata Found"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						document, err := api.DB.Queries.GetDocumentWithStats(api.DB.Ctx, database.GetDocumentWithStatsParams{
 | 
				
			||||||
 | 
							UserID:     rUser.(string),
 | 
				
			||||||
 | 
							DocumentID: rDocID.DocumentID,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Error("[identifyDocument] GetDocumentWithStats DB Error:", err)
 | 
				
			||||||
 | 
							c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid Request"})
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO
 | 
						templateVars["Data"] = document
 | 
				
			||||||
	firstResult := metadataResults[0]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if firstResult.Title != nil {
 | 
						c.HTML(http.StatusOK, "document", templateVars)
 | 
				
			||||||
		log.Info("Title:", *firstResult.Title)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if firstResult.Author != nil {
 | 
					 | 
				
			||||||
		log.Info("Author:", *firstResult.Author)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if firstResult.Description != nil {
 | 
					 | 
				
			||||||
		log.Info("Description:", *firstResult.Description)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if firstResult.ISBN10 != nil {
 | 
					 | 
				
			||||||
		log.Info("ISBN 10:", *firstResult.ISBN10)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if firstResult.ISBN13 != nil {
 | 
					 | 
				
			||||||
		log.Info("ISBN 13:", *firstResult.ISBN13)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	c.Redirect(http.StatusFound, "/")
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -85,6 +85,7 @@ func (api *API) authFormLogin(c *gin.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if username == "" || rawPassword == "" {
 | 
						if username == "" || rawPassword == "" {
 | 
				
			||||||
		c.HTML(http.StatusUnauthorized, "login", gin.H{
 | 
							c.HTML(http.StatusUnauthorized, "login", gin.H{
 | 
				
			||||||
 | 
								"RegistrationEnabled": api.Config.RegistrationEnabled,
 | 
				
			||||||
			"Error":               "Invalid Credentials",
 | 
								"Error":               "Invalid Credentials",
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@ -93,6 +94,7 @@ func (api *API) authFormLogin(c *gin.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if authorized := api.authorizeCredentials(username, password); authorized != true {
 | 
						if authorized := api.authorizeCredentials(username, password); authorized != true {
 | 
				
			||||||
		c.HTML(http.StatusUnauthorized, "login", gin.H{
 | 
							c.HTML(http.StatusUnauthorized, "login", gin.H{
 | 
				
			||||||
 | 
								"RegistrationEnabled": api.Config.RegistrationEnabled,
 | 
				
			||||||
			"Error":               "Invalid Credentials",
 | 
								"Error":               "Invalid Credentials",
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
 | 
				
			|||||||
@ -380,8 +380,12 @@ current_streak AS (
 | 
				
			|||||||
        end_date AS current_streak_end_date
 | 
					        end_date AS current_streak_end_date
 | 
				
			||||||
    FROM streaks
 | 
					    FROM streaks
 | 
				
			||||||
    WHERE CASE
 | 
					    WHERE CASE
 | 
				
			||||||
      WHEN ?2 = "WEEK" THEN DATE('now', time_offset, 'weekday 0', '-7 day') = current_streak_end_date
 | 
					      WHEN ?2 = "WEEK" THEN
 | 
				
			||||||
      WHEN ?2 = "DAY" THEN DATE('now', time_offset, '-1 day') = current_streak_end_date OR DATE('now', time_offset) = current_streak_end_date
 | 
					          DATE('now', time_offset, 'weekday 0', '-14 day') = current_streak_end_date
 | 
				
			||||||
 | 
					          OR DATE('now', time_offset, 'weekday 0', '-7 day') = current_streak_end_date
 | 
				
			||||||
 | 
					      WHEN ?2 = "DAY" THEN
 | 
				
			||||||
 | 
					          DATE('now', time_offset, '-1 day') = current_streak_end_date
 | 
				
			||||||
 | 
					          OR DATE('now', time_offset) = current_streak_end_date
 | 
				
			||||||
    END
 | 
					    END
 | 
				
			||||||
    LIMIT 1
 | 
					    LIMIT 1
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
@ -1006,8 +1006,12 @@ current_streak AS (
 | 
				
			|||||||
        end_date AS current_streak_end_date
 | 
					        end_date AS current_streak_end_date
 | 
				
			||||||
    FROM streaks
 | 
					    FROM streaks
 | 
				
			||||||
    WHERE CASE
 | 
					    WHERE CASE
 | 
				
			||||||
      WHEN ?2 = "WEEK" THEN DATE('now', time_offset, 'weekday 0', '-7 day') = current_streak_end_date
 | 
					      WHEN ?2 = "WEEK" THEN
 | 
				
			||||||
      WHEN ?2 = "DAY" THEN DATE('now', time_offset, '-1 day') = current_streak_end_date OR DATE('now', time_offset) = current_streak_end_date
 | 
					          DATE('now', time_offset, 'weekday 0', '-14 day') = current_streak_end_date
 | 
				
			||||||
 | 
					          OR DATE('now', time_offset, 'weekday 0', '-7 day') = current_streak_end_date
 | 
				
			||||||
 | 
					      WHEN ?2 = "DAY" THEN
 | 
				
			||||||
 | 
					          DATE('now', time_offset, '-1 day') = current_streak_end_date
 | 
				
			||||||
 | 
					          OR DATE('now', time_offset) = current_streak_end_date
 | 
				
			||||||
    END
 | 
					    END
 | 
				
			||||||
    LIMIT 1
 | 
					    LIMIT 1
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
@ -129,7 +129,7 @@ func GetMetadata(metadataSearch MetadataInfo) ([]MetadataInfo, error) {
 | 
				
			|||||||
	return allMetadata, nil
 | 
						return allMetadata, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func SaveCover(gbid string, coverDir string, documentID string) (*string, error) {
 | 
					func SaveCover(gbid string, coverDir string, documentID string, overwrite bool) (*string, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Google Books -> JPG
 | 
						// Google Books -> JPG
 | 
				
			||||||
	coverFile := "." + filepath.Clean(fmt.Sprintf("/%s.jpg", documentID))
 | 
						coverFile := "." + filepath.Clean(fmt.Sprintf("/%s.jpg", documentID))
 | 
				
			||||||
@ -137,7 +137,7 @@ func SaveCover(gbid string, coverDir string, documentID string) (*string, error)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Validate File Doesn't Exists
 | 
						// Validate File Doesn't Exists
 | 
				
			||||||
	_, err := os.Stat(coverFilePath)
 | 
						_, err := os.Stat(coverFilePath)
 | 
				
			||||||
	if err == nil {
 | 
						if err == nil && overwrite == false {
 | 
				
			||||||
		log.Warn("[SaveCover] File Alreads Exists")
 | 
							log.Warn("[SaveCover] File Alreads Exists")
 | 
				
			||||||
		return &coverFile, nil
 | 
							return &coverFile, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										13
									
								
								screenshots/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					# Screenshots
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can find PWA and Web screenshots in their respective folders.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Process Images
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					# Resize
 | 
				
			||||||
 | 
					sips -Z 1500 *.png
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Crop Top & Bottom
 | 
				
			||||||
 | 
					sips --cropOffset 85 1 -c 1385 688 *.png
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
							
								
								
									
										25
									
								
								screenshots/pwa/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					# PWA Screenshots
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p align="center">
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/login.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/login.png" width="32%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/home.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/home.png" width="32%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/activity.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/activity.png" width="32%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p align="center">
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/documents.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/documents.png" width="32%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/document.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/document.png" width="32%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/metadata.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/pwa/metadata.png" width="32%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								screenshots/pwa/activity.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 228 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/pwa/document.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 585 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/pwa/documents.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 489 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/pwa/home.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 143 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/pwa/login.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 60 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/pwa/metadata.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 335 KiB  | 
							
								
								
									
										28
									
								
								screenshots/web/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					# Web Screenshots
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p align="center">
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/login.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/login.png" width="49%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/home.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/home.png" width="49%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p align="center">
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/activity.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/activity.png" width="49%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/documents.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/documents.png" width="49%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<p align="center">
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/document.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/document.png" width="49%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					    <a href="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/metadata.png">
 | 
				
			||||||
 | 
					        <img src="https://gitea.va.reichard.io/evan/BookManager/raw/branch/master/screenshots/web/metadata.png" width="49%">
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								screenshots/web/activity.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 245 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/web/document.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 639 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/web/documents.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 791 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/web/home.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 131 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/web/login.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 790 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshots/web/metadata.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 357 KiB  | 
| 
		 Before Width: | Height: | Size: 1.8 MiB  | 
| 
		 Before Width: | Height: | Size: 362 KiB  | 
| 
		 Before Width: | Height: | Size: 2.8 MiB  | 
@ -1,9 +1,10 @@
 | 
				
			|||||||
<!DOCTYPE html>
 | 
					<!DOCTYPE html>
 | 
				
			||||||
<html lang="en">
 | 
					<html lang="en">
 | 
				
			||||||
  <head>
 | 
					  <head>
 | 
				
			||||||
    <link rel="manifest" href="./manifest.json" />
 | 
					    <link rel="manifest" href="{{ .RelBase }}./manifest.json" />
 | 
				
			||||||
    <meta charset="utf-8" />
 | 
					    <meta charset="utf-8" />
 | 
				
			||||||
    <meta name="viewport" content="width=device-width" />
 | 
					    <meta name="viewport"
 | 
				
			||||||
 | 
						  content="width=device-width, initial-scale=0.90, user-scalable=no">
 | 
				
			||||||
    <script src="https://cdn.tailwindcss.com"></script>
 | 
					    <script src="https://cdn.tailwindcss.com"></script>
 | 
				
			||||||
    <title>Book Manager - {{block "title" .}}{{end}}</title>
 | 
					    <title>Book Manager - {{block "title" .}}{{end}}</title>
 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
@ -11,33 +12,18 @@
 | 
				
			|||||||
    <main
 | 
					    <main
 | 
				
			||||||
      class="relative h-screen overflow-hidden bg-gray-100 dark:bg-gray-800"
 | 
					      class="relative h-screen overflow-hidden bg-gray-100 dark:bg-gray-800"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <div class="flex items-start justify-between">
 | 
					      <div class="flex items-center justify-between w-full h-16">
 | 
				
			||||||
	<input type="checkbox" id="mobile-nav-button" class="hidden"/>
 | 
					        <div id="mobile-nav-button" class="flex flex-col z-40 relative ml-6">
 | 
				
			||||||
        <div class="fixed -left-64 duration-500 transition-all w-56 z-50 h-screen shadow-lg lg:left-0 lg:block lg:relative">
 | 
					          <input type="checkbox" class="absolute lg:hidden z-50 -top-2 w-7 h-7 flex cursor-pointer opacity-0 w-12 h-12" />
 | 
				
			||||||
          <div class="h-full bg-white dark:bg-gray-700">
 | 
					          <span class="lg:hidden w-7 h-0.5 z-40 mt-0.5 bg-white"></span>
 | 
				
			||||||
            <div class="flex items-center justify-center gap-4 h-16">
 | 
					          <span class="lg:hidden w-7 h-0.5 z-40 mt-1 bg-white"></span>
 | 
				
			||||||
	      <label
 | 
					          <span class="lg:hidden w-7 h-0.5 z-40 mt-1 bg-white"></span>
 | 
				
			||||||
		id="mobile-nav-close-button"
 | 
					
 | 
				
			||||||
		for="mobile-nav-button"
 | 
					          <div id="menu" class="fixed -mt-6 -ml-6 lg:-mt-8 h-full w-56 lg:w-48 bg-white dark:bg-gray-700 shadow-lg">
 | 
				
			||||||
		class="flex block items-center p-2 text-gray-500 bg-white rounded-full shadow text-md cursor-pointer lg:hidden"
 | 
					            <div class="h-16 flex justify-end lg:justify-around">
 | 
				
			||||||
	      >
 | 
					              <p class="text-xl font-bold dark:text-white text-right my-auto pr-4 lg:pr-0">Book Manager</p>
 | 
				
			||||||
		<svg
 | 
					 | 
				
			||||||
		  width="20"
 | 
					 | 
				
			||||||
		  height="20"
 | 
					 | 
				
			||||||
		  class="text-gray-400"
 | 
					 | 
				
			||||||
		  fill="currentColor"
 | 
					 | 
				
			||||||
		  viewBox="0 0 1792 1792"
 | 
					 | 
				
			||||||
		  xmlns="http://www.w3.org/2000/svg"
 | 
					 | 
				
			||||||
		>
 | 
					 | 
				
			||||||
		  <path
 | 
					 | 
				
			||||||
		    d="M1664 1344v128q0 26-19 45t-45 19h-1408q-26 0-45-19t-19-45v-128q0-26 19-45t45-19h1408q26 0 45 19t19 45zm0-512v128q0 26-19 45t-45 19h-1408q-26 0-45-19t-19-45v-128q0-26 19-45t45-19h1408q26 0 45 19t19 45zm0-512v128q0 26-19 45t-45 19h-1408q-26 0-45-19t-19-45v-128q0-26 19-45t45-19h1408q26 0 45 19t19 45z"
 | 
					 | 
				
			||||||
		  ></path>
 | 
					 | 
				
			||||||
		</svg>
 | 
					 | 
				
			||||||
	      </label>
 | 
					 | 
				
			||||||
              <p class="text-xl font-bold dark:text-white">Book Manager</p>
 | 
					 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <nav class="mt-6">
 | 
					            <div class="mt-6">
 | 
				
			||||||
              <div>
 | 
					 | 
				
			||||||
              <a
 | 
					              <a
 | 
				
			||||||
                class="flex items-center justify-start w-full p-2 pl-6 my-2 transition-colors duration-200 border-l-4 {{if eq .RouteName "home"}}border-purple-500 dark:text-white{{else}}border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-100{{end}}"
 | 
					                class="flex items-center justify-start w-full p-2 pl-6 my-2 transition-colors duration-200 border-l-4 {{if eq .RouteName "home"}}border-purple-500 dark:text-white{{else}}border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-100{{end}}"
 | 
				
			||||||
                href="/"
 | 
					                href="/"
 | 
				
			||||||
@ -114,30 +100,8 @@
 | 
				
			|||||||
                <span class="mx-4 text-sm font-normal"> Graphs </span>
 | 
					                <span class="mx-4 text-sm font-normal"> Graphs </span>
 | 
				
			||||||
              </a>
 | 
					              </a>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            </nav>
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="flex flex-col w-full">
 | 
					 | 
				
			||||||
          <header class="z-40 flex items-center justify-between w-full h-16">
 | 
					 | 
				
			||||||
            <div class="block ml-6 lg:hidden">
 | 
					 | 
				
			||||||
              <label
 | 
					 | 
				
			||||||
		for="mobile-nav-button"
 | 
					 | 
				
			||||||
                class="flex items-center p-2 text-gray-500 bg-white rounded-full shadow text-md cursor-pointer"
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                <svg
 | 
					 | 
				
			||||||
                  width="20"
 | 
					 | 
				
			||||||
                  height="20"
 | 
					 | 
				
			||||||
                  class="text-gray-400"
 | 
					 | 
				
			||||||
                  fill="currentColor"
 | 
					 | 
				
			||||||
                  viewBox="0 0 1792 1792"
 | 
					 | 
				
			||||||
                  xmlns="http://www.w3.org/2000/svg"
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                  <path
 | 
					 | 
				
			||||||
                    d="M1664 1344v128q0 26-19 45t-45 19h-1408q-26 0-45-19t-19-45v-128q0-26 19-45t45-19h1408q26 0 45 19t19 45zm0-512v128q0 26-19 45t-45 19h-1408q-26 0-45-19t-19-45v-128q0-26 19-45t45-19h1408q26 0 45 19t19 45zm0-512v128q0 26-19 45t-45 19h-1408q-26 0-45-19t-19-45v-128q0-26 19-45t45-19h1408q26 0 45 19t19 45z"
 | 
					 | 
				
			||||||
                  ></path>
 | 
					 | 
				
			||||||
                </svg>
 | 
					 | 
				
			||||||
              </label>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
        <h1 class="text-xl font-bold dark:text-white px-6">{{block "header" .}}{{end}}</h1>
 | 
					        <h1 class="text-xl font-bold dark:text-white px-6">{{block "header" .}}{{end}}</h1>
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          class="relative flex items-center justify-end w-full p-4 space-x-4"
 | 
					          class="relative flex items-center justify-end w-full p-4 space-x-4"
 | 
				
			||||||
@ -159,7 +123,7 @@
 | 
				
			|||||||
          <input type="checkbox" id="user-dropdown-button" class="hidden"/>
 | 
					          <input type="checkbox" id="user-dropdown-button" class="hidden"/>
 | 
				
			||||||
          <div
 | 
					          <div
 | 
				
			||||||
            id="user-dropdown"
 | 
					            id="user-dropdown"
 | 
				
			||||||
		class="transition duration-200 absolute right-4 top-16 pt-4"
 | 
					            class="transition duration-200 z-20 absolute right-4 top-16 pt-4"
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
              class="w-56 origin-top-right bg-white rounded-md shadow-lg dark:shadow-gray-800 dark:bg-gray-700 ring-1 ring-black ring-opacity-5"
 | 
					              class="w-56 origin-top-right bg-white rounded-md shadow-lg dark:shadow-gray-800 dark:bg-gray-700 ring-1 ring-black ring-opacity-5"
 | 
				
			||||||
@ -202,24 +166,16 @@
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </label>
 | 
					          </label>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
          </header>
 | 
					      </div>
 | 
				
			||||||
          <div class="h-screen px-4 pb-24 overflow-auto md:px-6">
 | 
					
 | 
				
			||||||
 | 
					      <div class="h-screen px-4 pb-24 overflow-auto md:px-6 lg:ml-48">
 | 
				
			||||||
        {{block "content" .}}{{end}}
 | 
					        {{block "content" .}}{{end}}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </main>
 | 
					    </main>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Custom Animation CSS -->
 | 
					    <!-- Custom Animation CSS -->
 | 
				
			||||||
    <style>
 | 
					    <style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      /* ----------------------------- */
 | 
					 | 
				
			||||||
      /* ------ Navigation Slide ----- */
 | 
					 | 
				
			||||||
      /* ----------------------------- */
 | 
					 | 
				
			||||||
      #mobile-nav-button:checked + div {
 | 
					 | 
				
			||||||
	left: 0px;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /* ----------------------------- */
 | 
					      /* ----------------------------- */
 | 
				
			||||||
      /* ------- User Dropdown ------- */
 | 
					      /* ------- User Dropdown ------- */
 | 
				
			||||||
      /* ----------------------------- */
 | 
					      /* ----------------------------- */
 | 
				
			||||||
@ -232,6 +188,55 @@
 | 
				
			|||||||
        visibility: hidden;
 | 
					        visibility: hidden;
 | 
				
			||||||
        opacity: 0;
 | 
					        opacity: 0;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /* ----------------------------- */
 | 
				
			||||||
 | 
					      /* ----- Mobile Navigation ----- */
 | 
				
			||||||
 | 
					      /* ----------------------------- */
 | 
				
			||||||
 | 
					      #mobile-nav-button span {
 | 
				
			||||||
 | 
					        transform-origin: 5px 0px;
 | 
				
			||||||
 | 
					        transition: transform 0.5s cubic-bezier(0.77,0.2,0.05,1.0),
 | 
				
			||||||
 | 
					                    background 0.5s cubic-bezier(0.77,0.2,0.05,1.0),
 | 
				
			||||||
 | 
					                    opacity 0.55s ease;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      #mobile-nav-button span:first-child {
 | 
				
			||||||
 | 
					        transform-origin: 0% 0%;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      #mobile-nav-button span:nth-last-child(2) {
 | 
				
			||||||
 | 
					        transform-origin: 0% 100%;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      #mobile-nav-button input:checked ~ span {
 | 
				
			||||||
 | 
					        opacity: 1;
 | 
				
			||||||
 | 
					        transform: rotate(45deg) translate(2px, -2px);
 | 
				
			||||||
 | 
					        background: #FFFFFF;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      #mobile-nav-button input:checked ~ span:nth-last-child(3) {
 | 
				
			||||||
 | 
					        opacity: 0;
 | 
				
			||||||
 | 
					        transform: rotate(0deg) scale(0.2, 0.2);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      #mobile-nav-button input:checked ~ span:nth-last-child(2) {
 | 
				
			||||||
 | 
					        transform: rotate(-45deg) translate(0, 6px);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      #mobile-nav-button input:checked ~ div {
 | 
				
			||||||
 | 
					        transform: none;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      @media (min-width: 1024px) { 
 | 
				
			||||||
 | 
					        #mobile-nav-button input ~ div {
 | 
				
			||||||
 | 
					          transform: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      #menu {
 | 
				
			||||||
 | 
					        transform-origin: 0% 0%;
 | 
				
			||||||
 | 
					        transform: translate(-100%, 0);
 | 
				
			||||||
 | 
					        transition: transform 0.5s cubic-bezier(0.77,0.2,0.05,1.0);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
  </body>
 | 
					  </body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,35 +1,37 @@
 | 
				
			|||||||
{{template "base.html" .}}
 | 
					{{template "base.html" . }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{{define "title"}}Documents{{end}}
 | 
					{{define "title"}}Documents{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{{define "header"}}
 | 
					{{define "header"}}
 | 
				
			||||||
<a href="../documents">Documents</a>
 | 
					<a href="{{ .RelBase }}./documents">Documents</a>
 | 
				
			||||||
{{end}}
 | 
					{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{{define "content"}}
 | 
					{{define "content"}}
 | 
				
			||||||
<div class="h-full w-full overflow-scroll bg-white shadow-lg dark:bg-gray-700 rounded dark:text-white p-4">
 | 
					<div class="h-full w-full relative">
 | 
				
			||||||
  <div class="flex flex-col gap-2 float-left w-40 md:w-60 lg:w-80 mr-4 mb-2 relative">
 | 
					  <!-- Document Info -->
 | 
				
			||||||
    <label class="z-20 cursor-pointer" for="edit-cover-button">
 | 
					  <div class="h-full w-full overflow-scroll bg-white shadow-lg dark:bg-gray-700 rounded dark:text-white p-4">
 | 
				
			||||||
      <img class="rounded object-fill h-full" src="../documents/{{.Data.ID}}/cover"></img>
 | 
					    <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="{{ .RelBase }}./documents/{{.Data.ID}}/cover"></img>
 | 
				
			||||||
      </label>
 | 
					      </label>
 | 
				
			||||||
    <div class="flex flex-wrap-reverse justify-between z-40 gap-2 relative">
 | 
					      <div class="flex flex-wrap-reverse justify-between z-20 gap-2 relative">
 | 
				
			||||||
      <div class="mr-2 min-w-[50%]">
 | 
						<div class="min-w-[50%] md:mr-2">
 | 
				
			||||||
	  <div class="flex gap-1 text-sm">
 | 
						  <div class="flex gap-1 text-sm">
 | 
				
			||||||
	    <p class="text-gray-400">ISBN 10:</p>
 | 
						      <p class="text-gray-500">ISBN-10:</p>
 | 
				
			||||||
	      <p class="font-medium">
 | 
						      <p class="font-medium">
 | 
				
			||||||
	      {{ or .Data.Isbn10 "N/A" }}
 | 
						      {{ or .Data.Isbn10 "N/A" }}
 | 
				
			||||||
	      </p>
 | 
						      </p>
 | 
				
			||||||
	  </div>
 | 
						  </div>
 | 
				
			||||||
	  <div class="flex gap-1 text-sm">
 | 
						  <div class="flex gap-1 text-sm">
 | 
				
			||||||
	    <p class="text-gray-400">ISBN 13:</p>
 | 
						      <p class="text-gray-500">ISBN-13:</p>
 | 
				
			||||||
	      <p class="font-medium">
 | 
						      <p class="font-medium">
 | 
				
			||||||
	      {{ or .Data.Isbn13 "N/A" }}
 | 
						      {{ or .Data.Isbn13 "N/A" }}
 | 
				
			||||||
	      </p>
 | 
						      </p>
 | 
				
			||||||
	  </div>
 | 
						  </div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
      <div class="flex grow justify-between my-auto text-gray-500 dark:text-gray-400">
 | 
						<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"/>
 | 
						  <input type="checkbox" id="edit-cover-button" class="hidden css-button"/>
 | 
				
			||||||
	<div class="absolute z-50 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">
 | 
						  <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
 | 
						    <form
 | 
				
			||||||
	      method="POST"
 | 
						      method="POST"
 | 
				
			||||||
	      enctype="multipart/form-data"
 | 
						      enctype="multipart/form-data"
 | 
				
			||||||
@ -38,10 +40,13 @@
 | 
				
			|||||||
	    >
 | 
						    >
 | 
				
			||||||
	      <input
 | 
						      <input
 | 
				
			||||||
		type="file"
 | 
							type="file"
 | 
				
			||||||
	      id="cover"
 | 
							id="cover_file"
 | 
				
			||||||
	      name="cover"
 | 
							name="cover_file"
 | 
				
			||||||
	      >
 | 
						      >
 | 
				
			||||||
	    <button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Upload Cover</button>
 | 
						      <button
 | 
				
			||||||
 | 
							class="font-medium px-2 py-1 text-white bg-gray-500 dark:bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
 | 
				
			||||||
 | 
							type="submit"
 | 
				
			||||||
 | 
						      >Upload Cover</button>
 | 
				
			||||||
	    </form>
 | 
						    </form>
 | 
				
			||||||
	    <form
 | 
						    <form
 | 
				
			||||||
	      method="POST"
 | 
						      method="POST"
 | 
				
			||||||
@ -49,7 +54,10 @@
 | 
				
			|||||||
	      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="checkbox" checked id="remove_cover" name="remove_cover" class="hidden" />
 | 
						      <input type="checkbox" checked id="remove_cover" name="remove_cover" class="hidden" />
 | 
				
			||||||
	    <button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Remove Cover</button>
 | 
						      <button
 | 
				
			||||||
 | 
							class="font-medium px-2 py-1 text-white bg-gray-500 dark: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>
 | 
				
			||||||
@ -72,13 +80,16 @@
 | 
				
			|||||||
	      </svg>
 | 
						      </svg>
 | 
				
			||||||
	    </label>
 | 
						    </label>
 | 
				
			||||||
	    <input type="checkbox" id="delete-button" class="hidden css-button"/>
 | 
						    <input type="checkbox" id="delete-button" class="hidden css-button"/>
 | 
				
			||||||
	  <div class="absolute z-50 bottom-7 left-5 p-3 transition-all duration-200 bg-gray-200 rounded shadow-lg shadow-gray-500 dark:shadow-gray-900 dark:bg-gray-600">
 | 
						    <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
 | 
						      <form
 | 
				
			||||||
		method="POST"
 | 
							method="POST"
 | 
				
			||||||
		action="./{{ .Data.ID }}/delete"
 | 
							action="./{{ .Data.ID }}/delete"
 | 
				
			||||||
		class="text-black dark:text-white text-sm"
 | 
							class="text-black dark:text-white text-sm"
 | 
				
			||||||
	      >
 | 
						      >
 | 
				
			||||||
	      <button class="font-medium w-24 px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Delete</button>
 | 
							<button
 | 
				
			||||||
 | 
							  class="font-medium w-24 px-2 py-1 text-white bg-gray-500 dark: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>
 | 
				
			||||||
@ -113,7 +124,7 @@
 | 
				
			|||||||
	      </svg>
 | 
						      </svg>
 | 
				
			||||||
	    </label>
 | 
						    </label>
 | 
				
			||||||
	    <input type="checkbox" id="edit-button" class="hidden css-button"/>
 | 
						    <input type="checkbox" id="edit-button" class="hidden css-button"/>
 | 
				
			||||||
	  <div class="absolute z-50 bottom-7 left-5 p-3 transition-all duration-200 bg-gray-200 rounded shadow-lg shadow-gray-500 dark:shadow-gray-900 dark:bg-gray-600">
 | 
						    <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
 | 
						      <form
 | 
				
			||||||
		method="POST"
 | 
							method="POST"
 | 
				
			||||||
		action="./{{ .Data.ID }}/identify"
 | 
							action="./{{ .Data.ID }}/identify"
 | 
				
			||||||
@ -124,23 +135,29 @@
 | 
				
			|||||||
		  id="title"
 | 
							  id="title"
 | 
				
			||||||
		  name="title"
 | 
							  name="title"
 | 
				
			||||||
		  placeholder="Title"
 | 
							  placeholder="Title"
 | 
				
			||||||
		class="p-2 bg-gray-400 text-black dark:bg-gray-700 dark:text-white"
 | 
							  value="{{ or .Data.Title nil }}"
 | 
				
			||||||
 | 
							  class="p-2 bg-gray-300 text-black dark:bg-gray-700 dark:text-white"
 | 
				
			||||||
		>
 | 
							>
 | 
				
			||||||
		<input
 | 
							<input
 | 
				
			||||||
		  type="text"
 | 
							  type="text"
 | 
				
			||||||
		  id="author"
 | 
							  id="author"
 | 
				
			||||||
		  name="author"
 | 
							  name="author"
 | 
				
			||||||
		  placeholder="Author"
 | 
							  placeholder="Author"
 | 
				
			||||||
		class="p-2 bg-gray-400 text-black dark:bg-gray-700 dark:text-white"
 | 
							  value="{{ or .Data.Author nil }}"
 | 
				
			||||||
 | 
							  class="p-2 bg-gray-300 text-black dark:bg-gray-700 dark:text-white"
 | 
				
			||||||
		>
 | 
							>
 | 
				
			||||||
		<input
 | 
							<input
 | 
				
			||||||
		  type="text"
 | 
							  type="text"
 | 
				
			||||||
		  id="isbn"
 | 
							  id="isbn"
 | 
				
			||||||
		  name="isbn"
 | 
							  name="isbn"
 | 
				
			||||||
		  placeholder="ISBN 10 / ISBN 13"
 | 
							  placeholder="ISBN 10 / ISBN 13"
 | 
				
			||||||
		class="p-2 bg-gray-400 text-black dark:bg-gray-700 dark:text-white"
 | 
							  value="{{ or .Data.Isbn13 (or .Data.Isbn10 nil) }}"
 | 
				
			||||||
 | 
							  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 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Identify</button>
 | 
							<button
 | 
				
			||||||
 | 
							  class="font-medium px-2 py-1 text-white bg-gray-500 dark: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>
 | 
				
			||||||
@ -180,9 +197,9 @@
 | 
				
			|||||||
	</div>
 | 
						</div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  <div class="grid grid-cols-2 justify-between gap-4 pb-4">
 | 
					    <div class="grid sm:grid-cols-2 justify-between gap-4 pb-4">
 | 
				
			||||||
      <div class="relative">
 | 
					      <div class="relative">
 | 
				
			||||||
      <div class="text-gray-400 inline-flex gap-2 relative">
 | 
						<div class="text-gray-500 inline-flex gap-2 relative">
 | 
				
			||||||
	  <p>Title</p>
 | 
						  <p>Title</p>
 | 
				
			||||||
	  <label class="my-auto" for="edit-title-button">
 | 
						  <label class="my-auto" for="edit-title-button">
 | 
				
			||||||
	    <svg
 | 
						    <svg
 | 
				
			||||||
@ -205,7 +222,7 @@
 | 
				
			|||||||
	    </svg>
 | 
						    </svg>
 | 
				
			||||||
	  </label>
 | 
						  </label>
 | 
				
			||||||
	  <input type="checkbox" id="edit-title-button" class="hidden css-button"/>
 | 
						  <input type="checkbox" id="edit-title-button" class="hidden css-button"/>
 | 
				
			||||||
	<div class="absolute z-50 top-7 right-0 p-3 transition-all duration-200 bg-gray-200 rounded shadow-lg shadow-gray-500 dark:shadow-gray-900 dark:bg-gray-600">
 | 
						  <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
 | 
						    <form
 | 
				
			||||||
	      method="POST"
 | 
						      method="POST"
 | 
				
			||||||
	      action="./{{ .Data.ID }}/edit"
 | 
						      action="./{{ .Data.ID }}/edit"
 | 
				
			||||||
@ -216,9 +233,12 @@
 | 
				
			|||||||
		id="title"
 | 
							id="title"
 | 
				
			||||||
		name="title"
 | 
							name="title"
 | 
				
			||||||
		value="{{ or .Data.Title "N/A" }}"
 | 
							value="{{ or .Data.Title "N/A" }}"
 | 
				
			||||||
	      class="p-2 bg-gray-400 text-black dark:bg-gray-700 dark:text-white"
 | 
							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 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Save</button>
 | 
						      <button
 | 
				
			||||||
 | 
							class="font-medium px-2 py-1 text-white bg-gray-500 dark:bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
 | 
				
			||||||
 | 
							type="submit"
 | 
				
			||||||
 | 
						      >Save</button>
 | 
				
			||||||
	    </form>
 | 
						    </form>
 | 
				
			||||||
	  </div>
 | 
						  </div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
@ -227,7 +247,7 @@
 | 
				
			|||||||
	</p>
 | 
						</p>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="relative">
 | 
					      <div class="relative">
 | 
				
			||||||
      <div class="text-gray-400 inline-flex gap-2 relative">
 | 
						<div class="text-gray-500 inline-flex gap-2 relative">
 | 
				
			||||||
	  <p>Author</p>
 | 
						  <p>Author</p>
 | 
				
			||||||
	  <label class="my-auto" for="edit-author-button">
 | 
						  <label class="my-auto" for="edit-author-button">
 | 
				
			||||||
	    <svg
 | 
						    <svg
 | 
				
			||||||
@ -251,7 +271,7 @@
 | 
				
			|||||||
	  </label>
 | 
						  </label>
 | 
				
			||||||
	  <input type="checkbox" id="edit-author-button" class="hidden css-button"/>
 | 
						  <input type="checkbox" id="edit-author-button" class="hidden css-button"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="absolute z-50 top-7 right-0 p-3 transition-all duration-200 bg-gray-200 rounded shadow-lg shadow-gray-500 dark:shadow-gray-900 dark:bg-gray-600">
 | 
						  <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
 | 
						    <form
 | 
				
			||||||
	      method="POST"
 | 
						      method="POST"
 | 
				
			||||||
	      action="./{{ .Data.ID }}/edit"
 | 
						      action="./{{ .Data.ID }}/edit"
 | 
				
			||||||
@ -262,9 +282,12 @@
 | 
				
			|||||||
		id="author"
 | 
							id="author"
 | 
				
			||||||
		name="author"
 | 
							name="author"
 | 
				
			||||||
		value="{{ or .Data.Author "N/A" }}"
 | 
							value="{{ or .Data.Author "N/A" }}"
 | 
				
			||||||
	      class="p-2 bg-gray-400 text-black dark:bg-gray-700 dark:text-white"
 | 
							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 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Save</button>
 | 
						      <button
 | 
				
			||||||
 | 
							class="font-medium px-2 py-1 text-white bg-gray-500 dark:bg-gray-500 dark:text-gray-800 hover:bg-gray-800 dark:hover:bg-gray-100"
 | 
				
			||||||
 | 
							type="submit"
 | 
				
			||||||
 | 
						      >Save</button>
 | 
				
			||||||
	    </form>
 | 
						    </form>
 | 
				
			||||||
	  </div>
 | 
						  </div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
@ -273,26 +296,26 @@
 | 
				
			|||||||
	</p>
 | 
						</p>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
	<p class="text-gray-400">Time Read</p>
 | 
						  <p class="text-gray-500">Time Read</p>
 | 
				
			||||||
	  <p class="font-medium text-lg">
 | 
						  <p class="font-medium text-lg">
 | 
				
			||||||
	  {{ .Data.TotalTimeMinutes }} Minutes
 | 
						  {{ .Data.TotalTimeMinutes }} Minutes
 | 
				
			||||||
	  </p>
 | 
						  </p>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
	<p class="text-gray-400">Progress</p>
 | 
						  <p class="text-gray-500">Progress</p>
 | 
				
			||||||
	  <p class="font-medium text-lg">
 | 
						  <p class="font-medium text-lg">
 | 
				
			||||||
	  {{ .Data.CurrentPage }} / {{ .Data.TotalPages }} ({{ .Data.Percentage }}%)
 | 
						  {{ .Data.CurrentPage }} / {{ .Data.TotalPages }} ({{ .Data.Percentage }}%)
 | 
				
			||||||
	  </p>
 | 
						  </p>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <!--
 | 
					      <!--
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
	<p class="text-gray-400">ISBN 10</p>
 | 
						  <p class="text-gray-500">ISBN 10</p>
 | 
				
			||||||
	  <p class="font-medium text-lg">
 | 
						  <p class="font-medium text-lg">
 | 
				
			||||||
	  {{ or .Data.Isbn10 "N/A" }}
 | 
						  {{ or .Data.Isbn10 "N/A" }}
 | 
				
			||||||
	  </p>
 | 
						  </p>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
	<p class="text-gray-400">ISBN 13</p>
 | 
						  <p class="text-gray-500">ISBN 13</p>
 | 
				
			||||||
	  <p class="font-medium text-lg">
 | 
						  <p class="font-medium text-lg">
 | 
				
			||||||
	  {{ or .Data.Isbn13 "N/A" }}
 | 
						  {{ or .Data.Isbn13 "N/A" }}
 | 
				
			||||||
	  </p>
 | 
						  </p>
 | 
				
			||||||
@ -301,7 +324,7 @@
 | 
				
			|||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="relative">
 | 
					    <div class="relative">
 | 
				
			||||||
    <div class="text-gray-400 inline-flex gap-2 relative">
 | 
					      <div class="text-gray-500 inline-flex gap-2 relative">
 | 
				
			||||||
	<p>Description</p>
 | 
						<p>Description</p>
 | 
				
			||||||
	<label class="my-auto" for="edit-description-button">
 | 
						<label class="my-auto" for="edit-description-button">
 | 
				
			||||||
	  <svg
 | 
						  <svg
 | 
				
			||||||
@ -328,9 +351,9 @@
 | 
				
			|||||||
    <div class="relative font-medium text-justify hyphens-auto">
 | 
					    <div class="relative font-medium text-justify hyphens-auto">
 | 
				
			||||||
	<input type="checkbox" id="edit-description-button" class="hidden css-button"/>
 | 
						<input type="checkbox" id="edit-description-button" class="hidden css-button"/>
 | 
				
			||||||
	<div
 | 
						<div
 | 
				
			||||||
	class="absolute h-full w-full min-h-[10em] z-50 top-1 right-0 gap-4 flex transition-all duration-200"
 | 
						  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-40 md:w-60 lg:w-80 object-fill" src="../documents/{{.Data.ID}}/cover"></img>
 | 
						<img class="hidden md:block invisible rounded w-44 md:w-60 lg:w-80 object-fill" src="{{ .RelBase }}./documents/{{.Data.ID}}/cover"></img>
 | 
				
			||||||
	  <form
 | 
						  <form
 | 
				
			||||||
	    method="POST"
 | 
						    method="POST"
 | 
				
			||||||
	    action="./{{ .Data.ID }}/edit"
 | 
						    action="./{{ .Data.ID }}/edit"
 | 
				
			||||||
@ -340,14 +363,122 @@
 | 
				
			|||||||
	      type="text"
 | 
						      type="text"
 | 
				
			||||||
	      id="description"
 | 
						      id="description"
 | 
				
			||||||
	      name="description"
 | 
						      name="description"
 | 
				
			||||||
	    class="h-full w-full p-2 bg-gray-400 text-black dark:bg-gray-700 dark:text-white"
 | 
						      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>
 | 
						    >{{ or .Data.Description "N/A" }}</textarea>
 | 
				
			||||||
	  <button class="font-medium px-2 py-1 text-white bg-gray-500 dark:text-gray-800 dark:bg-gray-400 hover:bg-gray-800 dark:hover:bg-gray-100" type="submit">Save</button>
 | 
						    <button
 | 
				
			||||||
 | 
						      class="font-medium px-2 py-1 text-white bg-gray-500 dark: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>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  {{ if .MetadataError }}
 | 
				
			||||||
 | 
					  <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>
 | 
				
			||||||
 | 
					      <a href="{{ .RelBase }}./documents/{{ .Data.ID }}"
 | 
				
			||||||
 | 
						class="w-full text-center font-medium px-2 py-1 text-white bg-gray-500 dark: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="{{ .RelBase }}./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.GBID }}?fife=w480-h690"></img>
 | 
				
			||||||
 | 
						    </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.GBID }}">
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					      </form>
 | 
				
			||||||
 | 
					      <div class="flex justify-end gap-4 m-4">
 | 
				
			||||||
 | 
						<a href="{{ .RelBase }}./documents/{{ .Data.ID }}"
 | 
				
			||||||
 | 
						  class="w-24 text-center font-medium px-2 py-1 text-white bg-gray-500 dark: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: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>
 | 
					<style>
 | 
				
			||||||
  .css-button:checked + div {
 | 
					  .css-button:checked + div {
 | 
				
			||||||
    visibility: visible;
 | 
					    visibility: visible;
 | 
				
			||||||
 | 
				
			|||||||