1
0

Compare commits

...

20 Commits

Author SHA1 Message Date
d7dff35533
Version set to 24.53.0. 2024-12-31 00:13:37 +04:00
bf92e6ec7b
From go.mod a toolchain option was removed. 2024-12-30 21:22:50 +04:00
23bfb9ad4d
Use recommended way of access localStorage using setItem/getItem funcs. 2024-12-30 20:58:01 +04:00
1d956a8430
In main.js ' was replaced with ". 2024-12-30 20:40:26 +04:00
883835bf24
Keep sorting in local storage. 2024-12-30 20:39:38 +04:00
4b188da546
Localized size units in main.js. 2024-12-30 20:39:09 +04:00
6669337d3a
Fix for Size field. 2024-12-30 16:50:14 +04:00
aa387e2021
Remove unused func AssetsGetFile(). 2024-12-30 15:58:41 +04:00
3bb26f397c
Localization of size units. 2024-12-30 15:45:23 +04:00
28488a75c2
Added a fool proof to remove a trailing slash of a base directoryPath. 2024-12-30 15:31:48 +04:00
73a3b16f29
Fixed benchmark for ScanDirectory(). 2024-12-30 15:30:51 +04:00
dac60a6010
Fixed a current path under the table. 2024-12-30 15:29:59 +04:00
034216efd4
CurrentPath() func was moved to index.templ and was renamed to currentPathToLink(). 2024-12-30 15:00:22 +04:00
dc2729ff91
Changed logic of ScanDirectory() func. 2024-12-30 14:58:03 +04:00
2c8a9e3e61
In Index template removed one tab level to save horizontal space. 2024-12-27 16:35:03 +04:00
c63d59f0a4
Move root path out from CurrentPath() func and localise it. 2024-12-27 16:31:16 +04:00
edd1d9f16d
Fixed usage for -file-handling option. 2024-12-27 16:30:45 +04:00
cec25f6c6a
In Makefile make run and install targets to compile a program first. 2024-12-27 16:30:09 +04:00
fdec858f48
Version set to 24.52.0. 2024-12-27 16:29:19 +04:00
aeccc9d629
Found out it makes an audio element to collapse in chrome-based browsers, and in firefox does nothing. Removed. 2024-12-21 17:12:40 +04:00
14 changed files with 232 additions and 200 deletions

View File

@ -6,7 +6,7 @@ SYSDDIR=${SYSDDIR_:/%=%}
DESTDIR:=
PREFIX:=/usr/local
VERSION=24.50.0
VERSION=24.53.0
FLAGS=-buildmode=pie -modcacherw -mod=readonly -trimpath
LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags osusergo,netgo
@ -22,10 +22,10 @@ ifeq (,$(wildcard $(shell go env GOPATH)/bin/templ))
endif
$(shell go env GOPATH)/bin/templ generate
run:
run: | ${TARGET}
bin/${TARGET} -file-handling -path /mnt/data -listen 127.0.0.1:19135
install:
install: | ${TARGET}
install -Dm 0755 bin/${TARGET} ${DESTDIR}${PREFIX}/bin/${TARGET}
install -Dm 0644 init/systemd/${TARGET}.service ${DESTDIR}/${SYSDDIR}/${TARGET}.service

View File

@ -1,6 +1,6 @@
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
pkgname=dwelling-files
pkgver=24.50.0
pkgver=24.53.0
pkgrel=1
pkgdesc="Arav's dwelling / Files"
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')

View File

