Skip to content

Commit

Permalink
Merge branch 'main' of github.com:svera/coreander into resume-reading
Browse files Browse the repository at this point in the history
  • Loading branch information
svera committed Jan 15, 2025
2 parents 44ad08f + a568c4e commit b42e4e8
Show file tree
Hide file tree
Showing 25 changed files with 201 additions and 197 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

A personal documents server, Coreander indexes the documents (EPUBs and PDFs with no DRM) that it finds in the passed folder, and provides a web interface to search and access them.

![Coreander screenshot](screenshot.png)
![Coreander home](home.png)
*Coreander home*

## Features
* Single binary with all dependencies included.
Expand Down Expand Up @@ -130,3 +131,12 @@ On first run, Coreander creates an admin user with the following credentials:
* `RECOVERY_TIMEOUT`: Specifies the maximum time a user recovery link may last, in hours. Floating-point values are allowed. Defaults to 2 hours.
* `UPLOAD_DOCUMENT_MAX_SIZE`: Maximum document size allowed to be uploaded to the library, in megabytes. Set this to 0 to unlimit upload size. Defaults to 20 megabytes.
* `FQDN`: Domain name of the server. If Coreander is listening to a non-standard HTTP / HTTPS port, include it using a colon (e. g. example.com:3000). Defaults to `localhost`.

## Screenshots

