Add project files
This commit is contained in:
parent
a19b51acce
commit
c77b8fc4e1
5 changed files with 198 additions and 0 deletions
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module github.com/ChaoticByte/plaintext-encyclopedia
|
||||||
|
|
||||||
|
go 1.22.4
|
77
main.go
Normal file
77
main.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger *log.Logger
|
||||||
|
var appTemplate *template.Template = template.New("app")
|
||||||
|
|
||||||
|
type TemplateData struct {
|
||||||
|
TOC []string
|
||||||
|
Entry string
|
||||||
|
Title string
|
||||||
|
EntryTitle string
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTemplate() {
|
||||||
|
data, err := os.ReadFile(TemplateFile)
|
||||||
|
if err != nil { logger.Panic(err) }
|
||||||
|
appTemplate.Parse(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTOC() []string {
|
||||||
|
entriesDirFs := os.DirFS(EntriesDirectory)
|
||||||
|
m, err := fs.Glob(entriesDirFs, "*")
|
||||||
|
if err != nil { logger.Panic(err) }
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEntry(name string) (string, error) {
|
||||||
|
data, err := os.ReadFile(strings.TrimRight(EntriesDirectory, "/") + "/" + name)
|
||||||
|
if err != nil { return "", err }
|
||||||
|
return string(data), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleApplication(w http.ResponseWriter, req *http.Request) {
|
||||||
|
var entry string
|
||||||
|
var err error
|
||||||
|
entryName := strings.Trim(req.URL.Path, "/")
|
||||||
|
if entryName != "" {
|
||||||
|
if strings.Contains(entryName, "/") || strings.Contains(entryName, ".") {
|
||||||
|
// path traversal
|
||||||
|
logger.Println("Possible path traversal attempt from", req.RemoteAddr, "to", entryName)
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// load entry
|
||||||
|
entry, err = getEntry(entryName)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("Couldn't open entry", entryName)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = appTemplate.ExecuteTemplate(w, "app", TemplateData{TOC: getTOC(), Entry: entry, Title: MainTitle, EntryTitle: entryName})
|
||||||
|
if err != nil { logger.Println(err) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// get logger
|
||||||
|
logger = log.Default()
|
||||||
|
// load template
|
||||||
|
loadTemplate()
|
||||||
|
// handle static files
|
||||||
|
staticHandler := http.StripPrefix("/static/", http.FileServer(http.Dir(StaticDirectory)))
|
||||||
|
http.Handle("/static/", staticHandler)
|
||||||
|
// handle application
|
||||||
|
http.HandleFunc("/", handleApplication)
|
||||||
|
// start server
|
||||||
|
logger.Println("Starting server on", ServerListen)
|
||||||
|
err := http.ListenAndServe(ServerListen, nil)
|
||||||
|
if err != nil { logger.Panic(err) }
|
||||||
|
}
|
27
public/index.html
Normal file
27
public/index.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
<title>{{ if .EntryTitle }}{{ .EntryTitle }} - {{ end }}{{ .Title }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<label class="sidebar-switch">
|
||||||
|
<div class="sidebar-symbol">≡</div>
|
||||||
|
<input type="checkbox" {{- if not .Entry }}checked{{ end }}>
|
||||||
|
<div class="sidebar">
|
||||||
|
{{- range .TOC -}}
|
||||||
|
<div><a href="{{ . }}">{{ . }}</a></div>
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<div class="main">
|
||||||
|
<h1>{{ .EntryTitle }}</h1>
|
||||||
|
<div class="content">
|
||||||
|
{{ .Entry }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
84
public/static/style.css
Normal file
84
public/static/style.css
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: top;
|
||||||
|
width: 100vw;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
.sidebar-symbol {
|
||||||
|
margin-left: .5rem;
|
||||||
|
margin-top: .5rem;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-family: monospace;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
.sidebar-switch { pointer-events: none; }
|
||||||
|
.sidebar-switch > input { display: none; }
|
||||||
|
.sidebar-switch > .sidebar { display: none; }
|
||||||
|
.sidebar-switch > input:checked + .sidebar { display: flex; }
|
||||||
|
.sidebar {
|
||||||
|
flex-direction: column;
|
||||||
|
pointer-events: all;
|
||||||
|
width: max-content;
|
||||||
|
padding: 1rem;
|
||||||
|
gap: .25rem;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
.main {
|
||||||
|
padding-right: 2rem;
|
||||||
|
margin: 2rem 0;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.main > h1, .content {
|
||||||
|
width: 70vw;
|
||||||
|
max-width: 50rem;
|
||||||
|
}
|
||||||
|
.main > h1 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.content { white-space: pre-line; }
|
||||||
|
|
||||||
|
@media only screen and (max-width: 750px) {
|
||||||
|
body {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.sidebar-symbol {
|
||||||
|
margin-left: unset;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.sidebar-switch {
|
||||||
|
width: 90vw;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
width: 100%;
|
||||||
|
height: max-content;
|
||||||
|
padding: 1rem 0;
|
||||||
|
gap: .4rem;
|
||||||
|
}
|
||||||
|
.main {
|
||||||
|
margin-top: .5rem;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.main > h1, .content {
|
||||||
|
width: 90vw;
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
}
|
7
settings.go
Normal file
7
settings.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
var ServerListen = ":7000"
|
||||||
|
var EntriesDirectory = "./entries"
|
||||||
|
var TemplateFile = "./public/index.html"
|
||||||
|
var StaticDirectory = "./public/static"
|
||||||
|
var MainTitle = "Encyclopedia"
|
Reference in a new issue