@ -21,7 +21,7 @@ var version string
var listenAddress *string = flag.String("listen", "/var/run/dwelling-files/sock", "listen address (ip:port|unix_path)")
var directoryPath *string = flag.String("path", "/srv/ftp", "path to file share")
var enableFileHandler *bool = flag.Bool("file-handling", false, "enable file handling if it is handled by something else (e.g. NGiNX)")
var enableFileHandler *bool = flag.Bool("file-handling", false, "enable file handling if it is not handled by something else")
var showVersion *bool = flag.Bool("v", false, "show version")
func main() {
@ -43,6 +43,10 @@ func main() {
r.Handler(http.MethodGet, "/*filepath", Index)
r.ServeStatic("/assets/*filepath", web.Assets())
if (*directoryPath)[len(*directoryPath)-1] == '/' {
*directoryPath = (*directoryPath)[:len(*directoryPath)-1]
}
var fileServer http.Handler
if *enableFileHandler {
fileServer = http.FileServer(http.Dir(*directoryPath))
@ -100,12 +104,12 @@ func Index(w http.ResponseWriter, r *http.Request) {
path += "/"
}
entries, stats, err := files.ScanDirectory(*directoryPath+path, path)
entries, stats, err := files.ScanDirectory(*directoryPath, path)
if err != nil {
log.Println("Error directory scan:", err)
http.Error(w, "Not found", http.StatusNotFound)
return
}
web.Index(files.CurrentPath(path), version, &stats, &entries, r).Render(r.Context(), w)
web.Index(path, version, &stats, &entries, r).Render(r.Context(), w)
}

2
go.mod
View File

@ -2,8 +2,6 @@ module dwelling-files
go 1.21
toolchain go1.23.4
require (
git.arav.su/Arav/httpr v0.3.2
github.com/a-h/templ v0.2.793

View File

@ -1,20 +0,0 @@
package files
import (
"strings"
)
func CurrentPath(path string) (curPath string) {
parts := strings.Split(path, "/")[1:]
curPath = "<a href=\"/\">root</a>"
for i, part := range parts {
var sb strings.Builder
sb.WriteString("/<a href=\"/")
sb.WriteString(strings.Join(parts[:i+1], "/"))
sb.WriteString("/\">")
sb.WriteString(part)
sb.WriteString("</a>")
curPath += sb.String()
}
return
}

View File

@ -10,9 +10,10 @@ import (
const FileDateFormat = "2006-01-02 15:04:05 MST"
type DirStat struct {
Files int64
FilesSize string
Directories int64
Files int64
FilesSize string
FilesSizeUnit string
Directories int64
}
type DirEntry struct {
@ -20,10 +21,17 @@ type DirEntry struct {
Link string
Datetime time.Time
Size string
SizeUnit string
}
func ScanDirectory(path, urlBase string) (entries []DirEntry, stats DirStat, err error) {
dir, err := os.ReadDir(path)
// ScanDirectory returns entries of directory which is located by its relative
// path within a base directory.
//
// rel path should start/end with a / symbol.
func ScanDirectory(base, rel string) (entries []DirEntry, stats DirStat, err error) {
abs := base + rel
dir, err := os.ReadDir(abs)
if err != nil {
return
}
@ -37,7 +45,7 @@ func ScanDirectory(path, urlBase string) (entries []DirEntry, stats DirStat, err
var isDirLink bool
if entry.Mode().Type()&os.ModeSymlink != 0 {
if slp, err := filepath.EvalSymlinks(filepath.Join(path, entry.Name())); err == nil {
if slp, err := filepath.EvalSymlinks(filepath.Join(abs, entry.Name())); err == nil {
lStat, _ := os.Lstat(slp)
isDirLink = lStat.IsDir()
}
@ -52,11 +60,13 @@ func ScanDirectory(path, urlBase string) (entries []DirEntry, stats DirStat, err
})
stats.Directories++
} else {
sz, ui := convertFileSize(entry.Size())
fileEntries = append(fileEntries, DirEntry{
Name: entry.Name(),
Link: "/file" + urlBase + url.PathEscape(entry.Name()),
Link: "/file" + rel + url.PathEscape(entry.Name()),
Datetime: entry.ModTime(),
Size: convertFileSize(entry.Size()),
Size: sz,
SizeUnit: ui,
})
totalFilesSize += entry.Size()
@ -64,7 +74,7 @@ func ScanDirectory(path, urlBase string) (entries []DirEntry, stats DirStat, err
}
}
stats.FilesSize = convertFileSize(totalFilesSize)
stats.FilesSize, stats.FilesSizeUnit = convertFileSize(totalFilesSize)
entries = append(entries, dirEntries...)
entries = append(entries, fileEntries...)

View File

@ -2,12 +2,12 @@ package files
import "testing"
const path = "/mnt/data/music/Various"
const urlBase = "/srv/ftp/"
const base = "/srv/ftp"
const path = "/music/Various"
func BenchmarkScanDirectory(b *testing.B) {
for i := 0; i < b.N; i++ {
/*e, _, _ :=*/ ScanDirectory(path, urlBase)
/*e, _, _ :=*/ ScanDirectory(base, path)
// b.Log(e[len(e)-1], len(e))
}
}

View File

@ -8,9 +8,9 @@ import (
var sizeSuffixes = [...]string{"B", "KiB", "MiB", "GiB", "TiB"}
// convertFileSize converts size in bytes down to biggest units it represents.
// Returns a concatenation of a converted size and a unit
func convertFileSize(size int64) string {
var idx int
// Returns a converted size string and a unit idx
func convertFileSize(size int64) (string, string) {
var idx uint8
var fSize float64 = float64(size)
for idx = 0; fSize >= 1024; fSize /= 1024 {
idx++
@ -18,5 +18,5 @@ func convertFileSize(size int64) string {
fSizeStr := strconv.FormatFloat(fSize, 'f', 3, 64)
fSizeStr = strings.TrimRight(fSizeStr, "0")
fSizeStr = strings.TrimSuffix(fSizeStr, ".")
return fSizeStr + " " + sizeSuffixes[idx]
return fSizeStr, sizeSuffixes[idx]
}

View File

@ -198,8 +198,7 @@ thead tr th.clickable.sort-down::after { content: '↓'; }
#overlay :is(video, audio, img) {
margin: auto;
max-height: 100%;
max-width: 100%;
width: auto; }
max-width: 100%; }
#overlay button {
background: none;

View File

@ -6,7 +6,7 @@ const overlay = document.getElementById("overlay");
const overlay_content = overlay.children[1];
const overlay_label = overlay.children[2];
const g_tbody = document.getElementsByTagName('tbody')[0];
const g_tbody = document.getElementsByTagName("tbody")[0];
let g_first_row = g_tbody.firstChild;
let g_last_row = g_tbody.lastChild;
const g_back_row = document.getElementsByTagName("tr")[1];
@ -17,8 +17,8 @@ let g_oc_translate = [0, 0];
let g_current_row = g_back_row;
if (localStorage.getItem('audio_volume') == null)
localStorage['audio_volume'] = 0.5;
if (localStorage.getItem("audio_volume") == null)
localStorage.setItem("audio_volume", 0.5);
function overlayClose() {
@ -80,8 +80,8 @@ function overlaySet(pathname, media_type_element) {
if (media_type_element !== "img") {
media_element.autoplay = media_element.controls = true;
media_element.addEventListener("volumechange", e => {
localStorage['audio_volume'] = e.target.volume; });
media_element.volume = localStorage["audio_volume"];
localStorage.setItem("audio_volume", e.target.volume); });
media_element.volume = localStorage.getItem("audio_volume");
media_element.addEventListener("ended", e => { if (overlay_autoplay.checked) b_next.click(); });
}
@ -143,13 +143,13 @@ Array.from(g_tbody.children)
overlay_autoplay = document.getElementsByName("autoplay")[0];
if (localStorage.getItem('autoplay') == null)
localStorage['autoplay'] = overlay_autoplay.checked = false;
else
overlay_autoplay.checked = localStorage['autoplay'];
if (localStorage.getItem("autoplay") == null) {
localStorage.setItem("autoplay", overlay_autoplay.checked = false);
} else
overlay_autoplay.checked = localStorage.getItem("autoplay");
overlay_autoplay.addEventListener("change", e => {
localStorage['autoplay'] = e.target.checked;
localStorage.setItem("autoplay", overlay_autoplay.checked);
const media_element = overlay_content.firstChild;
if (e.target.checked && media_element !== undefined)
if (media_element.tagName === "AUDIO" || media_element.tagName === "VIDEO")
@ -220,60 +220,11 @@ document.getElementsByName("filter")[0].addEventListener("input", e => filter(e.
//// SORT BY COLUMN
const units = {"B": 0, "KiB": 1, "MiB": 2, "GiB": 3, "TiB": 4};
const [thead_name, thead_date, thead_size] = document.getElementsByTagName('thead')[0]
const [thead_name, thead_date, thead_size] = document.getElementsByTagName("thead")[0]
.children[0].children;
let g_sort_reverse = false;
thead_name.classList.toggle("clickable");
thead_name.addEventListener('click', e => {
e.preventDefault();
sortTable((a,b) => {
const a_name = a.children[0].textContent.toLowerCase();
const b_name = b.children[0].textContent.toLowerCase();
return a_name < b_name ? -1 : a_name > b_name ? 1 : 0;
}, null, null, thead_name, [thead_date, thead_size]);
g_first_row = g_tbody.firstChild;
g_last_row = g_tbody.lastChild;
});
thead_date.classList.toggle("clickable");
thead_date.addEventListener('click', e => {
e.preventDefault();
sortTable((a,b) => {
const a_date = new Date(a.children[1].textContent.slice(0, -4));
const b_date = new Date(b.children[1].textContent.slice(0, -4));
return a_date - b_date;
}, null, null, thead_date, [thead_name, thead_size]);
g_first_row = g_tbody.firstChild;
g_last_row = g_tbody.lastChild;
});
function sizeToBytes(size, unit) {
if (units[unit] == 0) return size;
for (let i = 0; i <= units[unit]; ++i) size *= 1024;
return size;
}
thead_size.classList.toggle("clickable");
thead_size.addEventListener('click', e => {
e.preventDefault();
sortTable(
(a,b) => {
if (a.textContent == "DIR")
return 1;
let [a_size, a_unit] = a.children[2].textContent.split(" ");
let [b_size, b_unit] = b.children[2].textContent.split(" ");
return sizeToBytes(+a_size, a_unit) - sizeToBytes(+b_size, b_unit);
},
e => e.children[2].textContent == "DIR",
e => e.children[2].textContent != "DIR",
thead_size, [thead_name, thead_date]);
g_first_row = g_tbody.firstChild;
g_last_row = g_tbody.lastChild;
});
if (localStorage.getItem("sort_reverse") == null)
localStorage.setItem("sort_reverse", false);
function sortTable(compareFn, filterFn, filterNegFn, target, other) {
let records = Array.from(g_tbody.children);
@ -296,7 +247,7 @@ function sortTable(compareFn, filterFn, filterNegFn, target, other) {
if (filterFn != null)
g_tbody.append(...dirs);
if (g_sort_reverse) {
if (localStorage.getItem("sort_reverse") == "true") {
g_tbody.append(...records.reverse());
target.classList.add("sort-up");
target.classList.remove("sort-down");
@ -305,6 +256,68 @@ function sortTable(compareFn, filterFn, filterNegFn, target, other) {
target.classList.add("sort-down");
target.classList.remove("sort-up");
}
g_sort_reverse = !g_sort_reverse;
localStorage.setItem("sort_reverse", !(localStorage.getItem("sort_reverse") == "true"));
}
thead_name.classList.toggle("clickable");
thead_name.addEventListener("click", e => {
e.preventDefault();
sortTable((a,b) => {
const a_name = a.children[0].textContent.toLowerCase();
const b_name = b.children[0].textContent.toLowerCase();
return a_name < b_name ? -1 : a_name > b_name ? 1 : 0;
}, null, null, thead_name, [thead_date, thead_size]);
g_first_row = g_tbody.firstChild;
g_last_row = g_tbody.lastChild;
localStorage.setItem("sort_column", "name");
});
thead_date.classList.toggle("clickable");
thead_date.addEventListener("click", e => {
e.preventDefault();
sortTable((a,b) => {
const a_date = new Date(a.children[1].textContent.slice(0, -4));
const b_date = new Date(b.children[1].textContent.slice(0, -4));
return a_date - b_date;
}, null, null, thead_date, [thead_name, thead_size]);
g_first_row = g_tbody.firstChild;
g_last_row = g_tbody.lastChild;
localStorage.setItem("sort_column", "date");
});
const size_units = document.getElementsByTagName("html")[0].lang == "ru" ?
{"Б": 0, "КиБ": 1, "МиБ": 2, "ГиБ": 3, "ТиБ": 4} :
{"B": 0, "KiB": 1, "MiB": 2, "GiB": 3, "TiB": 4};
function sizeToBytes(size, unit) {
if (size_units[unit] == 0) return size;
for (let i = 0; i <= size_units[unit]; ++i) size *= 1024;
return size;
}
thead_size.classList.toggle("clickable");
thead_size.addEventListener("click", e => {
e.preventDefault();
sortTable(
(a,b) => {
if (a.textContent == "DIR")
return 1;
let [a_size, a_unit] = a.children[2].textContent.split(" ");
let [b_size, b_unit] = b.children[2].textContent.split(" ");
return sizeToBytes(+a_size, a_unit) - sizeToBytes(+b_size, b_unit);
},
e => e.children[2].textContent == "DIR",
e => e.children[2].textContent != "DIR",
thead_size, [thead_name, thead_date]);
g_first_row = g_tbody.firstChild;
g_last_row = g_tbody.lastChild;
localStorage.setItem("sort_column", "size");
});
localStorage.setItem("sort_reverse", !(localStorage.getItem("sort_reverse") == "true"));
switch (localStorage.getItem("sort_column")) {
case "name": thead_name.click(); break;
case "date": thead_date.click(); break;
case "size": thead_size.click(); break;
}

View File

@ -5,95 +5,113 @@ import "net/http"
import "dwelling-files/pkg/files"
import "dwelling-files/pkg/utils"
import "strconv"
import "strings"
func currentPathToLink(path string) (curPath string) {
parts := strings.Split(path, "/")[1:]
for i, part := range parts {
var sb strings.Builder
sb.WriteString("/<a href=\"/")
sb.WriteString(strings.Join(parts[:i+1], "/"))
sb.WriteString("/\">")
sb.WriteString(part)
sb.WriteString("</a>")
curPath += sb.String()
}
return
}
templ Index(currentPath, progVer string, stat *files.DirStat, entries *[]files.DirEntry, r *http.Request) {
<!doctype html>
<html lang={ i18n.GetLocale(ctx).Code().String() }>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#cd2682" />
<meta name="color-scheme" content="light dark" />
<!doctype html>
<html lang={ i18n.GetLocale(ctx).Code().String() }>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#cd2682" />
<meta name="color-scheme" content="light dark" />
<title>Arav's dwelling / { i18n.T(ctx, "title") }</title>
<title>Arav's dwelling / { i18n.T(ctx, "title") }</title>
<meta name="description" content={ i18n.T(ctx, "description") } />
<meta name="keywords" content={ i18n.T(ctx, "keywords") } />
<meta name="description" content={ i18n.T(ctx, "description") } />
<meta name="keywords" content={ i18n.T(ctx, "keywords") } />
<link rel="icon" href="/assets/img/favicon.svg" sizes="any" type="image/svg+xml"/>
<link rel="stylesheet" href="/assets/css/main.css" />
<script src="/assets/js/main.js" defer></script>
</head>
<body>
<header>
<svg width="360" viewBox="0 -36 360 66">
<text y="7" textLength="360" lengthAdjust="spacingAndGlyphs">Arav's dwelling</text>
<text y="25" textLength="360" lengthAdjust="spacingAndGlyphs">Welcome to my sacred place, wanderer</text>
</svg>
<nav>
<a href={ templ.SafeURL(utils.MainSite(r.Host)) }>{ i18n.T(ctx, "back-home") }</a>
<h1>{ i18n.T(ctx, "title") }</h1>
</nav>
</header>
<main>
<section>
<span>@templ.Raw(currentPath)</span>
<p>{ i18n.T(ctx, "stats.files") }: { strconv.FormatInt(stat.Files, 10) } ({ stat.FilesSize }); { i18n.T(ctx, "stats.directories") }: { strconv.FormatInt(stat.Directories, 10) }.</p>
<input type="text" name="filter" placeholder={ i18n.T(ctx, "stats.filter") } class="hidden">
</section>
<section>
<table>
<thead>
<tr>
<th>{ i18n.T(ctx, "table.name") }</th>
<th>{ i18n.T(ctx, "table.date") }</th>
<th>{ i18n.T(ctx, "table.size") }</th>
<link rel="icon" href="/assets/img/favicon.svg" sizes="any" type="image/svg+xml"/>
<link rel="stylesheet" href="/assets/css/main.css" />
<script src="/assets/js/main.js" defer></script>
</head>
<body>
<header>
<svg width="360" viewBox="0 -36 360 66">
<text y="7" textLength="360" lengthAdjust="spacingAndGlyphs">Arav's dwelling</text>
<text y="25" textLength="360" lengthAdjust="spacingAndGlyphs">Welcome to my sacred place, wanderer</text>
</svg>
<nav>
<a href={ templ.SafeURL(utils.MainSite(r.Host)) }>{ i18n.T(ctx, "back-home") }</a>
<h1>{ i18n.T(ctx, "title") }</h1>
</nav>
</header>
<main>
<section>
<span><a href="/">{ i18n.T(ctx, "curpath-root") }</a>@templ.Raw(currentPathToLink(currentPath))</span>
<p>{ i18n.T(ctx, "stats.files") }: { strconv.FormatInt(stat.Files, 10) } ({ stat.FilesSize }); { i18n.T(ctx, "stats.directories") }: { strconv.FormatInt(stat.Directories, 10) }.</p>
<input type="text" name="filter" placeholder={ i18n.T(ctx, "stats.filter") } class="hidden">
</section>
<section>
<table>
<thead>
<tr>
<th>{ i18n.T(ctx, "table.name") }</th>
<th>{ i18n.T(ctx, "table.date") }</th>
<th>{ i18n.T(ctx, "table.size") }</th>
</tr>
<tr tabindex="0">
<td><a href="../">../</a></td>
</tr>
</thead>
<tbody>
for i, entry := range *entries {
<tr tabindex={ strconv.FormatInt(int64(i)+1, 10) }>
<td><a href={ templ.SafeURL(entry.Link) }>{ entry.Name }</a></td>
<td>{ utils.ToClientTimezone(entry.Datetime, r).Format(files.FileDateFormat) }</td>
if entry.Size == "DIR" {
<td>DIR</td>
} else {
<td>{ entry.Size + " " + i18n.T(ctx, "size-unit."+entry.SizeUnit) }</td>
}
</tr>
<tr tabindex="0">
<td><a href="../">../</a></td>
</tr>
</thead>
<tbody>
for i, entry := range *entries {
<tr tabindex={ strconv.FormatInt(int64(i)+1, 10) }>
<td><a href={ templ.SafeURL(entry.Link) }>{ entry.Name }</a></td>
<td>{ utils.ToClientTimezone(entry.Datetime, r).Format(files.FileDateFormat) }</td>
<td>{ entry.Size }</td>
</tr>
}
</tbody>
</table>
</section>
}
</tbody>
</table>
</section>
<section>
<span><a href="/">{ i18n.T(ctx, "curpath-root") }</a>@templ.Raw(currentPathToLink(currentPath))</span>
</section>
<noscript>
<section>
<span>@templ.Raw(currentPath)</span>
<p>{ i18n.T(ctx, "no-script-explain") }</p>
</section>
<noscript>
<section>
<p>{ i18n.T(ctx, "no-script-explain") }</p>
</section>
</noscript>
<section id="instruction" class="hidden">
<p>{ i18n.T(ctx, "instruction") }</p>
</section>
</main>
<footer>
<a href="?lang=ru">рус</a>
<a href="?lang=en">eng</a>
<br/>
v{ progVer } 2017&mdash;2024 { i18n.T(ctx, "footer.author") } &lt;<a href="mailto:me@arav.su">me@arav.su</a>&gt; <a href={ templ.SafeURL(utils.MainSite(r.Host) + "/privacy") }>{ i18n.T(ctx, "footer.privacy") }</a>
</footer>
<div id="overlay">
<button name="prev">&#10096;</button>
<div></div>
<span></span>
<span class="c-autoplay">
<input id="c-autoplay" type="checkbox" name="autoplay">
<label for="c-autoplay">{ i18n.T(ctx, "autoplay") }</label>
</span>
<button name="next">&#10097;</button>
</div>
</body>
</html>
</noscript>
<section id="instruction" class="hidden">
<p>{ i18n.T(ctx, "instruction") }</p>
</section>
</main>
<footer>
<a href="?lang=ru">рус</a>
<a href="?lang=en">eng</a>
<br/>
v{ progVer } 2017&mdash;2024 { i18n.T(ctx, "footer.author") } &lt;<a href="mailto:me@arav.su">me@arav.su</a>&gt; <a href={ templ.SafeURL(utils.MainSite(r.Host) + "/privacy") }>{ i18n.T(ctx, "footer.privacy") }</a>
</footer>
<div id="overlay">
<button name="prev">&#10096;</button>
<div></div>
<span></span>
<span class="c-autoplay">
<input id="c-autoplay" type="checkbox" name="autoplay">
<label for="c-autoplay">{ i18n.T(ctx, "autoplay") }</label>
</span>
<button name="next">&#10097;</button>
</div>
</body>
</html>
}

View File

@ -3,6 +3,7 @@ en:
description: My file share
keywords: files ftp share self-host
back-home: Back home
curpath-root: root
stats:
files: Files
directories: Directories
@ -16,4 +17,10 @@ en:
autoplay: Autoplay
footer:
author: Alexander ❝Arav❞ Andreev
privacy: Privacy statements
privacy: Privacy statements
size-unit:
B: "B"
KiB: "KiB"
MiB: "MiB"
GiB: "GiB"
TiB: "TiB"

View File

@ -3,6 +3,7 @@ ru:
description: Моя файловая шара
keywords: файлы шара ftp селф-хост само-хост self-host
back-home: Назад домой
curpath-root: корень
stats:
files: Файлы
directories: Директории
@ -16,4 +17,10 @@ ru:
autoplay: Автовоспроизведение
footer:
author: Александр «Arav» Андреев
privacy: О приватности
privacy: О приватности
size-unit:
B: "Б"
KiB: "КиБ"
MiB: "МиБ"
GiB: "ГиБ"
TiB: "ТиБ"

View File

@ -13,7 +13,3 @@ func Assets() http.FileSystem {
f, _ := fs.Sub(assetsDir, "assets")
return http.FS(f)
}
func AssetsGetFile(path string) ([]byte, error) {
return assetsDir.ReadFile("assets/" + path)
}