![Search results](search-results.png)
*Search results*
![Document detail in dark mode](doc-detail.png)
*Document detail in dark mode*
![Reading interface](reading.png)
*Reading interface*
Binary file added doc-detail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions internal/index/bleve_read.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func (b *BleveIndexer) SameSubjects(slugID string, quantity int) ([]Document, er
typeQuery.SetField("Type")
bq.AddMust(typeQuery)

res := make([]Document, quantity)
res := make([]Document, 0, quantity)
for i := 0; i < quantity; i++ {
doc, err := b.runQuery(bq, 1)
if err != nil {
Expand All @@ -278,7 +278,7 @@ func (b *BleveIndexer) SameSubjects(slugID string, quantity int) ([]Document, er
if len(doc) == 0 {
return res, nil
}
res[i] = doc[0]
res = append(res, doc[0])
for _, slug := range doc[0].AuthorsSlugs {
qa := bleve.NewTermQuery(slug)
qa.SetField("AuthorsSlugs")
Expand Down
2 changes: 1 addition & 1 deletion internal/webserver/controller/auth/signin.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (a *Controller) SignIn(c *fiber.Ctx) error {
}, "layout")
}

user.LastLogin = time.Now()
user.LastLogin = time.Now().UTC()
if err := a.repository.Update(user); err != nil {
log.Printf("error updating user last login time: %v\n", err)
return fiber.ErrInternalServerError
Expand Down
2 changes: 1 addition & 1 deletion internal/webserver/controller/user/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
func (u *Controller) Create(c *fiber.Ctx) error {
role, _ := strconv.Atoi(c.FormValue("role"))
user := model.User{
Name: c.FormValue("name"),
Name: strings.TrimSpace(c.FormValue("name")),
Username: strings.ToLower(c.FormValue("username")),
Email: c.FormValue("email"),
SendToEmail: c.FormValue("send-to-email"),
Expand Down
5 changes: 5 additions & 0 deletions internal/webserver/embedded/css/bootstrap-icons.min.css

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions internal/webserver/embedded/css/display.css
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,19 @@ a.collapse-control.collapsed:after {
.actions .dropdown-menu {
white-space: nowrap;
}

.datetime::first-letter {
text-transform: capitalize;
}

.zoomable {
overflow: hidden;
}

.zoomable img {
transition: all .3s ease;
}

.zoomable img:hover {
transform: scale(1.1);
}
Binary file not shown.
Binary file not shown.
11 changes: 11 additions & 0 deletions internal/webserver/embedded/js/datetime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use strict";

import { DateTime } from "./luxon.min.js";

document.addEventListener('DOMContentLoaded', function() {
const datetime = document.querySelectorAll('.datetime span');
datetime.forEach(function(element) {
const dt = DateTime.fromISO(element.textContent);
element.textContent = dt.toRelative({ locale: document.documentElement.lang });
});
});
1 change: 1 addition & 0 deletions internal/webserver/embedded/js/luxon.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion internal/webserver/embedded/translations/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,5 @@
"Titles by %s": "Títulos de %s"
"Send to %s": "Enviar a %s"
"Home": "Inicio"
"Last login": "Último Acceso"
"Last login": "Último acceso"
"Never": "Nunca"
142 changes: 66 additions & 76 deletions internal/webserver/embedded/views/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<!-- Bootstrap core CSS -->
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/display.css" rel="stylesheet">
<link href="/css/bootstrap-icons.min.css" rel="stylesheet">
<!-- Favicons -->
<meta name="theme-color" content="#000">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
Expand All @@ -24,7 +25,7 @@

<body class="d-flex flex-column h-100" hx-ext="response-targets">
<header>
<nav class="navbar navbar-expand-md fixed-top {{if .HomeNavbar}}navbar-home{{end}}">
<nav class="navbar navbar-expand-lg fixed-top {{if .HomeNavbar}}navbar-home{{end}}">
<div class="container d-flex justify-content-end">
{{if not .HomeNavbar}}
<span class="navbar-brand mb-0 h1">
Expand All @@ -36,86 +37,57 @@
<div class="input-group input-group-sm">
<label for="search" class="visually-hidden">{{t .Lang "search"}}</label>
<input type="search" name="search" id="searchbox" class="form-control" placeholder={{t .Lang "Title, authors, subjects, etc."}} maxlength="255" required autofocus value="{{.Keywords}}">
<button class="btn btn-outline-secondary" type="submit"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/>
</svg></button>
<button class="btn btn-outline-secondary" type="submit"><i class="bi-search"></i></button>
</div>
</form>
{{end}}
<button class="navbar-toggler" type="button" data-bs-toggle="offcanvas"
data-bs-target="#offcanvasNavbar" aria-controls="offcanvasNavbar">
<span class="navbar-toggler-icon"></span>
</button>
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNavbar"
<div class="offcanvas-lg offcanvas-end" tabindex="-1" id="offcanvasNavbar"
aria-labelledby="offcanvasNavbarLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasNavbarLabel">Coreander</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
<h5 class="offcanvas-title" id="offcanvasNavbarLabel">
<img src="/images/coreander-logo-small.png" alt="Logo" width="32" height="32" class="d-inline-block align-text-middle">
{{.Session.Name}}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#offcanvasNavbar" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3 text-start">
{{if and (.Session) (ne .Session.Name "")}}
<li class="nav-item dropdown">
<button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
{{.Session.Name}}
</button>
<ul class="dropdown-menu">
<li>
{{if eq .Session.Role 2}}
<a class="dropdown-item" href="/users">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-people-fill" viewBox="0 0 16 16">
<path
d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7Zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm-5.784 6A2.238 2.238 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1h4.216ZM4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z" />
</svg>
{{t .Lang "Users"}}
</a>
{{else}}
<a class="dropdown-item" href="/users/{{.Session.Username}}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-person-fill" viewBox="0 0 16 16">
<path
d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3Zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" />
</svg>
{{t .Lang "Update your profile"}}
</a>
{{end}}
</li>
<li>
<a class="dropdown-item" href="/highlights">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16">
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
</svg>
{{t .Lang "Highlights"}}
</a>
</li>
{{if eq .Session.Role 2}}
<li>
<a class="dropdown-item" href="/upload">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-cloud-upload-fill" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 0a5.53 5.53 0 0 0-3.594 1.342c-.766.66-1.321 1.52-1.464 2.383C1.266 4.095 0 5.555 0 7.318 0 9.366 1.708 11 3.781 11H7.5V5.707L5.354 7.854a.5.5 0 1 1-.708-.708l3-3a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 5.707V11h4.188C14.502 11 16 9.57 16 7.773c0-1.636-1.242-2.969-2.834-3.194C12.923 1.999 10.69 0 8 0m-.5 14.5V11h1v3.5a.5.5 0 0 1-1 0"/>
</svg>
{{t .Lang "Upload document"}}
</a>
</li>
{{end}}
<li>
<hr class="dropdown-divider">
</li>
<li>
<a class="dropdown-item" href="/sessions" hx-delete="/sessions">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-box-arrow-right" viewBox="0 0 16 16">
<path fill-rule="evenodd"
d="M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0v2z" />
<path fill-rule="evenodd"
d="M15.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L14.293 7.5H5.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z" />
</svg>
{{t .Lang "Logout"}}
</a>
</li>
</ul>
<li class="p-2">
{{if eq .Session.Role 2}}
<a href="/users" class="link-underline link-underline-opacity-0">
<i class="bi-person-fill"></i>

{{t .Lang "Users"}}
</a>
{{else}}
<a href="/users/{{.Session.Username}}" class="link-underline link-underline-opacity-0">
<i class="bi-person-fill"></i>

{{t .Lang "Update your profile"}}
</a>
{{end}}
</li>
<li class="p-2">
<a href="/highlights" class="link-underline link-underline-opacity-0">
<i class="bi-star-fill"></i>

{{t .Lang "Highlights"}}
</a>
</li>
{{if eq .Session.Role 2}}
<li class="p-2">
<a href="/upload" class="link-underline link-underline-opacity-0">
<i class="bi-cloud-upload-fill"></i>

{{t .Lang "Upload document"}}
</a>
</li>
{{end}}
{{else if not .DisableLoginLink}}
<li class="p-2">
<a href="/sessions/new">{{t .Lang "Login"}}</a>
Expand All @@ -125,17 +97,35 @@ <h5 class="offcanvas-title" id="offcanvasNavbarLabel">Coreander</h5>
{{$lang := .Lang}}
{{$URLPath := .URLPath}}
{{$queryString := .QueryString}}
{{range $i, $currentLang := .SupportedLanguages}}
{{if eq $lang $currentLang}}
<li class="p-2">{{uppercase $currentLang}}</li>
{{else if eq $queryString ""}}
<li class="p-2"><a href="{{$URLPath}}?l={{$currentLang}}">{{uppercase $currentLang}}</a></li>
{{else}}
<li class="p-2"><a href="{{$URLPath}}?{{$queryString}}&l={{$currentLang}}">{{uppercase $currentLang}}</a></li>
<li class="nav-item dropdown">
<button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi-globe"></i>
</button>
<ul class="dropdown-menu">
{{range $i, $currentLang := .SupportedLanguages}}
{{if eq $lang $currentLang}}
<li class="dropdown-item">{{uppercase $currentLang}}</li>
{{else if eq $queryString ""}}
<li class="dropdown-item"><a href="{{$URLPath}}?l={{$currentLang}}">{{uppercase $currentLang}}</a></li>
{{else}}
<li class="dropdown-item"><a href="{{$URLPath}}?{{$queryString}}&l={{$currentLang}}">{{uppercase $currentLang}}</a></li>
{{end}}
{{end}}
{{end}}
</ul>
</li>
<li class="nav-item dropdown">
{{template "partials/color-mode-toggle" dict "Lang" $lang}}
</li>
{{if and (.Session) (ne .Session.Name "")}}
<hr class="d-lg-none">
{{template "partials/color-mode-toggle" dict "Lang" $lang}}
<li class="p-2">
<a href="/sessions" hx-delete="/sessions" class="link-underline link-underline-opacity-0">
<i class="bi-box-arrow-right"></i>

{{t .Lang "Logout"}}
</a>
</li>
{{end}}
</ul>
</div>
</div>
Expand Down
Loading

0 comments on commit b42e4e8

Please sign in to comment.