diff options
-rw-r--r-- | main.go | 81 | ||||
-rw-r--r-- | static/favicon.svg | 53 | ||||
-rw-r--r-- | static/style.css | 250 | ||||
-rw-r--r-- | templates/archive.html | 19 | ||||
-rw-r--r-- | templates/index.html | 1 |
5 files changed, 153 insertions, 251 deletions
diff --git a/main.go b/main.go index 656b816..4fa807b 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,34 @@ +// Server entry point +// Copyright (C) 2022 Nguyễn Gia Phong +// +// This file is part of Phylactery. +// +// Phylactery is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Phylactery is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Phylactery. If not, see <https://www.gnu.org/licenses/>. + package main import ( + "archive/zip" "embed" "html/template" "log" + "io" "net/http" "os" + "path" + "strconv" + "strings" ) //go:embed static/* @@ -14,14 +37,70 @@ var static embed.FS //go:embed templates/*.html var templates embed.FS +type Page struct { + Index int + Name string +} + +type Archive struct { + Path string + Title string + Entries []Page +} + func main() { http.Handle("/static/", http.FileServer(http.FS(static))) t, err := template.ParseFS(templates, "templates/*.html") if err != nil { log.Fatal(err) } + lib := os.Getenv("PHYLACTERY_LIBRARY") http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - t.ExecuteTemplate(w, "index.html", "Phylactery") + p := path.Clean(lib + r.URL.Path) + stat, err := os.Stat(p) + if err != nil { + http.NotFound(w, r) + return + } + + if stat.IsDir() { + // TODO + } + + // TODO: LRU caching + cbz, err := zip.OpenReader(p) + if err != nil { + http.Error(w, "invalid cbz", 406) + return + } + defer cbz.Close() + + r.ParseForm() + if entry, isImage := r.Form["entry"]; isImage { + i, err := strconv.Atoi(entry[0]) + if err != nil || i < 0 || i >= len(cbz.File) { + http.NotFound(w, r) + return + } + image, _ := cbz.File[i].Open() + defer image.Close() + io.Copy(w, image) + return + } + + var pages []Page + for i, f := range cbz.File { + image, _ := cbz.File[i].Open() + defer image.Close() + buf := make([]byte, 512) + n, _ := image.Read(buf) + mime := http.DetectContentType(buf[:n]) + if strings.HasPrefix(mime, "image/") { + pages = append(pages, Page{ i, f.Name }) + } + } + archive := Archive{ r.URL.Path, stat.Name(), pages } + t.ExecuteTemplate(w, "archive.html", archive) }) addr := os.Getenv("PHYLACTERY_ADDRESS") diff --git a/static/favicon.svg b/static/favicon.svg new file mode 100644 index 0000000..2305a95 --- /dev/null +++ b/static/favicon.svg @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg" xmlns:ns1="http://sozi.baierouge.fr" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg2" sodipodi:modified="true" sodipodi:docname="scroll.svg" viewBox="0 0 216.61 247.01" sodipodi:version="0.32" version="1.0" inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:version="0.45pre1" sodipodi:docbase="C:\scratch\clipart"> + <sodipodi:namedview id="base" bordercolor="#666666" inkscape:pageshadow="2" guidetolerance="10" pagecolor="#ffffff" gridtolerance="10000" inkscape:window-height="569" inkscape:zoom="1.5822796" objecttolerance="10" borderopacity="1.0" inkscape:current-layer="layer1" inkscape:cx="106.43052" inkscape:cy="121.74098" inkscape:window-y="1212" inkscape:window-x="1504" inkscape:window-width="744" showborder="true" inkscape:pageopacity="0.0" inkscape:document-units="px"/> + <g id="layer1" inkscape:label="Layer 1" inkscape:groupmode="layer" transform="translate(66.751 -781.12)"> + <g id="g3159"> + <path id="path4447" sodipodi:nodetypes="csssccssscc" style="fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-width:3;fill:#ffffff" d="m-41.494 1010.7c9.375 1.5 19.091-7.3 25.403-23.73 9.7605-25.4 10.036-62.77 1.938-90.25-8.099-27.48-7.823-64.85 1.937-90.25 6.1828-16.1 15.658-24.84 24.882-23.76l112.1 15.9c-9.43-1.63-19.24 7.15-25.58 23.66-9.761 25.4-10.037 62.77-1.938 90.25 8.098 27.47 7.828 64.84-1.937 90.28-6.161 16-15.592 24.7-24.726 23.7l-112.08-15.8z"/> + <path id="path4458" sodipodi:nodetypes="csccssc" style="fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-width:3;fill:#ffffff" d="m121.84 798.44c-8.48 0.44-16.96 9.03-22.652 23.84-7.279 18.95-9.248 44.53-6.438 67.72l36.41 5.16c4.82 0.6 9.95-3.72 13.43-11.41 9.03-19.91 6.84-52.78-2.25-70.66-5.29-10.42-11.9-14.99-18.5-14.65z"/> + <path id="path4496" sodipodi:nodetypes="cssc" style="stroke:#000000;stroke-linecap:round;stroke-width:3;fill:none" d="m129.91 895.22c-3.97-0.07-7.85-3.37-10.61-10.72-3.34-8.89-4.01-25.85 1.59-31.49 2.06-2.08 7.53 0.53 5.42 7.69"/> + <path id="path4498" sodipodi:nodetypes="cc" style="stroke:#000000;stroke-linecap:round;stroke-width:3;fill:none" d="m123.17 852.36l-30.261-4.29"/> + <path id="path4500" sodipodi:nodetypes="cc" style="stroke:#000000;stroke-linecap:round;stroke-width:3;fill:none" d="m126.31 860.7l-8.61-1.22"/> + <path id="path4504" sodipodi:nodetypes="ccssccssc" style="fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-width:3;fill:#ffffff" d="m70.622 1026.6l-112.09-15.9c-5.669-0.9-11.2-5.6-15.762-14.57-9.082-17.88-11.284-50.74-2.256-70.65 3.415-7.53 8.371-11.83 13.136-11.45l112.08 15.9c-4.966-0.88-10.234 3.46-13.81 11.35-9.029 19.91-6.826 52.77 2.256 70.62 4.748 9.4 10.544 14 16.446 14.7z"/> + <path id="path4523" sodipodi:nodetypes="csss" style="stroke:#000000;stroke-linecap:round;stroke-width:3;fill:none" d="m68.193 964.34c-2.108 7.16 3.359 9.76 5.424 7.68 5.597-5.63 4.923-22.59 1.585-31.48-2.673-7.11-6.383-10.43-10.219-10.71"/> + <path id="path4525" sodipodi:nodetypes="cc" style="stroke:#000000;stroke-linecap:round;stroke-width:3;fill:none" d="m71.484 972.69l-25.265-3.58"/> + <path id="path4527" sodipodi:nodetypes="cc" style="stroke:#000000;stroke-linecap:round;stroke-width:3;fill:none" d="m68.181 964.34l-21.409-3.03"/> + </g> + </g> + <metadata> + <rdf:RDF> + <cc:Work> + <dc:format>image/svg+xml</dc:format> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/> + <dc:publisher> + <cc:Agent rdf:about="http://openclipart.org/"> + <dc:title>Openclipart</dc:title> + </cc:Agent> + </dc:publisher> + <dc:title>scroll</dc:title> + <dc:date>2007-11-08T21:26:16</dc:date> + <dc:description>Scroll of paper, white filled.</dc:description> + <dc:source>https://openclipart.org/detail/8032/scroll-by-kelan</dc:source> + <dc:creator> + <cc:Agent> + <dc:title>kelan</dc:title> + </cc:Agent> + </dc:creator> + <dc:subject> + <rdf:Bag> + <rdf:li>paper</rdf:li> + <rdf:li>scroll</rdf:li> + <rdf:li>white</rdf:li> + </rdf:Bag> + </dc:subject> + </cc:Work> + <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/"> + <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> + </cc:License> + </rdf:RDF> + </metadata> +</svg> \ No newline at end of file diff --git a/static/style.css b/static/style.css deleted file mode 100644 index d7bea79..0000000 --- a/static/style.css +++ /dev/null @@ -1,250 +0,0 @@ -/* Variables */ -:root { - --black: #1c1b19; - --red: #ef2f27; - --green: #519f50; - --yellow: #fbb829; - --blue: #2c78bf; - --magenta: #e02c6d; - --cyan: #0aaeb3; - --white: #d0bfa1; - --bright-black: #918175; - --bright-red: #f75341; - --bright-green: #98bc37; - --bright-yellow: #fed06e; - --bright-blue: #68a8e4; - --bright-magenta: #ff5c8f; - --bright-cyan: #53fde9; - --bright-white: #fce8c3; - - --block-bg: var(--black); - --fade-fg: var(--bright-black); - --link-fg: var(--green); - --overlay-bg: #8881; - --text-bg: #121212; - --text-fg: var(--bright-white); -} - -@media (prefers-color-scheme: light) { - :root { - --block-bg: #eeeeec; - --fade-fg: #888a85; - --link-fg: #436e58; - --text-bg: #ffffff; - --text-fg: #2e3436; - } -} - -/* Default font and layout */ -html { - background-color: var(--text-bg); - box-sizing: border-box; - color: var(--text-fg); - font-size: min(max(100%, 2vw), 150%); - margin: auto; - max-width: 36rem; - scrollbar-color: var(--fade-fg) var(--overlay-bg); -} - -body { margin: 0 1rem } - -.franklin-content .row { display: block } - -/* Text geometry */ -p, details { - hyphens: auto; - line-height: 1.4rem; - text-align: justify; -} - -/* Titles */ -.franklin-content h1 a, -.franklin-content h2 a, -.franklin-content h3 a, -.franklin-content h4 a, -.franklin-content h5 a, -.franklin-content h6 a { - color: var(--text-fg); -} - -.franklin-content h1 a:hover, -.franklin-content h2 a:hover, -.franklin-content h3 a:hover, -.franklin-content h4 a:hover, -.franklin-content h5 a:hover, -.franklin-content h6 a:hover { - text-decoration: none; -} - -.franklin-toc ol ol { - list-style-type: lower-alpha; -} - -/* General formatting */ -.franklin-content li p { margin: 0 } - -.franklin-content a { - color: var(--link-fg); - text-decoration: none; -} - -.franklin-content a:hover { - text-decoration: underline; - } - -/* Hyperrefs and footnotes */ -.franklin-content .bibref a, -.franklin-content .eqref a { color: var(--link-fg) } - -.franklin-content .fndef { - border: none; - margin: 1ex 0; -} -.franklin-content .fndef tr { text-align: left } -.franklin-content .fndef td { padding: 0 } -.franklin-content .fndef td, .franklin-content sup { font-size: 80% } -.franklin-content .fndef td.fndef-backref { padding-left: 0 } -.franklin-content .fndef td.fndef-content { padding-left: 1ch } - -/* Images */ -.franklin-content img { - display: block; - margin: auto; - max-width: 100%; -} - -/* KaTeX */ -.katex { font-size: 1em !important } - -/* Boxes */ -.franklin-content blockquote, .note { - margin: 0 -1rem; - padding-bottom: 1ex; - padding-left: 0.75rem; - padding-right: 1rem; - padding-top: 1ex; -} -.franklin-content blockquote p, .note p { margin: 1ex 0 } -.note p:first-child { font-weight: bold } -.franklin-content blockquote { - background: var(--overlay-bg); - border-left: 0.25rem solid #8884; -} -.note { - background-color: #51affe25; - border-left: 0.25rem solid var(--blue); -} - -/* Header */ -header { - margin: 1.5rem -0.5rem; - display: flex; - flex-wrap: wrap; - justify-content: space-between; - align-items: center; -} - -header a { - border-bottom: solid; - color: var(--link-fg); - font-weight: bold; - margin: 0 0.5rem; - text-decoration: none; - transition: color 0.3s ease; -} -header a:hover { color: var(--text-fg) } - -nav, nav li { display: inline-block } -nav ul { margin: 0 } - -footer, .tags, .right { - color: var(--fade-fg); - font-size: 80%; -} - -footer { - margin-top: 1.5rem; - margin-bottom: 2rem; -} - -footer a, .tags a { - color: var(--fade-fg)!important; - text-decoration: underline!important; -} - -.nowrap { display: inline-block } -.tags { float: left } -.right { - float: right; - margin-left: auto; -} - -/* Table */ -table { - border-bottom: 0.15em solid var(--text-fg); - border-collapse: collapse; - border-top: 0.15em solid var(--text-fg); - line-height: 1em; - margin-bottom: 1.5em; - margin-left: auto; - margin-right: auto; - text-align: center; -} - -tr:first-of-type > th { border-bottom: 0.08em solid var(--text-fg) } -tr, th, td { padding: 0.5em } - -/* highlight.js */ -code, .hljs { - background-color: var(--block-bg); - font-size: inherit; - padding: 0.1em 0.2em; -} - -.hljs, .comment pre > code { - display: block; - line-height: 1.45em; - overflow-x: auto; -} - -.hljs-built_in .hljs-name, .hljs-title, .hljs-type { color: var(--cyan) } -.hljs-code, .hljs-selector-class { color: var(--bright-blue) } -.hljs-comment, .hljs-deletion, .hljs-meta { color: var(--fade-fg) } -.hljs-comment, .hljs-emphasis { font-style: italic; } -.hljs-string { color: var(--bright-green) } -.hljs-strong { font-weight: bold; } - -.hljs-bullet, .hljs-quote, .hljs-link, .hljs-number, -.hljs-regexp, .hljs-literal { color: var(--bright-magenta) } - -.hljs-keyword, .hljs-selector-tag, .hljs-section, -.hljs-attribute, .hljs-variable { color: var(--red) } - -.hljs-subst, .hljs-symbol, .hljs-selector-id, .hljs-selector-attr, -.hljs-selector-pseudo, .hljs-template-tag, .hljs-template-variable, -.hljs-addition { color: var(--red) } - -.comment { - background-color: var(--overlay-bg); - clear: both; - margin: 1ex 0; - overflow: hidden; - padding: 0 1rem; -} - -.openring { - display: flex; - flex-wrap: wrap; - margin: -0.5rem; - margin-bottom: 0; -} -.openring h3 { margin: 0 0 1ex } -.openring article { - background: var(--overlay-bg); - display: flex; - flex-direction: column; - flex: 1 1 0; - margin: 1ex; - min-width: 12rem; - padding: 1ex; -} diff --git a/templates/archive.html b/templates/archive.html new file mode 100644 index 0000000..d3d8e0b --- /dev/null +++ b/templates/archive.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html lang=en> +<meta charset=utf-8> +<link rel=stylesheet href=/static/archive.css> +<link rel=icon href=/static/favicon.svg> +<title>{{.Title}}</title> +<style> +body { margin: 0 } +img { + display: block; + margin: auto; + max-width: 100%; +} +</style> + +<body>{{range .Entries}} +<img src="?entry={{.Index}}" alt="{{.Name}}">{{end}} +</body> +</html> diff --git a/templates/index.html b/templates/index.html index 39785aa..67f96fb 100644 --- a/templates/index.html +++ b/templates/index.html @@ -3,6 +3,7 @@ <meta charset=utf-8> <meta name=viewport content='width=device-width, initial-scale=1'> <link rel=stylesheet href=/static/style.css> +<link rel=icon href=/static/favicon.svg> <title>{{.}}</title> <body> </body> |