forked from mirror/oddmu
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0696603f69 | |||
| 987d859431 | |||
| 36d40dff50 | |||
| 10d055c117 | |||
| 01dad0b932 | |||
| f5be60d026 | |||
|
|
f0d814b8f3 | ||
|
|
41f6d9e48e | ||
|
|
7beaf3e375 | ||
|
|
8f277714d7 | ||
|
|
11eace1b3c | ||
|
|
ad778a3068 | ||
|
|
6ec69d27eb | ||
|
|
0a093182e9 | ||
|
|
9952f2363b | ||
|
|
4d14517668 | ||
|
|
0051a5ca66 | ||
|
|
2436cc1114 | ||
|
|
96993b794a | ||
|
|
991260b78c | ||
|
|
751b9fe63d | ||
|
|
34afc151a4 | ||
|
|
efbea969fa | ||
|
|
e12ab8594c | ||
|
|
233b9817b5 | ||
|
|
ecfee31cbd | ||
|
|
e86de1beb8 | ||
| 19a1ea6efe |
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
This software is Copyright (c) 2015–2024 by Alex Schroeder.
|
||||
This software is Copyright (c) 2015–2026 by Alex Schroeder.
|
||||
|
||||
This is free software, licensed under:
|
||||
|
||||
|
||||
20
Makefile
20
Makefile
@@ -1,7 +1,8 @@
|
||||
SHELL=/bin/bash
|
||||
SHELL=/usr/bin/env bash
|
||||
PREFIX=${HOME}/.local
|
||||
BINARIES=oddmu-linux-amd64.tar.gz oddmu-linux-arm64.tar.gz oddmu-darwin-amd64.tar.gz oddmu-darwin-arm64.tar.gz oddmu-windows-amd64.tar.gz
|
||||
|
||||
.PHONY: help build test run upload docs install priv
|
||||
.PHONY: help build test run upload docs install priv clean dist dist-upload
|
||||
|
||||
help:
|
||||
@echo Help for Oddmu
|
||||
@@ -38,10 +39,13 @@ test:
|
||||
rm -rf testdata/*
|
||||
go test -shuffle on .
|
||||
|
||||
# go install golang.org/x/lint/golint@latest
|
||||
# go install github.com/go-critic/go-critic/cmd/go-critic@latest
|
||||
check:
|
||||
golint
|
||||
gocritic check
|
||||
go-critic check
|
||||
|
||||
# go install golang.org/x/tools/cmd/goimports@latest
|
||||
fix:
|
||||
goimports -w *.go
|
||||
|
||||
@@ -62,10 +66,13 @@ install:
|
||||
install -D -t ${PREFIX}/bin oddmu
|
||||
|
||||
clean:
|
||||
rm --force oddmu oddmu.exe oddmu-{linux,darwin,windows}-{amd64,arm64}{,.tar.gz}
|
||||
rm -f oddmu oddmu.exe oddmu-{linux,darwin,windows}-{amd64,arm64}{,.tar.gz}
|
||||
cd man && make clean
|
||||
|
||||
dist: oddmu-linux-amd64.tar.gz oddmu-linux-arm64.tar.gz oddmu-darwin-amd64.tar.gz oddmu-windows-amd64.tar.gz
|
||||
dist-upload: $(BINARIES)
|
||||
rsync -ai $^ sibirocobombus:alexschroeder.ch/wiki/oddmu/
|
||||
|
||||
dist: $(BINARIES)
|
||||
|
||||
oddmu-linux-amd64: *.go
|
||||
GOOS=linux GOARCH=amd64 go build -o $@
|
||||
@@ -74,6 +81,9 @@ oddmu-linux-arm64: *.go
|
||||
env GOOS=linux GOARCH=arm64 GOARM=5 go build -o $@
|
||||
|
||||
oddmu-darwin-amd64: *.go
|
||||
GOOS=darwin GOARCH=amd64 go build -o $@
|
||||
|
||||
oddmu-darwin-arm64: *.go
|
||||
GOOS=darwin GOARCH=arm64 go build -o $@
|
||||
|
||||
oddmu.exe: *.go
|
||||
|
||||
17
README.md
17
README.md
@@ -65,6 +65,11 @@ changes.
|
||||
This man page documents the "version" subcommand which you can use to
|
||||
get the installed Oddmu version.
|
||||
|
||||
[oddmu-man(1)](https://alexschroeder.ch/view/oddmu/oddmu-man.1): Oddmu
|
||||
comes with a "man" subcommand to print the manual pages. This man page
|
||||
documents the subcommand, but I guess if you can read the man page,
|
||||
you don't need the "man" subcommand.
|
||||
|
||||
Working locally:
|
||||
|
||||
[oddmu-links(1)](https://alexschroeder.ch/view/oddmu/oddmu-links.1):
|
||||
@@ -110,6 +115,10 @@ Markdown pages from the command line.
|
||||
This man page documents the "feed" subcommand to generate a feed from
|
||||
Markdown pages from the command line.
|
||||
|
||||
[oddmu-sitemap(1)](https://alexschroeder.ch/view/oddmu/oddmu-sitemap.1):
|
||||
This man page documents the "sitemap" subcommand to generate the
|
||||
static sitemap from the command line.
|
||||
|
||||
[oddmu-static(1)](https://alexschroeder.ch/view/oddmu/oddmu-static.1):
|
||||
This man page documents the "static" subcommand to generate an entire
|
||||
static website from the command line, avoiding the need to run Oddmu
|
||||
@@ -216,6 +225,13 @@ into `$HOME/.local/share/man/`.
|
||||
make install
|
||||
```
|
||||
|
||||
This installs `oddmu` into `/usr/local/bin` and the manual pages into
|
||||
`/usr/local/share/man`:
|
||||
|
||||
```sh
|
||||
sudo make install PREFIX=/usr/local
|
||||
```
|
||||
|
||||
Here's an example using [GNU Stow](https://www.gnu.org/software/stow/)
|
||||
to install it into `/usr/local/stow` in a way that allows you to
|
||||
uninstall it later:
|
||||
@@ -255,6 +271,7 @@ high-level introduction to the various source files.
|
||||
- `preview.go` implements the `/preview` handler
|
||||
- `score.go` implements the page scoring when showing search results
|
||||
- `search.go` implements the `/search` handler
|
||||
- `sitemap.go` implements the `/sitemap` handler
|
||||
- `snippets.go` implements the page summaries for search results
|
||||
- `templates.go` implements template loading and reloading
|
||||
- `tokenizer.go` implements the various tokenizers used
|
||||
|
||||
16
RELEASE
16
RELEASE
@@ -1,22 +1,22 @@
|
||||
When preparing a new release
|
||||
----------------------------
|
||||
|
||||
1. Run tests
|
||||
1. run tests
|
||||
|
||||
2. Update man/oddmu-releases.7.txt
|
||||
2. update man/oddmu-releases.7.txt
|
||||
- add missing items
|
||||
- change "(unreleased)"
|
||||
|
||||
3. make docs
|
||||
3. check copyright year in LICENSE
|
||||
|
||||
4. Make sure all files are checked in
|
||||
4. make docs
|
||||
|
||||
5. Tag the release and push the tag to all remotes
|
||||
5. make sure all files are checked in
|
||||
|
||||
6. cd man && make upload
|
||||
6. tag the release and push the tag to the remote
|
||||
|
||||
7. make dist
|
||||
|
||||
8. create a new release at https://github.com/kensanata/oddmu/releases
|
||||
8. make dist-upload
|
||||
|
||||
9. upload the four .tar.gz binaries to the GitHub release
|
||||
9. cd man && make upload
|
||||
|
||||
11
feed.go
11
feed.go
@@ -16,7 +16,9 @@ import (
|
||||
type dateSource int
|
||||
|
||||
const (
|
||||
// ModTime means that the feed item date is based on the page file's last modification date.
|
||||
ModTime dateSource = iota
|
||||
// URL means that the feed item date is based on the ISO date contained in the page name.
|
||||
URL
|
||||
)
|
||||
|
||||
@@ -67,11 +69,12 @@ func feed(p *Page, ti time.Time, from, n int, source dateSource) *Feed {
|
||||
feed.Date = ti.Format(time.RFC1123Z)
|
||||
feed.From = from
|
||||
feed.N = n
|
||||
if n == 0 {
|
||||
switch {
|
||||
case n == 0:
|
||||
feed.Complete = true
|
||||
} else if from > n {
|
||||
case from > n:
|
||||
feed.Prev = from - n
|
||||
} else {
|
||||
default:
|
||||
year, err := p.BlogYear()
|
||||
if err == nil && p.ArchiveExists(year+1) {
|
||||
feed.PrevYear = year + 1
|
||||
@@ -150,7 +153,7 @@ func (p *Page) Date(source dateSource) (time.Time, error) {
|
||||
return p.ModTime()
|
||||
}
|
||||
|
||||
// BLogYear returns the current year if the page name is "index". If the page name is a number such as "2026" then
|
||||
// BlogYear returns the current year if the page name is "index". If the page name is a number such as "2026" then
|
||||
// this is parsed as an integer and returned.
|
||||
func (p *Page) BlogYear() (int, error) {
|
||||
name := path.Base(p.Name)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<atom:link href="https://example.org/view/{{.Dir}}{{.PrevYear}}.rss?n={{.N}}" rel="previous" type="application/rss+xml"/>{{end}}{{if .Next}}
|
||||
<atom:link href="https://example.org/view/{{.Path}}.rss?from={{.Next}}&n={{.N}}" rel="next" type="application/rss+xml"/>{{end}}{{if .NextYear}}
|
||||
<atom:link href="https://example.org/view/{{.Dir}}{{.NextYear}}.rss?n={{.N}}" rel="next" type="application/rss+xml"/>{{end}}{{if .Complete}}
|
||||
<generator>Oddμ https://src.alexschroeder.ch/oddmu.git/</generator>
|
||||
<fh:complete/>{{end}}
|
||||
<description>This is the digital garden of Your Name.</description>
|
||||
<image>
|
||||
|
||||
@@ -78,7 +78,7 @@ func (p *Page) printFeed(w io.Writer, ti time.Time) subcommands.ExitStatus {
|
||||
fmt.Fprintf(os.Stderr, "Cannot write prefix: %s\n", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
loadTemplates()
|
||||
initTemplates()
|
||||
templates.RLock()
|
||||
defer templates.RUnlock()
|
||||
err = templates.template["feed.html"].Execute(w, f)
|
||||
|
||||
41
go.mod
41
go.mod
@@ -1,41 +1,40 @@
|
||||
module src.alexschroeder.ch/oddmu
|
||||
|
||||
go 1.22
|
||||
|
||||
toolchain go1.22.3
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.3
|
||||
github.com/gen2brain/heic v0.3.1
|
||||
github.com/gen2brain/webp v0.5.2
|
||||
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e
|
||||
github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.13
|
||||
github.com/gen2brain/heic v0.4.9
|
||||
github.com/gen2brain/webp v0.5.5
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a
|
||||
github.com/google/subcommands v1.2.0
|
||||
github.com/hexops/gotextdiff v1.0.3
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/pemistahl/lingua-go v1.4.0
|
||||
github.com/sergi/go-diff v1.3.1
|
||||
github.com/sergi/go-diff v1.4.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.1 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.6 // indirect
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/tetratelabs/wazero v1.8.1 // indirect
|
||||
golang.org/x/image v0.15.0 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||
golang.org/x/image v0.35.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
78
go.sum
78
go.sum
@@ -1,26 +1,30 @@
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f h1:RMnUwTnNR070mFAEIoqMYjNirHj8i0h79VXTYyBCyVA=
|
||||
github.com/edwvee/exiffix v0.0.0-20210922235313-0f6cbda5e58f/go.mod h1:KoE3Ti1qbQXCb3s/XGj0yApHnbnNnn1bXTtB5Auq/Vc=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gen2brain/heic v0.3.1 h1:ClY5YTdXdIanw7pe9ZVUM9XcsqH6CCCa5CZBlm58qOs=
|
||||
github.com/gen2brain/heic v0.3.1/go.mod h1:m2sVIf02O7wfO8mJm+PvE91lnq4QYJy2hseUon7So10=
|
||||
github.com/gen2brain/webp v0.5.2 h1:aYdjbU/2L98m+bqUdkYMOIY93YC+EN3HuZLMaqgMD9U=
|
||||
github.com/gen2brain/webp v0.5.2/go.mod h1:Nb3xO5sy6MeUAHhru9H3GT7nlOQO5dKRNNlE92CZrJw=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e h1:ESHlT0RVZphh4JGBz49I5R6nTdC8Qyc08vU25GQHzzQ=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be h1:FNPYI8/ifKGW7kdBdlogyGGaPXZmOXBbV1uz4Amr3s0=
|
||||
github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be/go.mod h1:G3dK5MziX9e4jUa8PWjowCOPCcyQwxsZ5a0oYA73280=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gen2brain/heic v0.4.9 h1:sHM7kjMV2+AlrSfYNsJiD0NrqAJqIMWAOqMSZ0HXrU8=
|
||||
github.com/gen2brain/heic v0.4.9/go.mod h1:0/0SrVQnUhOA3ekFY5/lApZYniF/DsgS3g9COWe83dM=
|
||||
github.com/gen2brain/webp v0.5.5 h1:MvQR75yIPU/9nSqYT5h13k4URaJK3gf9tgz/ksRbyEg=
|
||||
github.com/gen2brain/webp v0.5.5/go.mod h1:xOSMzp4aROt2KFW++9qcK/RBTOVC2S9tJG66ip/9Oc0=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a h1:l7A0loSszR5zHd/qK53ZIHMO8b3bBSmENnQ6eKnUT0A=
|
||||
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
@@ -33,10 +37,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/pemistahl/lingua-go v1.4.0 h1:ifYhthrlW7iO4icdubwlduYnmwU37V1sbNrwhKBR4rM=
|
||||
@@ -45,32 +49,30 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
|
||||
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA550=
|
||||
github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
|
||||
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -48,8 +48,8 @@ upload: ${MD} README.md
|
||||
|
||||
clean:
|
||||
@echo Removing HTML and Markdown files
|
||||
@rm --force ${HTML} ${MD} README.md
|
||||
@rm -f ${HTML} ${MD} README.md
|
||||
|
||||
realclean: clean
|
||||
@echo Removing man pages
|
||||
@rm --force ${MAN}
|
||||
@rm -f ${MAN}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "ODDMU-APACHE" "5" "2025-07-16"
|
||||
.TH "ODDMU-APACHE" "5" "2026-01-29"
|
||||
.PP
|
||||
.SH NAME
|
||||
.PP
|
||||
@@ -48,7 +48,7 @@ ServerAdmin alex@alexschroeder\&.ch
|
||||
<VirtualHost *:443>
|
||||
ServerName transjovian\&.org
|
||||
SSLEngine on
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*))?$"
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*)|sitemap.xml)?$"
|
||||
"http://localhost:8080/$1"
|
||||
</VirtualHost>
|
||||
.fi
|
||||
@@ -126,13 +126,13 @@ ServerAdmin alex@alexschroeder\&.ch
|
||||
ServerName transjovian\&.org
|
||||
ProxyPassMatch "^/((view|diff|search|archive)/(\&.*))?$"
|
||||
"http://localhost:8080/$1"
|
||||
RedirectMatch "^/((edit|save|add|append|upload|drop)/(\&.*))?$"
|
||||
RedirectMatch "^/((edit|save|add|append|upload|drop)/(\&.*)|sitemap.xml)?$"
|
||||
"https://transjovian\&.org/$1"
|
||||
</VirtualHost>
|
||||
<VirtualHost *:443>
|
||||
ServerName transjovian\&.org
|
||||
SSLEngine on
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*))?$"
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*)|sitemap.xml)?$"
|
||||
"http://localhost:8080/$1"
|
||||
</VirtualHost>
|
||||
.fi
|
||||
@@ -170,7 +170,7 @@ In that case, you need to use the ProxyPassMatch directive.\&
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*))?$"
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*)|sitemap.xml)?$"
|
||||
"unix:/run/oddmu/oddmu\&.sock|http://localhost/$1"
|
||||
.fi
|
||||
.RE
|
||||
@@ -189,7 +189,7 @@ A workaround is to add the redirect manually and drop the question-mark:
|
||||
.nf
|
||||
.RS 4
|
||||
RedirectMatch "^/$" "/view/index"
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*))$"
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(\&.*)|sitemap.xml)$"
|
||||
"unix:/run/oddmu/oddmu\&.sock|http://localhost/$1"
|
||||
.fi
|
||||
.RE
|
||||
@@ -248,12 +248,74 @@ to your "<VirtualHost *:443>" section:
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
.SS Actual usernames and passwords for authentication
|
||||
.PP
|
||||
On a community server where the users have accounts, wiki editing can be limited
|
||||
to the system'\&s users.\&
|
||||
.PP
|
||||
In order to do this, install the \fBmod-authnz-external\fR module for Apache and the
|
||||
\fBpwauth\fR binary.\& The module allows the password checking normally done inside
|
||||
Apache to be done by an separate external program running outside of Apache.\&
|
||||
.PP
|
||||
Here'\&s an example configuration:
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
AddExternalAuth pwauth /usr/sbin/pwauth
|
||||
SetExternalAuthMethod pwauth pipe
|
||||
|
||||
<LocationMatch "^/(edit|save|add|append|upload|drop)/">
|
||||
AuthType Basic
|
||||
AuthName "Password Required"
|
||||
AuthBasicProvider external
|
||||
AuthExternal pwauth
|
||||
Require valid-user
|
||||
</LocationMatch>
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
.SS Different logins for different access rights
|
||||
.PP
|
||||
What if you have a site with various subdirectories and each subdirectory is for
|
||||
a different group of friends?\& You can set this up using your webserver.\& One way
|
||||
to do this is to require specific usernames (which must have a password in the
|
||||
password file mentioned above.\&
|
||||
.PP
|
||||
This requires a valid login by the user "alex" or "berta":
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
<LocationMatch "^/(edit|save|add|append|upload|drop)/intetebi/">
|
||||
Require user alex berta
|
||||
</LocationMatch>
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
.SS Private wikis
|
||||
.PP
|
||||
Based on the above, you can prevent people from \fIreading\fR the wiki.\& The location
|
||||
must cover all the URLs in order to protect everything.\&
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
<Location />
|
||||
AuthType Basic
|
||||
AuthName "Password Required"
|
||||
AuthUserFile /home/oddmu/\&.htpasswd
|
||||
Require valid-user
|
||||
</Location>
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
.SS Subdirectories as separate sites
|
||||
.PP
|
||||
The way Oddmu handles subdirectories is that all files and directories are
|
||||
visible, except for "hidden" files and directories (whose name starts with a
|
||||
period).\& Specifically, do not rely on Apache to hide locations in subdirectories
|
||||
from public view.\& Search reveals the existence of these pages and produces an
|
||||
extract, even if users cannot follow the links.\& Archive links pack all the
|
||||
subdirectories, including locations you may have hidden from view using Apache.\&
|
||||
extract, even if users cannot follow the links.\& The Sitemap lists all pages,
|
||||
including subdirectories.\& Archive links pack all the subdirectories, including
|
||||
locations you may have hidden from view using Apache.\&
|
||||
.PP
|
||||
If you to treat subdirectories as separate sites, you need to set the
|
||||
environment variable ODDMU_FILTER to a regular expression matching the those
|
||||
@@ -338,46 +400,13 @@ In this case, "/css/oddmu-2023.\&css" would be the name of your stylesheet.\& If
|
||||
your document root is "/home/oddmu", then the filename of your stylesheet would
|
||||
have to be "/home/oddmu/css/oddmu-2023.\&css" for this to work.\&
|
||||
.PP
|
||||
.SS Different logins for different access rights
|
||||
.PP
|
||||
What if you have a site with various subdirectories and each subdirectory is for
|
||||
a different group of friends?\& You can set this up using your webserver.\& One way
|
||||
to do this is to require specific usernames (which must have a password in the
|
||||
password file mentioned above.\&
|
||||
.PP
|
||||
This requires a valid login by the user "alex" or "berta":
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
<LocationMatch "^/(edit|save|add|append|upload|drop)/intetebi/">
|
||||
Require user alex berta
|
||||
</LocationMatch>
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
.SS Private wikis
|
||||
.PP
|
||||
Based on the above, you can prevent people from \fIreading\fR the wiki.\& The location
|
||||
must cover all the URLs in order to protect everything.\&
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
<Location />
|
||||
AuthType Basic
|
||||
AuthName "Password Required"
|
||||
AuthUserFile /home/oddmu/\&.htpasswd
|
||||
Require valid-user
|
||||
</Location>
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
.SS Virtual hosting
|
||||
.PP
|
||||
Virtual hosting in this context means that the program serves two different
|
||||
sites for two different domains from the same machine.\& Oddmu doesn'\&t support
|
||||
that, but your webserver does.\& Therefore, start an Oddmu instance for every
|
||||
domain name, each listening on a different port.\& Then set up your web server
|
||||
such that ever domain acts as a reverse proxy to a different Oddmu instance.\&
|
||||
such that every domain proxies for a different Oddmu instance.\&
|
||||
.PP
|
||||
.SH SEE ALSO
|
||||
.PP
|
||||
|
||||
@@ -40,7 +40,7 @@ ServerAdmin alex@alexschroeder.ch
|
||||
<VirtualHost *:443>
|
||||
ServerName transjovian.org
|
||||
SSLEngine on
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(.*))?$" \
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(.*)|sitemap\.xml)?$" \
|
||||
"http://localhost:8080/$1"
|
||||
</VirtualHost>
|
||||
```
|
||||
@@ -106,13 +106,13 @@ ServerAdmin alex@alexschroeder.ch
|
||||
ServerName transjovian.org
|
||||
ProxyPassMatch "^/((view|diff|search|archive)/(.*))?$" \
|
||||
"http://localhost:8080/$1"
|
||||
RedirectMatch "^/((edit|save|add|append|upload|drop)/(.*))?$" \
|
||||
RedirectMatch "^/((edit|save|add|append|upload|drop)/(.*)|sitemap\.xml)?$" \
|
||||
"https://transjovian.org/$1"
|
||||
</VirtualHost>
|
||||
<VirtualHost *:443>
|
||||
ServerName transjovian.org
|
||||
SSLEngine on
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(.*))?$" \
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(.*)|sitemap\.xml)?$" \
|
||||
"http://localhost:8080/$1"
|
||||
</VirtualHost>
|
||||
```
|
||||
@@ -144,7 +144,7 @@ You probably want to serve some static files as well (see *Serve static files*).
|
||||
In that case, you need to use the ProxyPassMatch directive.
|
||||
|
||||
```
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(.*))?$" \
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(.*)|sitemap\.xml)?$" \
|
||||
"unix:/run/oddmu/oddmu.sock|http://localhost/$1"
|
||||
```
|
||||
|
||||
@@ -159,7 +159,7 @@ A workaround is to add the redirect manually and drop the question-mark:
|
||||
|
||||
```
|
||||
RedirectMatch "^/$" "/view/index"
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(.*))$" \
|
||||
ProxyPassMatch "^/((view|preview|diff|edit|save|add|append|upload|drop|search|archive)/(.*)|sitemap\.xml)$" \
|
||||
"unix:/run/oddmu/oddmu.sock|http://localhost/$1"
|
||||
```
|
||||
|
||||
@@ -209,12 +209,68 @@ to your "<VirtualHost \*:443>" section:
|
||||
</LocationMatch>
|
||||
```
|
||||
|
||||
## Actual usernames and passwords for authentication
|
||||
|
||||
On a community server where the users have accounts, wiki editing can be limited
|
||||
to the system's users.
|
||||
|
||||
In order to do this, install the *mod-authnz-external* module for Apache and the
|
||||
*pwauth* binary. The module allows the password checking normally done inside
|
||||
Apache to be done by an separate external program running outside of Apache.
|
||||
|
||||
Here's an example configuration:
|
||||
|
||||
```
|
||||
AddExternalAuth pwauth /usr/sbin/pwauth
|
||||
SetExternalAuthMethod pwauth pipe
|
||||
|
||||
<LocationMatch "^/(edit|save|add|append|upload|drop)/">
|
||||
AuthType Basic
|
||||
AuthName "Password Required"
|
||||
AuthBasicProvider external
|
||||
AuthExternal pwauth
|
||||
Require valid-user
|
||||
</LocationMatch>
|
||||
```
|
||||
|
||||
## Different logins for different access rights
|
||||
|
||||
What if you have a site with various subdirectories and each subdirectory is for
|
||||
a different group of friends? You can set this up using your webserver. One way
|
||||
to do this is to require specific usernames (which must have a password in the
|
||||
password file mentioned above.
|
||||
|
||||
This requires a valid login by the user "alex" or "berta":
|
||||
|
||||
```
|
||||
<LocationMatch "^/(edit|save|add|append|upload|drop)/intetebi/">
|
||||
Require user alex berta
|
||||
</LocationMatch>
|
||||
```
|
||||
|
||||
## Private wikis
|
||||
|
||||
Based on the above, you can prevent people from _reading_ the wiki. The location
|
||||
must cover all the URLs in order to protect everything.
|
||||
|
||||
```
|
||||
<Location />
|
||||
AuthType Basic
|
||||
AuthName "Password Required"
|
||||
AuthUserFile /home/oddmu/.htpasswd
|
||||
Require valid-user
|
||||
</Location>
|
||||
```
|
||||
|
||||
## Subdirectories as separate sites
|
||||
|
||||
The way Oddmu handles subdirectories is that all files and directories are
|
||||
visible, except for "hidden" files and directories (whose name starts with a
|
||||
period). Specifically, do not rely on Apache to hide locations in subdirectories
|
||||
from public view. Search reveals the existence of these pages and produces an
|
||||
extract, even if users cannot follow the links. Archive links pack all the
|
||||
subdirectories, including locations you may have hidden from view using Apache.
|
||||
extract, even if users cannot follow the links. The Sitemap lists all pages,
|
||||
including subdirectories. Archive links pack all the subdirectories, including
|
||||
locations you may have hidden from view using Apache.
|
||||
|
||||
If you to treat subdirectories as separate sites, you need to set the
|
||||
environment variable ODDMU_FILTER to a regular expression matching the those
|
||||
@@ -291,42 +347,13 @@ In this case, "/css/oddmu-2023.css" would be the name of your stylesheet. If
|
||||
your document root is "/home/oddmu", then the filename of your stylesheet would
|
||||
have to be "/home/oddmu/css/oddmu-2023.css" for this to work.
|
||||
|
||||
## Different logins for different access rights
|
||||
|
||||
What if you have a site with various subdirectories and each subdirectory is for
|
||||
a different group of friends? You can set this up using your webserver. One way
|
||||
to do this is to require specific usernames (which must have a password in the
|
||||
password file mentioned above.
|
||||
|
||||
This requires a valid login by the user "alex" or "berta":
|
||||
|
||||
```
|
||||
<LocationMatch "^/(edit|save|add|append|upload|drop)/intetebi/">
|
||||
Require user alex berta
|
||||
</LocationMatch>
|
||||
```
|
||||
|
||||
## Private wikis
|
||||
|
||||
Based on the above, you can prevent people from _reading_ the wiki. The location
|
||||
must cover all the URLs in order to protect everything.
|
||||
|
||||
```
|
||||
<Location />
|
||||
AuthType Basic
|
||||
AuthName "Password Required"
|
||||
AuthUserFile /home/oddmu/.htpasswd
|
||||
Require valid-user
|
||||
</Location>
|
||||
```
|
||||
|
||||
## Virtual hosting
|
||||
|
||||
Virtual hosting in this context means that the program serves two different
|
||||
sites for two different domains from the same machine. Oddmu doesn't support
|
||||
that, but your webserver does. Therefore, start an Oddmu instance for every
|
||||
domain name, each listening on a different port. Then set up your web server
|
||||
such that ever domain acts as a reverse proxy to a different Oddmu instance.
|
||||
such that every domain proxies for a different Oddmu instance.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "ODDMU-EXPORT" "1" "2025-08-31"
|
||||
.TH "ODDMU-EXPORT" "1" "2026-01-03"
|
||||
.PP
|
||||
.SH NAME
|
||||
.PP
|
||||
@@ -31,7 +31,8 @@ XML preamble is printed and appropriate escaping rules are used.\&
|
||||
.PP
|
||||
.SH FILES
|
||||
.PP
|
||||
By default, the export uses the \fB\fRfeed.\&html\fB\fR template in the current directory.\&
|
||||
By default, the export uses the feed template ("feed.\&html") in the current
|
||||
directory.\&
|
||||
.PP
|
||||
.SH EXAMPLES
|
||||
.PP
|
||||
|
||||
@@ -24,7 +24,8 @@ XML preamble is printed and appropriate escaping rules are used.
|
||||
|
||||
# FILES
|
||||
|
||||
By default, the export uses the **feed.html** template in the current directory.
|
||||
By default, the export uses the feed template ("feed.html") in the current
|
||||
directory.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "ODDMU-FILTER" "7" "2024-09-30"
|
||||
.TH "ODDMU-FILTER" "7" "2026-01-03"
|
||||
.PP
|
||||
.SH NAME
|
||||
.PP
|
||||
@@ -13,13 +13,13 @@ oddmu-filter - keeping subdirectories separate
|
||||
.PP
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
There are actions such as searching and archiving that act on multiple pages,
|
||||
not just a single page.\& These actions walk the directory tree, including all
|
||||
subdirectories.\& In some cases, this is not desirable.\&
|
||||
There are actions such as producing the sitemap, searching and archiving that
|
||||
act on multiple pages, not just a single page.\& These actions walk the directory
|
||||
tree, including all subdirectories.\& In some cases, this is not desirable.\&
|
||||
.PP
|
||||
Sometimes, subdirectories are separate sites, like the sites of other projects
|
||||
or different people.\& Depending on how you think about it, you might not want to
|
||||
include those "sites" in searches or archives of the whole site.\&
|
||||
include those "sites" in searches, sitemaps or archives of the whole site.\&
|
||||
.PP
|
||||
Since directory tree actions always start in the directory the visitor is
|
||||
currently looking at, directory tree actions starting in a "separate site"
|
||||
|
||||
@@ -6,13 +6,13 @@ oddmu-filter - keeping subdirectories separate
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
There are actions such as searching and archiving that act on multiple pages,
|
||||
not just a single page. These actions walk the directory tree, including all
|
||||
subdirectories. In some cases, this is not desirable.
|
||||
There are actions such as producing the sitemap, searching and archiving that
|
||||
act on multiple pages, not just a single page. These actions walk the directory
|
||||
tree, including all subdirectories. In some cases, this is not desirable.
|
||||
|
||||
Sometimes, subdirectories are separate sites, like the sites of other projects
|
||||
or different people. Depending on how you think about it, you might not want to
|
||||
include those "sites" in searches or archives of the whole site.
|
||||
include those "sites" in searches, sitemaps or archives of the whole site.
|
||||
|
||||
Since directory tree actions always start in the directory the visitor is
|
||||
currently looking at, directory tree actions starting in a "separate site"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "ODDMU-HTML" "1" "2025-04-05"
|
||||
.TH "ODDMU-HTML" "1" "2026-01-03"
|
||||
.PP
|
||||
.SH NAME
|
||||
.PP
|
||||
@@ -13,7 +13,7 @@ oddmu-html - render Oddmu page HTML
|
||||
.PP
|
||||
.SH SYNOPSIS
|
||||
.PP
|
||||
\fBoddmu html\fR [\fB\fR-template\fB\fR \fItemplate-name\fR] \fIpage-name\fR
|
||||
\fBoddmu html\fR [\fB-template\fR \fItemplate-name\fR] \fIpage-name\fR
|
||||
.PP
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
@@ -23,7 +23,7 @@ name if you want to read Markdown from \fBstdin\fR.\&
|
||||
.PP
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\fB\fR-template\fB\fR \fItemplate-name\fR
|
||||
\fB-template\fR \fItemplate-name\fR
|
||||
.RS 4
|
||||
Use the given template to render the page.\& Without this, the HTML lacks
|
||||
html and body tags.\& The only two options that make sense are "view.\&html"
|
||||
|
||||
@@ -6,7 +6,7 @@ oddmu-html - render Oddmu page HTML
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
*oddmu html* [**-template** _template-name_] _page-name_
|
||||
*oddmu html* [*-template* _template-name_] _page-name_
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
@@ -16,7 +16,7 @@ name if you want to read Markdown from *stdin*.
|
||||
|
||||
# OPTIONS
|
||||
|
||||
**-template** _template-name_
|
||||
*-template* _template-name_
|
||||
Use the given template to render the page. Without this, the HTML lacks
|
||||
html and body tags. The only two options that make sense are "view.html"
|
||||
and "static.html".
|
||||
|
||||
39
man/oddmu-man.1
Normal file
39
man/oddmu-man.1
Normal file
@@ -0,0 +1,39 @@
|
||||
.\" Generated by scdoc 1.11.3
|
||||
.\" Complete documentation for this program is not available as a GNU info page
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "ODDMU-MAN" "1" "2026-02-11"
|
||||
.PP
|
||||
.SH NAME
|
||||
.PP
|
||||
oddmu-man - print the manual pages
|
||||
.PP
|
||||
.SH SYNOPSIS
|
||||
.PP
|
||||
\fBoddmu man\fR
|
||||
.PP
|
||||
\fBoddmu man\fR \fItopic\fR
|
||||
.PP
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The "man" subcommand lists the topics available or prints the manual page for
|
||||
the given topic.\&
|
||||
.PP
|
||||
Example:
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
oddmu man apache
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
.SH SEE ALSO
|
||||
.PP
|
||||
\fIoddmu\fR(1)
|
||||
.PP
|
||||
.SH AUTHORS
|
||||
.PP
|
||||
Maintained by Alex Schroeder <alex@gnu.\&org>.\&
|
||||
30
man/oddmu-man.1.txt
Normal file
30
man/oddmu-man.1.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
ODDMU-MAN(1)
|
||||
|
||||
# NAME
|
||||
|
||||
oddmu-man - print the manual pages
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
*oddmu man*
|
||||
|
||||
*oddmu man* _topic_
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The "man" subcommand lists the topics available or prints the manual page for
|
||||
the given topic.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
oddmu man apache
|
||||
```
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
_oddmu_(1)
|
||||
|
||||
# AUTHORS
|
||||
|
||||
Maintained by Alex Schroeder <alex@gnu.org>.
|
||||
@@ -5,7 +5,7 @@
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "ODDMU-NGINX" "5" "2025-07-16"
|
||||
.TH "ODDMU-NGINX" "5" "2026-01-03"
|
||||
.PP
|
||||
.SH NAME
|
||||
.PP
|
||||
@@ -27,7 +27,7 @@ section.\& Add a new \fIlocation\fR section after the existing \fIlocation\fR se
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|archive)/ {
|
||||
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|sitemap|archive)/ {
|
||||
proxy_pass http://localhost:8080;
|
||||
}
|
||||
.fi
|
||||
@@ -97,7 +97,7 @@ server configuration.\& On a Debian system, that'\&d be in
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|archive)/ {
|
||||
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|sitemap|archive)/ {
|
||||
proxy_pass http://unix:/run/oddmu/oddmu\&.sock:;
|
||||
}
|
||||
.fi
|
||||
|
||||
@@ -19,7 +19,7 @@ The site is defined in "/etc/nginx/sites-available/default", in the _server_
|
||||
section. Add a new _location_ section after the existing _location_ section:
|
||||
|
||||
```
|
||||
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|archive)/ {
|
||||
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|sitemap|archive)/ {
|
||||
proxy_pass http://localhost:8080;
|
||||
}
|
||||
```
|
||||
@@ -81,7 +81,7 @@ server configuration. On a Debian system, that'd be in
|
||||
"/etc/nginx/sites-available/default".
|
||||
|
||||
```
|
||||
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|archive)/ {
|
||||
location ~ ^/(view|preview|diff|edit|save|add|append|upload|drop|search|sitemap|archive)/ {
|
||||
proxy_pass http://unix:/run/oddmu/oddmu.sock:;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
.\" Generated by scdoc 1.11.4
|
||||
.\" Generated by scdoc 1.11.3
|
||||
.\" Complete documentation for this program is not available as a GNU info page
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "ODDMU-RELEASES" "7" "2026-01-01"
|
||||
.TH "ODDMU-RELEASES" "7" "2026-02-11"
|
||||
.PP
|
||||
.SH NAME
|
||||
.PP
|
||||
@@ -15,9 +15,18 @@ oddmu-releases - what'\&s new?\&
|
||||
.PP
|
||||
This page lists user-visible features and template changes to consider.\&
|
||||
.PP
|
||||
.SS 1.20 (unreleased)
|
||||
.SS 1.21 (unreleased)
|
||||
.PP
|
||||
Add -shrink and -glob options to the \fIstatic\fR subcommand.\&
|
||||
Write any missing templates when Oddmu starts up.\&
|
||||
.PP
|
||||
Add man subcommand to print manual pages.\&
|
||||
.PP
|
||||
Both of these features make it possible to distribute just the binary.\&
|
||||
.PP
|
||||
.SS 1.20 (2026)
|
||||
.PP
|
||||
Add -shrink and -glob options to the \fIstatic\fR subcommand.\& See \fIoddmu-static\fR(1)
|
||||
for more.\&
|
||||
.PP
|
||||
Some tools were used to check the code (goimports, golint, gocritic).\&
|
||||
Unfortunately, the resulting changes necessitates a change in the templates
|
||||
@@ -40,8 +49,8 @@ faster than any of the feeds for hashtag pages, presumably, an extra features
|
||||
was added: on the first and on the last page of the feed, a link to the next or
|
||||
the previous year is added, if such a page exists.\& This works if at beginning of
|
||||
every year, you move all the entries on to a dedicated year page.\& You need to
|
||||
add the necessary links to the feed template ("feed.\&html").\& See
|
||||
\fIoddmu-templates\fR(5) for more.\&
|
||||
add the necessary links to the "feed.\&html" template.\& See \fIoddmu-templates\fR(5)
|
||||
for more.\&
|
||||
.PP
|
||||
Example:
|
||||
.PP
|
||||
@@ -62,6 +71,11 @@ Example:
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
Add \fIsitemap\fR subcommand and handler.\& See \fIoddmu-sitemap\fR(1) for more.\& If you
|
||||
want to make it available for search engines and the like, you most likely have
|
||||
to add it to your proxy configuration.\& See \fIoddmu-apache\fR(5) or \fIoddmu-nginx\fR(5)
|
||||
for more.\&
|
||||
.PP
|
||||
.SS 1.19 (2025)
|
||||
.PP
|
||||
Add \fIfeed\fR subcommand.\& This produces a "complete" feed.\&
|
||||
@@ -71,8 +85,8 @@ Add feed pagination for the \fIfeed\fR action.\& This produces a "paginated" fee
|
||||
See RFC 5005 for more information.\&
|
||||
.PP
|
||||
If you like the idea of feed pagination (not a given since that also helps bots
|
||||
scrape your site!\&) you need to add the necessary links to the feed template
|
||||
("feed.\&html").\& See \fIoddmu-templates\fR(5) for more.\&
|
||||
scrape your site!\&) you need to add the necessary links to the "feed.\&html"
|
||||
template.\& See \fIoddmu-templates\fR(5) for more.\&
|
||||
.PP
|
||||
Example, adding the feed history namespace:
|
||||
.PP
|
||||
@@ -104,16 +118,15 @@ In an effort to remove features that can be handled by the web server, the
|
||||
for a better solution.\&
|
||||
.PP
|
||||
You probably need to remove a sentence linking to the list action from the
|
||||
upload template ("upload.\&html").\&
|
||||
"upload.\&html" template.\&
|
||||
.PP
|
||||
.SS 1.17 (2025)
|
||||
.PP
|
||||
You need to update the upload template ("upload.\&html").\& Many things have
|
||||
changed!\& See \fIoddmu-templates\fR(5) for more.\&
|
||||
You need to update the "upload.\&html" template.\& Many things have changed!\& See
|
||||
\fIoddmu-templates\fR(5) for more.\&
|
||||
.PP
|
||||
You probably want to ensure that the upload link on the view template
|
||||
("view.\&html") and others, if you added it, has a \fIfilename\fR and \fIpagename\fR
|
||||
parameters.\&
|
||||
You probably want to ensure that the upload link on the "view.\&html" template and
|
||||
others, if you added it, has a \fIfilename\fR and \fIpagename\fR parameters.\&
|
||||
.PP
|
||||
Example:
|
||||
.PP
|
||||
@@ -123,8 +136,8 @@ Example:
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
You need to change {{.\&Name}} to {{.\&Path}} when it is used in URLs, in the list
|
||||
template ("list.\&html").\& If you don'\&t do this, file deleting and rename may not
|
||||
You need to change {{.\&Name}} to {{.\&Path}} when it is used in URLs, in the
|
||||
"list.\&html" template.\& If you don'\&t do this, file deleting and rename may not
|
||||
work on files containing a comma, a semicolon, a questionmark or a hash
|
||||
character.\& This fix was necessary because URLs for files containing a
|
||||
questionmark or a hash character would end the path at this character and treat
|
||||
@@ -179,7 +192,7 @@ together with appropriate permission checks.\&
|
||||
See \fIoddmu-apache\fR(5) or \fIoddmu-nginx\fR(5) for example.\&
|
||||
.PP
|
||||
In addition to that, you might want a link to the \fIlist\fR action from one of the
|
||||
existing templates.\& For example, from upload.\&html:
|
||||
existing templates.\& For example, from the "upload.\&html" template:
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
@@ -216,10 +229,9 @@ These are the quotation marks currently supported: '\&foo'\& "foo" ‘foo’ ‚
|
||||
“foo” „foo“ ”foo” «foo» »foo« ‹foo› ›foo‹ 「foo」 「foo」 『foo』 – any such
|
||||
quoted text is searched as-is, including whitespace.\&
|
||||
.PP
|
||||
Add loading="lazy" for images in search.\&html
|
||||
.PP
|
||||
If you want to take advantage of this, you'\&ll need to adapt your "search.\&html"
|
||||
template accordingly.\& Use like this, for example:
|
||||
Add loading="lazy" for images in the search template.\& If you want to take
|
||||
advantage of this, you'\&ll need to adapt your "search.\&html" template accordingly.\&
|
||||
Use like this, for example:
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
@@ -279,10 +291,10 @@ If you want to take advantage of this, you'\&ll need to adapt your templates
|
||||
accordingly.\& The "preview.\&html" template is a mix of "view.\&html" and
|
||||
"edit.\&html".\&
|
||||
.PP
|
||||
There is an optional change to make to copies of \fIupload.\&html\fR if you upload
|
||||
multiple images at a time.\& Instead of showing just the link to the last upload,
|
||||
you can now show the link (and the images or links, if you want to) to all the
|
||||
files uploaded.\& Use like this, for example:
|
||||
There is an optional change to make to copies of the "upload.\&html" template if
|
||||
you upload multiple images at a time.\& Instead of showing just the link to the
|
||||
last upload, you can now show the link (and the images or links, if you want to)
|
||||
to all the files uploaded.\& Use like this, for example:
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
@@ -292,9 +304,9 @@ Links:<tt>{{range \&.Actual}}<br>{{end}}</tt>
|
||||
.PP
|
||||
.SS 1.9 (2024)
|
||||
.PP
|
||||
There is a change to make to copies of \fIupload.\&html\fR if subdirectories are being
|
||||
used.\& The \fILast\fR property no longer contains the directory.\& It has to be added
|
||||
to the template as follows:
|
||||
There is a change to make to copies of the "upload.\&html" template if
|
||||
subdirectories are being used.\& The \fILast\fR property no longer contains the
|
||||
directory.\& It has to be added to the template as follows:
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
@@ -322,7 +334,7 @@ The upload template can use the \fIToday\fR property.\&
|
||||
The upload template comes with JavaScript that allows users to paste images or
|
||||
drag and drop files.\&
|
||||
.PP
|
||||
The upload template changed the id for the filename field from `text` to `name`.\&
|
||||
The upload template changed the id for the filename field from "text" to "name".\&
|
||||
.PP
|
||||
The source repository now comes with example templates.\&
|
||||
.PP
|
||||
@@ -332,7 +344,7 @@ No user-visible changes.\& Documentation and code comments got better.\&
|
||||
.PP
|
||||
.SS 1.7 (2024)
|
||||
.PP
|
||||
Allow upload of multiple files.\& This requires an update to the \fIupload.\&html\fR
|
||||
Allow upload of multiple files.\& This requires an update to the "upload.\&html"
|
||||
template: Add the \fImultiple\fR attribute to the file input element and change the
|
||||
label from "file" to "files".\&
|
||||
.PP
|
||||
|
||||
@@ -8,9 +8,18 @@ oddmu-releases - what's new?
|
||||
|
||||
This page lists user-visible features and template changes to consider.
|
||||
|
||||
## 1.20 (unreleased)
|
||||
## 1.21 (unreleased)
|
||||
|
||||
Add -shrink and -glob options to the _static_ subcommand.
|
||||
Write any missing templates when Oddmu starts up.
|
||||
|
||||
Add man subcommand to print manual pages.
|
||||
|
||||
Both of these features make it possible to distribute just the binary.
|
||||
|
||||
## 1.20 (2026)
|
||||
|
||||
Add -shrink and -glob options to the _static_ subcommand. See _oddmu-static_(1)
|
||||
for more.
|
||||
|
||||
Some tools were used to check the code (goimports, golint, gocritic).
|
||||
Unfortunately, the resulting changes necessitates a change in the templates
|
||||
@@ -31,8 +40,8 @@ faster than any of the feeds for hashtag pages, presumably, an extra features
|
||||
was added: on the first and on the last page of the feed, a link to the next or
|
||||
the previous year is added, if such a page exists. This works if at beginning of
|
||||
every year, you move all the entries on to a dedicated year page. You need to
|
||||
add the necessary links to the feed template ("feed.html"). See
|
||||
_oddmu-templates_(5) for more.
|
||||
add the necessary links to the "feed.html" template. See _oddmu-templates_(5)
|
||||
for more.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -51,6 +60,11 @@ Example:
|
||||
…
|
||||
```
|
||||
|
||||
Add _sitemap_ subcommand and handler. See _oddmu-sitemap_(1) for more. If you
|
||||
want to make it available for search engines and the like, you most likely have
|
||||
to add it to your proxy configuration. See _oddmu-apache_(5) or _oddmu-nginx_(5)
|
||||
for more.
|
||||
|
||||
## 1.19 (2025)
|
||||
|
||||
Add _feed_ subcommand. This produces a "complete" feed.
|
||||
@@ -60,8 +74,8 @@ Add feed pagination for the _feed_ action. This produces a "paginated" feed.
|
||||
See RFC 5005 for more information.
|
||||
|
||||
If you like the idea of feed pagination (not a given since that also helps bots
|
||||
scrape your site!) you need to add the necessary links to the feed template
|
||||
("feed.html"). See _oddmu-templates_(5) for more.
|
||||
scrape your site!) you need to add the necessary links to the "feed.html"
|
||||
template. See _oddmu-templates_(5) for more.
|
||||
|
||||
Example, adding the feed history namespace:
|
||||
|
||||
@@ -91,16 +105,15 @@ _list_, _delete_ and _rename_ actions were removed again. See _oddmu-webdav_(5)
|
||||
for a better solution.
|
||||
|
||||
You probably need to remove a sentence linking to the list action from the
|
||||
upload template ("upload.html").
|
||||
"upload.html" template.
|
||||
|
||||
## 1.17 (2025)
|
||||
|
||||
You need to update the upload template ("upload.html"). Many things have
|
||||
changed! See _oddmu-templates_(5) for more.
|
||||
You need to update the "upload.html" template. Many things have changed! See
|
||||
_oddmu-templates_(5) for more.
|
||||
|
||||
You probably want to ensure that the upload link on the view template
|
||||
("view.html") and others, if you added it, has a _filename_ and _pagename_
|
||||
parameters.
|
||||
You probably want to ensure that the upload link on the "view.html" template and
|
||||
others, if you added it, has a _filename_ and _pagename_ parameters.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -108,8 +121,8 @@ Example:
|
||||
<a href="/upload/{{.Dir}}?filename={{.Base}}-1.jpg&pagename={{.Base}}">Upload</a>
|
||||
```
|
||||
|
||||
You need to change {{.Name}} to {{.Path}} when it is used in URLs, in the list
|
||||
template ("list.html"). If you don't do this, file deleting and rename may not
|
||||
You need to change {{.Name}} to {{.Path}} when it is used in URLs, in the
|
||||
"list.html" template. If you don't do this, file deleting and rename may not
|
||||
work on files containing a comma, a semicolon, a questionmark or a hash
|
||||
character. This fix was necessary because URLs for files containing a
|
||||
questionmark or a hash character would end the path at this character and treat
|
||||
@@ -164,7 +177,7 @@ together with appropriate permission checks.
|
||||
See _oddmu-apache_(5) or _oddmu-nginx_(5) for example.
|
||||
|
||||
In addition to that, you might want a link to the _list_ action from one of the
|
||||
existing templates. For example, from upload.html:
|
||||
existing templates. For example, from the "upload.html" template:
|
||||
|
||||
```
|
||||
<p>You can rename and delete files <a href="/list/{{.Dir}}">from the file list</a>.
|
||||
@@ -197,10 +210,9 @@ These are the quotation marks currently supported: 'foo' "foo" ‘foo’ ‚foo
|
||||
“foo” „foo“ ”foo” «foo» »foo« ‹foo› ›foo‹ 「foo」 「foo」 『foo』 – any such
|
||||
quoted text is searched as-is, including whitespace.
|
||||
|
||||
Add loading="lazy" for images in search.html
|
||||
|
||||
If you want to take advantage of this, you'll need to adapt your "search.html"
|
||||
template accordingly. Use like this, for example:
|
||||
Add loading="lazy" for images in the search template. If you want to take
|
||||
advantage of this, you'll need to adapt your "search.html" template accordingly.
|
||||
Use like this, for example:
|
||||
|
||||
```
|
||||
{{range .Items}}
|
||||
@@ -252,10 +264,10 @@ If you want to take advantage of this, you'll need to adapt your templates
|
||||
accordingly. The "preview.html" template is a mix of "view.html" and
|
||||
"edit.html".
|
||||
|
||||
There is an optional change to make to copies of _upload.html_ if you upload
|
||||
multiple images at a time. Instead of showing just the link to the last upload,
|
||||
you can now show the link (and the images or links, if you want to) to all the
|
||||
files uploaded. Use like this, for example:
|
||||
There is an optional change to make to copies of the "upload.html" template if
|
||||
you upload multiple images at a time. Instead of showing just the link to the
|
||||
last upload, you can now show the link (and the images or links, if you want to)
|
||||
to all the files uploaded. Use like this, for example:
|
||||
|
||||
```
|
||||
Links:<tt>{{range .Actual}}<br>{{end}}</tt>
|
||||
@@ -263,9 +275,9 @@ Links:<tt>{{range .Actual}}<br>{{end}}</tt>
|
||||
|
||||
## 1.9 (2024)
|
||||
|
||||
There is a change to make to copies of _upload.html_ if subdirectories are being
|
||||
used. The _Last_ property no longer contains the directory. It has to be added
|
||||
to the template as follows:
|
||||
There is a change to make to copies of the "upload.html" template if
|
||||
subdirectories are being used. The _Last_ property no longer contains the
|
||||
directory. It has to be added to the template as follows:
|
||||
|
||||
```
|
||||
{{if ne .Last ""}}
|
||||
@@ -289,7 +301,7 @@ The upload template can use the _Today_ property.
|
||||
The upload template comes with JavaScript that allows users to paste images or
|
||||
drag and drop files.
|
||||
|
||||
The upload template changed the id for the filename field from `text` to `name`.
|
||||
The upload template changed the id for the filename field from "text" to "name".
|
||||
|
||||
The source repository now comes with example templates.
|
||||
|
||||
@@ -299,7 +311,7 @@ No user-visible changes. Documentation and code comments got better.
|
||||
|
||||
## 1.7 (2024)
|
||||
|
||||
Allow upload of multiple files. This requires an update to the _upload.html_
|
||||
Allow upload of multiple files. This requires an update to the "upload.html"
|
||||
template: Add the _multiple_ attribute to the file input element and change the
|
||||
label from "file" to "files".
|
||||
|
||||
|
||||
49
man/oddmu-sitemap.1
Normal file
49
man/oddmu-sitemap.1
Normal file
@@ -0,0 +1,49 @@
|
||||
.\" Generated by scdoc 1.11.3
|
||||
.\" Complete documentation for this program is not available as a GNU info page
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "ODDMU-SITEMAP" "1" "2026-01-03"
|
||||
.PP
|
||||
.SH NAME
|
||||
.PP
|
||||
oddmu-sitemap - print static sitemap.\&xml
|
||||
.PP
|
||||
.SH SYNOPSIS
|
||||
.PP
|
||||
\fBoddmu sitemap\fR [\fB-base\fR \fIURL\fR] [\fB-filter\fR \fIregexp\fR]
|
||||
.PP
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
The "sitemap" subcommand prints the list of all pages in Sitemap format.\& Oddmu
|
||||
already serves the sitemap at the URL "/sitemap.\&xml" but if you'\&d prefer to
|
||||
provide a static file, use this command and redirect the output to a file called
|
||||
"sitemap.\&xml" in your document root at regular intervals.\&
|
||||
.PP
|
||||
If you do this, don'\&t proxy the "/sitemap" URL in the web server configuration.\&
|
||||
.PP
|
||||
Your "robots.\&txt" file, if you have one, should point at the sitemap you
|
||||
provide.\&
|
||||
.PP
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\fB-base\fR \fIURL\fR
|
||||
.RS 4
|
||||
The base URL is something like "https://example.\&org/view/".\&
|
||||
.RE
|
||||
\fB-filter\fR \fIregexp\fR
|
||||
.RS 4
|
||||
A regular expression matching the pages to exclude from the sitemap.\&
|
||||
This emulates the effect of the ODDMU_FILTER environment variable.\&
|
||||
.PP
|
||||
.RE
|
||||
.SH SEE ALSO
|
||||
.PP
|
||||
\fIoddmu\fR(1), \fIoddmu-filter\fR(7), \fIoddmu-apache\fR(1), \fIoddmu-nginx\fR(1),
|
||||
https://www.\&sitemaps.\&org/
|
||||
.PP
|
||||
.SH AUTHORS
|
||||
.PP
|
||||
Maintained by Alex Schroeder <alex@gnu.\&org>.\&
|
||||
38
man/oddmu-sitemap.1.txt
Normal file
38
man/oddmu-sitemap.1.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
ODDMU-SITEMAP(1)
|
||||
|
||||
# NAME
|
||||
|
||||
oddmu-sitemap - print static sitemap.xml
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
*oddmu sitemap* [*-base* _URL_] [*-filter* _regexp_]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The "sitemap" subcommand prints the list of all pages in Sitemap format. Oddmu
|
||||
already serves the sitemap at the URL "/sitemap.xml" but if you'd prefer to
|
||||
provide a static file, use this command and redirect the output to a file called
|
||||
"sitemap.xml" in your document root at regular intervals.
|
||||
|
||||
If you do this, don't proxy the "/sitemap" URL in the web server configuration.
|
||||
|
||||
Your "robots.txt" file, if you have one, should point at the sitemap you
|
||||
provide.
|
||||
|
||||
# OPTIONS
|
||||
|
||||
*-base* _URL_
|
||||
The base URL is something like "https://example.org/view/".
|
||||
*-filter* _regexp_
|
||||
A regular expression matching the pages to exclude from the sitemap.
|
||||
This emulates the effect of the ODDMU_FILTER environment variable.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
_oddmu_(1), _oddmu-filter_(7), _oddmu-apache_(1), _oddmu-nginx_(1),
|
||||
https://www.sitemaps.org/
|
||||
|
||||
# AUTHORS
|
||||
|
||||
Maintained by Alex Schroeder <alex@gnu.org>.
|
||||
@@ -1,11 +1,11 @@
|
||||
.\" Generated by scdoc 1.11.4
|
||||
.\" Generated by scdoc 1.11.3
|
||||
.\" Complete documentation for this program is not available as a GNU info page
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "ODDMU-STATIC" "1" "2025-12-05"
|
||||
.TH "ODDMU-STATIC" "1" "2026-02-06"
|
||||
.PP
|
||||
.SH NAME
|
||||
.PP
|
||||
@@ -13,7 +13,7 @@ oddmu-static - create a static copy of the site
|
||||
.PP
|
||||
.SH SYNOPSIS
|
||||
.PP
|
||||
\fBoddmu static\fR [\fB\fR-jobs\fB\fR \fIn\fR] [\fB\fR-glob\fB\fR \fIpattern\fR] [\fB\fR-shrink\fB\fR] \fIdir-name\fR
|
||||
\fBoddmu static\fR [\fB-jobs\fR \fIn\fR] [\fB-glob\fR \fIpattern\fR] [\fB-shrink\fR] \fIdir-name\fR
|
||||
.PP
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
@@ -32,7 +32,7 @@ no feed items are found, no feed is written.\& The feed is limited to the ten mo
|
||||
recent items.\&
|
||||
.PP
|
||||
Hidden files and directories (starting with a ".\&") and backup files (ending with
|
||||
a "\(ti") are skipped.\&
|
||||
a "~") are skipped.\&
|
||||
.PP
|
||||
All other files are \fIhard linked\fR.\& This is done to save space: on a typical blog
|
||||
the images take a lot more space than the text.\& On my blog in 2023 I had 2.\&62
|
||||
@@ -53,18 +53,18 @@ original, too.\&
|
||||
.PP
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
\fB\fR-jobs\fB\fR \fIn\fR
|
||||
\fB-jobs\fR \fIn\fR
|
||||
.RS 4
|
||||
By default, two jobs are used to process the files.\& If your machine has
|
||||
more cores, you can increase the number of jobs.\&
|
||||
.PP
|
||||
.RE
|
||||
\fB\fR-glob\fB\fR \fIpattern\fR
|
||||
\fB-glob\fR \fIpattern\fR
|
||||
.RS 4
|
||||
By default, all files are used for the static export.\& You can limit the
|
||||
files used by providing a shell file name pattern.\& A "*" matches any
|
||||
number of characters; a "?\&" matches exactly one character; "[a-z]"
|
||||
matches a character listed, including ranges; "[\(haa-z]" matches a
|
||||
matches a character listed, including ranges; "[^a-z]" matches a
|
||||
character not listed, including ranges; "\e" a backslash escapes the
|
||||
following character.\& You must use quotes around the pattern if you are
|
||||
using a shell as the shell would otherwise expand the pattern, resulting
|
||||
@@ -72,12 +72,12 @@ in the error "Exactly one target directory is required".\&
|
||||
.PP
|
||||
.PP
|
||||
.RE
|
||||
\fB\fR-shrink\fB\fR
|
||||
\fB-shrink\fR
|
||||
.RS 4
|
||||
By default, images are linked or copied.\& With this option, JPEG, PNG and
|
||||
WebP files are scaled down if more than 800 pixels wide and the quality
|
||||
is set to 10% for JPEG and WebP files.\& This is \fIvery bad quality\fR but
|
||||
the result is that these image files are very small.\&
|
||||
is set to 30% for JPEG and WebP files.\& This is \fIbad quality\fR but the
|
||||
result is that these image files are very small.\&
|
||||
.PP
|
||||
.RE
|
||||
.SH EXAMPLES
|
||||
|
||||
@@ -6,7 +6,7 @@ oddmu-static - create a static copy of the site
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
*oddmu static* [**-jobs** _n_] [**-glob** _pattern_] [**-shrink**] _dir-name_
|
||||
*oddmu static* [*-jobs* _n_] [*-glob* _pattern_] [*-shrink*] _dir-name_
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
@@ -46,11 +46,11 @@ original, too.
|
||||
|
||||
# OPTIONS
|
||||
|
||||
**-jobs** _n_
|
||||
*-jobs* _n_
|
||||
By default, two jobs are used to process the files. If your machine has
|
||||
more cores, you can increase the number of jobs.
|
||||
|
||||
**-glob** _pattern_
|
||||
*-glob* _pattern_
|
||||
By default, all files are used for the static export. You can limit the
|
||||
files used by providing a shell file name pattern. A "\*" matches any
|
||||
number of characters; a "?" matches exactly one character; "[a-z]"
|
||||
@@ -61,11 +61,11 @@ original, too.
|
||||
in the error "Exactly one target directory is required".
|
||||
|
||||
|
||||
**-shrink**
|
||||
*-shrink*
|
||||
By default, images are linked or copied. With this option, JPEG, PNG and
|
||||
WebP files are scaled down if more than 800 pixels wide and the quality
|
||||
is set to 10% for JPEG and WebP files. This is _very bad quality_ but
|
||||
the result is that these image files are very small.
|
||||
is set to 30% for JPEG and WebP files. This is _bad quality_ but the
|
||||
result is that these image files are very small.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
.\" Generated by scdoc 1.11.4
|
||||
.\" Generated by scdoc 1.11.3
|
||||
.\" Complete documentation for this program is not available as a GNU info page
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "ODDMU-TEMPLATES" "5" "2026-01-01" "File Formats Manual"
|
||||
.TH "ODDMU-TEMPLATES" "5" "2026-01-03" "File Formats Manual"
|
||||
.PP
|
||||
.SH NAME
|
||||
.PP
|
||||
@@ -31,12 +31,12 @@ placeholders.\&
|
||||
.IP \(bu 4
|
||||
\fIfeed.\&html\fR uses a \fIfeed\fR
|
||||
.IP \(bu 4
|
||||
\fIlist.\&html\fR uses a \fIlist\fR
|
||||
.IP \(bu 4
|
||||
\fIpreview.\&html\fR uses a \fIpage\fR
|
||||
.IP \(bu 4
|
||||
\fIsearch.\&html\fR uses a \fIsearch\fR
|
||||
.IP \(bu 4
|
||||
\fIsitemap.\&html\fR uses a \fIsitemap\fR
|
||||
.IP \(bu 4
|
||||
\fIstatic.\&html\fR uses a \fIpage\fR
|
||||
.IP \(bu 4
|
||||
\fIupload.\&html\fR uses an \fIupload\fR
|
||||
@@ -172,32 +172,6 @@ explanation.\& The next year is one lower than the year currently shown (if on a
|
||||
year page) or the current year (if looking at the index).\& If it isn'\&t set, it'\&s
|
||||
value is 0.\&
|
||||
.PP
|
||||
.SS List
|
||||
.PP
|
||||
The list contains a directory name and an array of files.\&
|
||||
.PP
|
||||
\fI{{.\&Dir}}\fR is the directory name that is being listed, percent-encoded.\&
|
||||
.PP
|
||||
\fI{{.\&Files}}\fR is the array of files.\& To refer to them, you need to use a \fI{{range
|
||||
Files}}\fR … \fI{{end}}\fR construct.\&
|
||||
.PP
|
||||
Each file has the following attributes:
|
||||
.PP
|
||||
\fI{{.\&Name}}\fR is the filename.\& The ".\&md" suffix for Markdown files is part of the
|
||||
name (unlike page names).\&
|
||||
.PP
|
||||
\fI{{.\&Path}}\fR is the page name, percent-encoded.\&
|
||||
.PP
|
||||
\fI{{.\&Title}}\fR is the page title, if the file in question is a Markdown file.\&
|
||||
.PP
|
||||
\fI{{.\&IsDir}}\fR is a boolean used to indicate that this file is a directory.\&
|
||||
.PP
|
||||
\fI{{.\&IsUp}}\fR is a boolean used to indicate the entry for the parent directory
|
||||
(the first file in the array, unless the directory being listed is the top
|
||||
directory).\& The filename of this file is ".\&.\&".\&
|
||||
.PP
|
||||
\fI{{.\&Date}}\fR is the last modification date of the file.\&
|
||||
.PP
|
||||
.SS Search
|
||||
.PP
|
||||
\fI{{.\&Query}}\fR is the query string.\&
|
||||
@@ -236,6 +210,16 @@ are only listed if a search term matches.\&
|
||||
\fI{{.\&Html}}\fR the image alt-text with a bold tag used to highlight the first
|
||||
search term that matched.\&
|
||||
.PP
|
||||
.SS Sitemap
|
||||
.PP
|
||||
The sitemap contains a list of URLs, each with its location:
|
||||
.PP
|
||||
\fI{{.\&URL}}\fR is the list of URLs.\&
|
||||
.PP
|
||||
Each URL has the following attributes:
|
||||
.PP
|
||||
\fI{{.\&Loc}}\fR with the actual page URL.\&
|
||||
.PP
|
||||
.SS Upload
|
||||
.PP
|
||||
\fI{{.\&Dir}}\fR is the directory where the uploaded file ends up, based on the URL
|
||||
|
||||
@@ -18,9 +18,9 @@ placeholders.
|
||||
- _diff.html_ uses a _page_
|
||||
- _edit.html_ uses a _page_
|
||||
- _feed.html_ uses a _feed_
|
||||
- _list.html_ uses a _list_
|
||||
- _preview.html_ uses a _page_
|
||||
- _search.html_ uses a _search_
|
||||
- _sitemap.html_ uses a _sitemap_
|
||||
- _static.html_ uses a _page_
|
||||
- _upload.html_ uses an _upload_
|
||||
- _view.html_ uses a _page_
|
||||
@@ -146,32 +146,6 @@ explanation. The next year is one lower than the year currently shown (if on a
|
||||
year page) or the current year (if looking at the index). If it isn't set, it's
|
||||
value is 0.
|
||||
|
||||
## List
|
||||
|
||||
The list contains a directory name and an array of files.
|
||||
|
||||
_{{.Dir}}_ is the directory name that is being listed, percent-encoded.
|
||||
|
||||
_{{.Files}}_ is the array of files. To refer to them, you need to use a _{{range
|
||||
.Files}}_ … _{{end}}_ construct.
|
||||
|
||||
Each file has the following attributes:
|
||||
|
||||
_{{.Name}}_ is the filename. The ".md" suffix for Markdown files is part of the
|
||||
name (unlike page names).
|
||||
|
||||
_{{.Path}}_ is the page name, percent-encoded.
|
||||
|
||||
_{{.Title}}_ is the page title, if the file in question is a Markdown file.
|
||||
|
||||
_{{.IsDir}}_ is a boolean used to indicate that this file is a directory.
|
||||
|
||||
_{{.IsUp}}_ is a boolean used to indicate the entry for the parent directory
|
||||
(the first file in the array, unless the directory being listed is the top
|
||||
directory). The filename of this file is "..".
|
||||
|
||||
_{{.Date}}_ is the last modification date of the file.
|
||||
|
||||
## Search
|
||||
|
||||
_{{.Query}}_ is the query string.
|
||||
@@ -210,6 +184,16 @@ _{{.Name}}_ is the file name for use in URLs.
|
||||
_{{.Html}}_ the image alt-text with a bold tag used to highlight the first
|
||||
search term that matched.
|
||||
|
||||
## Sitemap
|
||||
|
||||
The sitemap contains a list of URLs, each with its location:
|
||||
|
||||
_{{.URL}}_ is the list of URLs.
|
||||
|
||||
Each URL has the following attributes:
|
||||
|
||||
_{{.Loc}}_ with the actual page URL.
|
||||
|
||||
## Upload
|
||||
|
||||
_{{.Dir}}_ is the directory where the uploaded file ends up, based on the URL
|
||||
|
||||
443
man/oddmu.1
443
man/oddmu.1
@@ -0,0 +1,443 @@
|
||||
.\" Generated by scdoc 1.11.3
|
||||
.\" Complete documentation for this program is not available as a GNU info page
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.nh
|
||||
.ad l
|
||||
.\" Begin generated content:
|
||||
.TH "ODDMU" "1" "2026-02-11"
|
||||
.PP
|
||||
.SH NAME
|
||||
.PP
|
||||
oddmu - a wiki server
|
||||
.PP
|
||||
Oddmu is sometimes written Oddμ because μ is the letter mu.\&
|
||||
.PP
|
||||
.SH SYNOPSIS
|
||||
.PP
|
||||
\fBoddmu\fR
|
||||
.PP
|
||||
\fBoddmu\fR \fIsubcommand\fR [\fIarguments\fR.\&.\&.\&]
|
||||
.PP
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
Oddmu can be used as a static site generator, turning Markdown files into HTML
|
||||
files, or it can be used as a public or a private wiki server.\& If it runs as a
|
||||
public wiki server, a regular webserver should be used as reverse proxy.\&
|
||||
.PP
|
||||
Run Oddmu without any arguments to serve the current working directory as a wiki
|
||||
on port 8080.\& Point your browser to http://localhost:8080/ to use it.\& This
|
||||
redirects you to http://localhost:8080/view/index – the first page you'\&ll
|
||||
create, most likely.\&
|
||||
.PP
|
||||
See \fIoddmu\fR(5) for details about the page formatting.\&
|
||||
.PP
|
||||
If you request a file that exists, like "index.\&md", Oddmu serves it as-is.\& If
|
||||
you request a file that doesn'\&t exist, like "index", Oddmu checks if a matching
|
||||
Markdown file exists by appending the extension ".\&md".\& If such a file is found,
|
||||
it is turned into HTML and shown.\& If no such file exists, Oddmu offers you to
|
||||
create the page.\&
|
||||
.PP
|
||||
If your files don'\&t provide their own title ("# title"), the file name (without
|
||||
".\&md") is used for the page title.\&
|
||||
.PP
|
||||
Every file can be viewed as feed by using the extension ".\&rss".\& The
|
||||
feed items are based on links in bullet lists using the asterix
|
||||
("*").\&
|
||||
.PP
|
||||
Subdirectories are created as necessary.\&
|
||||
.PP
|
||||
The wiki knows the following actions for a given page name and (optional)
|
||||
directory:
|
||||
.PP
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
\fI/\fR redirects to /view/index
|
||||
.IP \(bu 4
|
||||
\fI/view/dir/\fR redirects to /view/dir/index
|
||||
.IP \(bu 4
|
||||
\fI/view/dir/name\fR shows a page
|
||||
.IP \(bu 4
|
||||
\fI/view/dir/name.\&md\fR shows the source text of a page
|
||||
.IP \(bu 4
|
||||
\fI/view/dir/name.\&rss\fR shows the RSS feed for the pages linked
|
||||
.IP \(bu 4
|
||||
\fI/diff/dir/name\fR shows the last change to a page
|
||||
.IP \(bu 4
|
||||
\fI/edit/dir/name\fR shows a form to edit a page
|
||||
.IP \(bu 4
|
||||
\fI/preview/dir/name\fR shows a preview of a page edit and the form to edit it
|
||||
.IP \(bu 4
|
||||
\fI/save/dir/name\fR saves an edit
|
||||
.IP \(bu 4
|
||||
\fI/add/dir/name\fR shows a form to add to a page
|
||||
.IP \(bu 4
|
||||
\fI/append/dir/name\fR appends an addition to a page
|
||||
.IP \(bu 4
|
||||
\fI/upload/dir/name\fR shows a form to upload a file
|
||||
.IP \(bu 4
|
||||
\fI/drop/dir/name\fR saves an upload
|
||||
.IP \(bu 4
|
||||
\fI/search/dir/?\&q=term\fR to search for a term
|
||||
.IP \(bu 4
|
||||
\fI/sitemap.\&xml\fR to list the links to all the pages
|
||||
.IP \(bu 4
|
||||
\fI/archive/dir/name.\&zip\fR to download a zip file of a directory
|
||||
.PD
|
||||
.PP
|
||||
When calling the \fIsave\fR and \fIappend\fR action, the page name is taken from the URL
|
||||
path and the page content is taken from the \fIbody\fR form parameter.\& To
|
||||
illustrate, here'\&s how to edit the "welcome" page using \fIcurl\fR:
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
curl --form body="Did you bring a towel?"
|
||||
http://localhost:8080/save/welcome
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
When calling the \fIdrop\fR action, the query parameters used are \fIname\fR for the
|
||||
target filename and \fIfile\fR for the file to upload.\& If the query parameter
|
||||
\fImaxwidth\fR is set, an attempt is made to decode and resize the image.\& JPG, PNG,
|
||||
WEBP and HEIC files can be decoded.\& Only JPG, PNG and WEBP files can be encoded,
|
||||
however.\& If the target name ends in \fI.\&jpg\fR or \fI.\&png\fR, the \fIquality\fR query
|
||||
parameter is also taken into account.\& To upload some thumbnails:
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
for f in *\&.jpg; do
|
||||
curl --form name="$f" --form file=@"$f" --form maxwidth=100
|
||||
http://localhost:8080/drop/
|
||||
done
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
When calling the \fIsearch\fR action, the search terms are taken from the query
|
||||
parameter \fIq\fR.\&
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
curl \&'http://localhost:8080/search/?q=towel\&'
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
The page name to act upon is optionally taken from the query parameter \fIid\fR.\& In
|
||||
this case, the directory must still be part of the path and may not be part of
|
||||
the \fIid\fR.\& This is enforced so that the path can be used by a webserver for
|
||||
access control.\&
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
curl \&'http://localhost:8080/view/man/?id=oddmu\&.1\&.txt\&'
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
The base name for the \fIarchive\fR action is used by the browser to save the
|
||||
downloaded file.\& For Oddmu, only the directory is important.\& The following zips
|
||||
the \fIman\fR directory and saves it as \fIman.\&zip\fR.\&
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
curl --remote-name \&'http://localhost:8080/archive/man/man\&.zip
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
.SH CONFIGURATION
|
||||
.PP
|
||||
The template files are the HTML files in the working directory.\& If they are
|
||||
missing, the default files are written to disk as soon as they are required.\&
|
||||
Please change these templates!\&
|
||||
.PP
|
||||
The first change you should make is to replace the name and email address in the
|
||||
footer of \fIview.\&html\fR.\& Look for "Your Name" and "example.\&org".\&
|
||||
.PP
|
||||
The second change you should make is to replace the name, email address and
|
||||
domain name in "feed.\&html".\& Look for "Your Name" and "example.\&org".\&
|
||||
.PP
|
||||
See \fIoddmu-templates\fR(5) for more.\&
|
||||
.PP
|
||||
.SH ENVIRONMENT
|
||||
.PP
|
||||
You can change the port served by setting the ODDMU_PORT environment variable.\&
|
||||
.PP
|
||||
You can change the address served by setting the ODDMU_ADDRESS environment
|
||||
variable to either an IPv4 address or an IPv6 address.\& If ODDMU_ADDRESS is
|
||||
unset, then the program listens on all available unicast addresses, both IPv4
|
||||
and IPv6.\& Here are a few example addresses:
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
ODDMU_ADDRESS=127\&.0\&.0\&.1 # The loopback IPv4 address\&.
|
||||
ODDMU_ADDRESS=2001:db8::3:1 # An IPv6 address\&.
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
See the Socket Activation section for an alternative method of listening which
|
||||
supports Unix-domain sockets.\&
|
||||
.PP
|
||||
In order to limit language-detection to the languages you actually use, set the
|
||||
environment variable ODDMU_LANGUAGES to a comma-separated list of ISO 639-1
|
||||
codes, e.\&g.\& "en" or "en,de,fr,pt".\&
|
||||
.PP
|
||||
You can enable webfinger to link fediverse accounts to their correct profile
|
||||
pages by setting ODDMU_WEBFINGER to "1".\& See \fIoddmu\fR(5).\&
|
||||
.PP
|
||||
If you use secret subdirectories, you cannot rely on the web server to hide
|
||||
those pages because some actions such as searching and archiving include
|
||||
subdirectories.\& They act upon a whole tree of pages, not just a single page.\& The
|
||||
ODDMU_FILTER can be used to exclude subdirectories from such tree actions.\& See
|
||||
\fIoddmu-filter\fR(7) and \fIoddmu-apache\fR(5).\&
|
||||
.PP
|
||||
.SH Socket Activation
|
||||
.PP
|
||||
Instead of specifying ODDMU_ADDRESS or ODDMU_PORT, you can start the service
|
||||
through socket activation.\& The advantage of this method is that you can use a
|
||||
Unix-domain socket instead of a TCP socket, and the permissions and ownership of
|
||||
the socket are set before the program starts.\& See \fIoddmu.\&service\fR(5),
|
||||
\fIoddmu-apache\fR(5) and \fIoddmu-nginx\fR(5) for an example of how to use socket
|
||||
activation with a Unix-domain socket under systemd and Apache.\&
|
||||
.PP
|
||||
.SH SECURITY
|
||||
.PP
|
||||
If the machine you are running Oddmu on is accessible from the Internet, you
|
||||
must secure your installation.\& The best way to do this is use a regular web
|
||||
server as a reverse proxy.\& See \fIoddmu-apache\fR(5) and \fIoddmu-nginx\fR(5) for
|
||||
example configurations.\&
|
||||
.PP
|
||||
Oddmu assumes that all the users that can edit pages or upload files are trusted
|
||||
users and therefore their content is trusted.\& Oddmu does not perform HTML
|
||||
sanitization!\&
|
||||
.PP
|
||||
For an extra dose of security, consider using a Unix-domain socket.\&
|
||||
.PP
|
||||
.SH OPTIONS
|
||||
.PP
|
||||
Oddmu can be run on the command-line using various subcommands.\&
|
||||
.PP
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
to generate the HTML for a single page, see \fIoddmu-html\fR(1)
|
||||
.IP \(bu 4
|
||||
to generate the HTML for the entire site, using Oddmu as a static site
|
||||
generator, see \fIoddmu-static\fR(1)
|
||||
.IP \(bu 4
|
||||
to export the HTML for the entire site in one big feed, see \fIoddmu-export\fR(1)
|
||||
.IP \(bu 4
|
||||
to emulate a search of the files, see \fIoddmu-search\fR(1); to understand how the
|
||||
search engine indexes pages and how it sorts and scores results, see
|
||||
\fIoddmu-search\fR(7)
|
||||
.IP \(bu 4
|
||||
to search a regular expression and replace it across all files, see
|
||||
\fIoddmu-replace\fR(1)
|
||||
.IP \(bu 4
|
||||
to learn what the most popular hashtags are, see \fIoddmu-hashtags\fR(1)
|
||||
.IP \(bu 4
|
||||
to print a table of contents (TOC) for a page, see \fIoddmu-toc\fR(1)
|
||||
.IP \(bu 4
|
||||
to list the outgoing links for a page, see \fIoddmu-links\fR(1)
|
||||
.IP \(bu 4
|
||||
to find missing pages (local links that go nowhere), see \fIoddmu-missing\fR(1)
|
||||
.IP \(bu 4
|
||||
to list all the pages with name and title, see \fIoddmu-list\fR(1)
|
||||
.IP \(bu 4
|
||||
to add links to changes, index and hashtag pages to pages you created locally,
|
||||
see \fIoddmu-notify\fR(1)
|
||||
.IP \(bu 4
|
||||
to display build information, see \fIoddmu-version\fR(1)
|
||||
.PD
|
||||
.PP
|
||||
.SH EXAMPLES
|
||||
.PP
|
||||
When saving a page, the page name is take from the URL and the page content is
|
||||
taken from the "body" form parameter.\& To illustrate, here'\&s how to edit a page
|
||||
using \fIcurl\fR(1):
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
curl --form body="Did you bring a towel?"
|
||||
http://localhost:8080/save/welcome
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
To compute the space used by your setup, use regular tools:
|
||||
.PP
|
||||
.nf
|
||||
.RS 4
|
||||
du --exclude=\&'*/.*\&' --exclude \&'*~\&' --block-size=M
|
||||
.fi
|
||||
.RE
|
||||
.PP
|
||||
.SH DESIGN
|
||||
.PP
|
||||
This is a minimal wiki.\& There is no version history.\& It'\&s well suited as a
|
||||
\fIsecondary\fR medium: collaboration and conversation happens elsewhere, in chat,
|
||||
on social media.\& The wiki serves as the text repository that results from these
|
||||
discussions.\&
|
||||
.PP
|
||||
The idea is that the webserver handles as many tasks as possible.\& It logs
|
||||
requests, does rate limiting, handles encryption, gets the certificates, and so
|
||||
on.\& The web server acts as a reverse proxy and the wiki ends up being a content
|
||||
management system with almost no structure – or endless malleability, depending
|
||||
on your point of view.\& See \fIoddmu-apache\fR(5).\&
|
||||
.PP
|
||||
.SH NOTES
|
||||
.PP
|
||||
Page names are filenames with ".\&md" appended.\& If your filesystem cannot handle
|
||||
it, it can'\&t be a page name.\& Filenames can contain slashes and Oddmu creates
|
||||
subdirectories as necessary.\&
|
||||
.PP
|
||||
Files may not end with a tilde ('\&~'\&) – these are backup files.\& When saving pages
|
||||
and file uploads, the old file is renamed to the backup file unless the backup
|
||||
file is less than an hour old, thus collapsing all edits made in an hour into a
|
||||
single diff when comparing backup and current version.\& The backup also gets an
|
||||
updated timestamp so that subsequent edits don'\&t immediately overwrite it.\&
|
||||
.PP
|
||||
The \fBindex\fR page is the default page.\& People visiting the "root" of the site are
|
||||
redirected to "/view/index".\&
|
||||
.PP
|
||||
The \fBchanges\fR page is where links to new and changed files are added.\& As an
|
||||
author, you can prevent this from happening by deselecting the checkbox "Add
|
||||
link to the list of changes.\&" The changes page can be edited like every other
|
||||
page, so it'\&s easy to undo mistakes.\&
|
||||
.PP
|
||||
Links on the changes page are grouped by date.\& When new links are added, the
|
||||
current date of the machine Oddmu is running on is used.\& If a link already
|
||||
exists on the changes page, it is moved up to the current date.\& If that leaves
|
||||
an old date without any links, that date heading is removed.\&
|
||||
.PP
|
||||
A page whose name starts with an ISO date (YYYY-MM-DD, e.\&g.\& "2023-10-28") is
|
||||
called a \fBblog\fR page.\& When creating or editing blog pages, links to it are added
|
||||
from other pages as follows:
|
||||
.PP
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
If the blog page name starts with the current year, a link is created from the
|
||||
index page back to the blog page being created or edited.\& Again, you can
|
||||
prevent this from happening by deselecting the checkbox "Add link to the list
|
||||
of changes.\&" The index page can be edited like every other page, so it'\&s easy
|
||||
to undo mistakes.\&
|
||||
.PD
|
||||
.PP
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
For every \fBhashtag\fR used, another link might be created.\& If a page named like
|
||||
the hashtag exists, a backlink is added to it, linking to the new or edited
|
||||
blog page.\&
|
||||
.PD
|
||||
.PP
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
If a link to the new or edited blog page already exists but it'\&s title is no
|
||||
longer correct, it is updated.\&
|
||||
.PD
|
||||
.PP
|
||||
New links added for blog pages are added at the top of the first unnumbered list
|
||||
using the asterisk ('\&*'\&).\& If no such list exists, a new one is started at the
|
||||
bottom of the page.\& This allows you to have a different unnumbered list further
|
||||
up on the page, as long as it uses the minus for items ('\&-'\&).\&
|
||||
.PP
|
||||
Changes made locally to the source files (using an editor) do not create any
|
||||
links on the changes page, the index page or on any hashtag pages.\& See
|
||||
\fIoddmu-notify\fR(1) for a way to add the necessary links to the changes page and
|
||||
possibly to the index and hashtag pages.\&
|
||||
.PP
|
||||
A hashtag consists of a number sign ('\&#'\&) followed by Unicode letters, numbers
|
||||
or the underscore ('\&_'\&).\& Thus, a hashtag ends with punctuation or whitespace.\&
|
||||
.PP
|
||||
The page names, titles and hashtags are loaded into memory when the server
|
||||
starts.\& If you have a lot of pages, this takes a lot of memory.\& Oddmu watches
|
||||
the working directory and any subdirectories for changes made to page files and
|
||||
updates this cache when necessary.\&
|
||||
.PP
|
||||
Uploaded files cannot be edited unless they end with ".\&md".\& If you upload a file
|
||||
called "hello.\&txt" and attempt to edit it by using "/edit/hello.\&txt" you create
|
||||
a page with the name "hello.\&txt.\&md" instead.\&
|
||||
.PP
|
||||
In order to delete uploaded files via the web, create an empty file and upload
|
||||
it.\& In order to delete a wiki page, save an empty page.\&
|
||||
.PP
|
||||
Note that some HTML file names are special: they act as templates.\& See
|
||||
\fIoddmu-templates\fR(5) for their names and their use.\& Oddmu watches the working
|
||||
directory and any subdirectories for changes made to template files and reloads
|
||||
them.\& There is no need to restart the server after making changes to the
|
||||
templates.\&
|
||||
.PP
|
||||
.SH SEE ALSO
|
||||
.PP
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
\fIoddmu\fR(5), about the markup syntax and how feeds are generated based on link
|
||||
lists
|
||||
.IP \(bu 4
|
||||
\fIoddmu-releases\fR(7), on what features are part of the latest release
|
||||
.IP \(bu 4
|
||||
\fIoddmu-filter\fR(7), on how to treat subdirectories as separate sites
|
||||
.IP \(bu 4
|
||||
\fIoddmu-search\fR(7), on how search works
|
||||
.IP \(bu 4
|
||||
\fIoddmu-templates\fR(5), on how to write the HTML templates
|
||||
.PD
|
||||
.PP
|
||||
If you run Oddmu as a web server:
|
||||
.PP
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
\fIoddmu-apache\fR(5), on how to set up Apache as a reverse proxy
|
||||
.IP \(bu 4
|
||||
\fIoddmu-nginx\fR(5), on how to set up freenginx as a reverse proxy
|
||||
.IP \(bu 4
|
||||
\fIoddmu-webdav\fR(5), on how to set up Apache as a Web-DAV server
|
||||
.IP \(bu 4
|
||||
\fIoddmu.\&service\fR(5), on how to run the service under systemd
|
||||
.PD
|
||||
.PP
|
||||
If you run Oddmu as a static site generator or pages offline and sync them with
|
||||
Oddmu running as a webserver:
|
||||
.PP
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
\fIoddmu-hashtags\fR(1), on working with hashtags
|
||||
.IP \(bu 4
|
||||
\fIoddmu-html\fR(1), on how to render a page
|
||||
.IP \(bu 4
|
||||
\fIoddmu-feed\fR(1), on how to render a feed
|
||||
.IP \(bu 4
|
||||
\fIoddmu-list\fR(1), on how to list pages and titles
|
||||
.IP \(bu 4
|
||||
\fIoddmu-links\fR(1), on how to list the outgoing links for a page
|
||||
.IP \(bu 4
|
||||
\fIoddmu-missing\fR(1), on how to find broken local links
|
||||
.IP \(bu 4
|
||||
\fIoddmu-notify\fR(1), on updating index, changes and hashtag pages
|
||||
.IP \(bu 4
|
||||
\fIoddmu-replace\fR(1), on how to search and replace text
|
||||
.IP \(bu 4
|
||||
\fIoddmu-search\fR(1), on how to run a search
|
||||
.IP \(bu 4
|
||||
\fIoddmu-sitemap\fR(1), on generating a static sitemap.\&xml
|
||||
.IP \(bu 4
|
||||
\fIoddmu-static\fR(1), on generating a static site
|
||||
.IP \(bu 4
|
||||
\fIoddmu-toc\fR(1), on how to list the table of contents (toc) a page
|
||||
.IP \(bu 4
|
||||
\fIoddmu-version\fR(1), on how to get all the build information from the binary
|
||||
.PD
|
||||
.PP
|
||||
If you want to stop using Oddmu:
|
||||
.PP
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
\fIoddmu-export\fR(1), on how to export all the files as one big RSS file
|
||||
.PD
|
||||
.PP
|
||||
And finally, if you don'\&t have the man pages, you can still read the original
|
||||
documents:
|
||||
.PP
|
||||
.PD 0
|
||||
.IP \(bu 4
|
||||
\fIoddmu-man\fR(1), to get help even if you don'\&t have the man pages installed
|
||||
.PD
|
||||
.PP
|
||||
.SH AUTHORS
|
||||
.PP
|
||||
Maintained by Alex Schroeder <alex@gnu.\&org>.\&
|
||||
|
||||
@@ -25,10 +25,11 @@ create, most likely.
|
||||
|
||||
See _oddmu_(5) for details about the page formatting.
|
||||
|
||||
If you request a page that doesn't exist, Oddmu tries to find a matching
|
||||
Markdown file by appending the extension ".md" to the page name. In the example
|
||||
above, the page name requested is "index" and the file name Oddmu tries to read
|
||||
is "index.md". If no such file exists, Oddmu offers you to create the page.
|
||||
If you request a file that exists, like "index.md", Oddmu serves it as-is. If
|
||||
you request a file that doesn't exist, like "index", Oddmu checks if a matching
|
||||
Markdown file exists by appending the extension ".md". If such a file is found,
|
||||
it is turned into HTML and shown. If no such file exists, Oddmu offers you to
|
||||
create the page.
|
||||
|
||||
If your files don't provide their own title ("# title"), the file name (without
|
||||
".md") is used for the page title.
|
||||
@@ -56,6 +57,7 @@ directory:
|
||||
- _/upload/dir/name_ shows a form to upload a file
|
||||
- _/drop/dir/name_ saves an upload
|
||||
- _/search/dir/?q=term_ to search for a term
|
||||
- _/sitemap.xml_ to list the links to all the pages
|
||||
- _/archive/dir/name.zip_ to download a zip file of a directory
|
||||
|
||||
When calling the _save_ and _append_ action, the page name is taken from the URL
|
||||
@@ -70,9 +72,9 @@ curl --form body="Did you bring a towel?" \
|
||||
When calling the _drop_ action, the query parameters used are _name_ for the
|
||||
target filename and _file_ for the file to upload. If the query parameter
|
||||
_maxwidth_ is set, an attempt is made to decode and resize the image. JPG, PNG,
|
||||
WEBP and HEIC files can be decoded. Only JPG and PNG files can be encoded,
|
||||
however. If the target name ends in _.jpg_, the _quality_ query parameter is
|
||||
also taken into account. To upload some thumbnails:
|
||||
WEBP and HEIC files can be decoded. Only JPG, PNG and WEBP files can be encoded,
|
||||
however. If the target name ends in _.jpg_ or _.png_, the _quality_ query
|
||||
parameter is also taken into account. To upload some thumbnails:
|
||||
|
||||
```
|
||||
for f in *.jpg; do
|
||||
@@ -89,11 +91,12 @@ curl 'http://localhost:8080/search/?q=towel'
|
||||
```
|
||||
|
||||
The page name to act upon is optionally taken from the query parameter _id_. In
|
||||
this case, the directory must also be part of the query parameter and not of the
|
||||
URL path.
|
||||
this case, the directory must still be part of the path and may not be part of
|
||||
the _id_. This is enforced so that the path can be used by a webserver for
|
||||
access control.
|
||||
|
||||
```
|
||||
curl 'http://localhost:8080/view/?id=man/oddmu.1.txt'
|
||||
curl 'http://localhost:8080/view/man/?id=oddmu.1.txt'
|
||||
```
|
||||
|
||||
The base name for the _archive_ action is used by the browser to save the
|
||||
@@ -106,8 +109,9 @@ curl --remote-name 'http://localhost:8080/archive/man/man.zip
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
The template files are the HTML files in the working directory. Please change
|
||||
these templates!
|
||||
The template files are the HTML files in the working directory. If they are
|
||||
missing, the default files are written to disk as soon as they are required.
|
||||
Please change these templates!
|
||||
|
||||
The first change you should make is to replace the name and email address in the
|
||||
footer of _view.html_. Look for "Your Name" and "example.org".
|
||||
@@ -246,55 +250,53 @@ current date of the machine Oddmu is running on is used. If a link already
|
||||
exists on the changes page, it is moved up to the current date. If that leaves
|
||||
an old date without any links, that date heading is removed.
|
||||
|
||||
If you want to link to the changes page, you need to do this yourself. Add a
|
||||
link from the index, for example. The "view.html" template currently doesn't do
|
||||
it. See _oddmu-templates_(5) if you want to add the link to the template.
|
||||
|
||||
A page whose name starts with an ISO date (YYYY-MM-DD, e.g. "2023-10-28") is
|
||||
called a *blog* page. When creating or editing blog pages, links to it are added
|
||||
from other pages.
|
||||
from other pages as follows:
|
||||
|
||||
If the blog page name starts with the current year, a link is created from the
|
||||
index page back to the blog page being created or edited. Again, you can prevent
|
||||
this from happening by deselecting the checkbox "Add link to the list of
|
||||
changes." The index page can be edited like every other page, so it's easy to
|
||||
undo mistakes.
|
||||
- If the blog page name starts with the current year, a link is created from the
|
||||
index page back to the blog page being created or edited. Again, you can
|
||||
prevent this from happening by deselecting the checkbox "Add link to the list
|
||||
of changes." The index page can be edited like every other page, so it's easy
|
||||
to undo mistakes.
|
||||
|
||||
For every *hashtag* used, another link might be created. If a page named like
|
||||
the hashtag exists, a backlink is added to it, linking to the new or edited blog
|
||||
page.
|
||||
- For every *hashtag* used, another link might be created. If a page named like
|
||||
the hashtag exists, a backlink is added to it, linking to the new or edited
|
||||
blog page.
|
||||
|
||||
If a link to the new or edited blog page already exists but it's title is no
|
||||
longer correct, it is updated.
|
||||
- If a link to the new or edited blog page already exists but it's title is no
|
||||
longer correct, it is updated.
|
||||
|
||||
New links added for blog pages are added at the top of the first unnumbered list
|
||||
using the asterisk ('\*'). If no such list exists, a new one is started at the
|
||||
bottom of the page. This allows you to have a different unnumbered list further
|
||||
up on the page, as long as it uses the minus for items ('-').
|
||||
|
||||
Changes made locally do not create any links on the changes page, the index page
|
||||
or on any hashtag pages. See _oddmu-notify_(1) for a way to add the necessary
|
||||
links to the changes page and possibly to the index and hashtag pages.
|
||||
Changes made locally to the source files (using an editor) do not create any
|
||||
links on the changes page, the index page or on any hashtag pages. See
|
||||
_oddmu-notify_(1) for a way to add the necessary links to the changes page and
|
||||
possibly to the index and hashtag pages.
|
||||
|
||||
A hashtag consists of a number sign ('#') followed by Unicode letters, numbers
|
||||
or the underscore ('\_'). Thus, a hashtag ends with punctuation or whitespace.
|
||||
|
||||
The page names, titles and hashtags are loaded into memory when the server
|
||||
starts. If you have a lot of pages, this takes a lot of memory.
|
||||
starts. If you have a lot of pages, this takes a lot of memory. Oddmu watches
|
||||
the working directory and any subdirectories for changes made to page files and
|
||||
updates this cache when necessary.
|
||||
|
||||
Oddmu watches the working directory and any subdirectories for changes made
|
||||
directly. Thus, in theory, it's not necessary to restart it after making such
|
||||
changes.
|
||||
|
||||
You cannot edit uploaded files. If you upload a file called "hello.txt" and
|
||||
attempt to edit it by using "/edit/hello.txt" you create a page with the name
|
||||
"hello.txt.md" instead.
|
||||
Uploaded files cannot be edited unless they end with ".md". If you upload a file
|
||||
called "hello.txt" and attempt to edit it by using "/edit/hello.txt" you create
|
||||
a page with the name "hello.txt.md" instead.
|
||||
|
||||
In order to delete uploaded files via the web, create an empty file and upload
|
||||
it. In order to delete a wiki page, save an empty page.
|
||||
|
||||
Note that some HTML file names are special: they act as templates. See
|
||||
_oddmu-templates_(5) for their names and their use.
|
||||
_oddmu-templates_(5) for their names and their use. Oddmu watches the working
|
||||
directory and any subdirectories for changes made to template files and reloads
|
||||
them. There is no need to restart the server after making changes to the
|
||||
templates.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
@@ -324,6 +326,7 @@ Oddmu running as a webserver:
|
||||
- _oddmu-notify_(1), on updating index, changes and hashtag pages
|
||||
- _oddmu-replace_(1), on how to search and replace text
|
||||
- _oddmu-search_(1), on how to run a search
|
||||
- _oddmu-sitemap_(1), on generating a static sitemap.xml
|
||||
- _oddmu-static_(1), on generating a static site
|
||||
- _oddmu-toc_(1), on how to list the table of contents (toc) a page
|
||||
- _oddmu-version_(1), on how to get all the build information from the binary
|
||||
@@ -332,6 +335,11 @@ If you want to stop using Oddmu:
|
||||
|
||||
- _oddmu-export_(1), on how to export all the files as one big RSS file
|
||||
|
||||
And finally, if you don't have the man pages, you can still read the original
|
||||
documents:
|
||||
|
||||
- _oddmu-man_(1), to get help even if you don't have the man pages installed
|
||||
|
||||
# AUTHORS
|
||||
|
||||
Maintained by Alex Schroeder <alex@gnu.org>.
|
||||
|
||||
81
man_cmd.go
Normal file
81
man_cmd.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"flag"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
type manCmd struct {
|
||||
}
|
||||
|
||||
func (cmd *manCmd) SetFlags(f *flag.FlagSet) {
|
||||
}
|
||||
|
||||
func (*manCmd) Name() string { return "man" }
|
||||
func (*manCmd) Synopsis() string { return "show a manual page" }
|
||||
func (*manCmd) Usage() string {
|
||||
return `man <topic>:
|
||||
Print a manual page on a topic. If no topic is given, the
|
||||
available topics are listed. Substrings are possible.
|
||||
`
|
||||
}
|
||||
|
||||
// A filesystem with a read-only copy of the man pages at build time.
|
||||
//
|
||||
//go:embed man/*.txt
|
||||
var manFiles embed.FS
|
||||
|
||||
func (cmd *manCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
topic := strings.Join(f.Args(), " ")
|
||||
return manCli(os.Stdout, topic)
|
||||
}
|
||||
|
||||
func manCli(w io.Writer, topic string) subcommands.ExitStatus {
|
||||
entries, err := manFiles.ReadDir("man")
|
||||
if err != nil {
|
||||
fmt.Println("An error in the build resulted in unreadable manual pages:", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
if (topic == "") {
|
||||
fmt.Println("Topics:")
|
||||
names := []string{}
|
||||
for _, entry := range entries {
|
||||
names = append(names, entry.Name())
|
||||
}
|
||||
slices.Sort(names)
|
||||
for _, name := range names {
|
||||
fmt.Println(name)
|
||||
}
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
var candidate string
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if strings.Contains(name, topic) {
|
||||
if candidate != "" {
|
||||
fmt.Printf("The topic '%s' matches both %s and %s, maybe more.\n", topic, candidate, name)
|
||||
fmt.Println("Please be more specific.")
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
candidate = name
|
||||
}
|
||||
}
|
||||
if candidate == "" {
|
||||
fmt.Printf("No manual page matching topic '%s' found\n", topic)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
b, err := manFiles.ReadFile("man/" + candidate)
|
||||
if err != nil {
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
os.Stdout.Write(b)
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
31
man_test.go
31
man_test.go
@@ -61,8 +61,8 @@ func TestManTemplates(t *testing.T) {
|
||||
assert.Greater(t, count, 0, "no templates were found")
|
||||
}
|
||||
|
||||
// Does oddmu-templates(5) mention all the templates?
|
||||
func TestManTemplateAttributess(t *testing.T) {
|
||||
// Does oddmu-templates(5) mention all the template attributes?
|
||||
func TestManTemplateAttributes(t *testing.T) {
|
||||
mfp := "man/oddmu-templates.5.txt"
|
||||
b, err := os.ReadFile(mfp)
|
||||
man := string(b)
|
||||
@@ -106,7 +106,7 @@ func TestManActions(t *testing.T) {
|
||||
wiki := string(b)
|
||||
count := 0
|
||||
// this doesn't match the root handler
|
||||
re := regexp.MustCompile(`\.HandleFunc\("(/[a-z]+/)", makeHandler\([a-z]+Handler, (true|false)(, http\.Method(Get|Post))+\)\)`)
|
||||
re := regexp.MustCompile(`mux\.HandleFunc\("(/[a-z]+/)", makeHandler\([a-z]+Handler, (true|false)(, http\.Method(Get|Post))+\)\)`)
|
||||
for _, match := range re.FindAllStringSubmatch(wiki, -1) {
|
||||
count++
|
||||
var path string
|
||||
@@ -122,6 +122,27 @@ func TestManActions(t *testing.T) {
|
||||
assert.Contains(t, main, "\n- _/_", "root")
|
||||
}
|
||||
|
||||
// Does oddmu(1) mention all the commands?
|
||||
func TestManCommands(t *testing.T) {
|
||||
b, err := os.ReadFile("man/oddmu.1.txt")
|
||||
assert.NoError(t, err)
|
||||
main := string(b)
|
||||
b, err = os.ReadFile("wiki.go")
|
||||
assert.NoError(t, err)
|
||||
wiki := string(b)
|
||||
count := 0
|
||||
re := regexp.MustCompile(`subcommands\.Register\(&([a-z]+)Cmd`)
|
||||
for _, match := range re.FindAllStringSubmatch(wiki, -1) {
|
||||
count++
|
||||
command := match[1]
|
||||
ref := "_oddmu-" + command + "_(1)"
|
||||
assert.Contains(t, main, ref, "link to the '%s' command", command)
|
||||
}
|
||||
assert.Greater(t, count, 0, "no commands were found")
|
||||
// root handler is manual
|
||||
assert.Contains(t, main, "\n- _/_", "root")
|
||||
}
|
||||
|
||||
// Does the README link to all the man pages and all the Go source files,
|
||||
// excluding the command and test files?
|
||||
func TestReadme(t *testing.T) {
|
||||
@@ -151,7 +172,9 @@ func TestReadme(t *testing.T) {
|
||||
}
|
||||
if strings.HasSuffix(fp, ".go") &&
|
||||
!strings.HasSuffix(fp, "_test.go") &&
|
||||
!strings.HasSuffix(fp, "_cmd.go") {
|
||||
!strings.HasSuffix(fp, "_cmd.go") &&
|
||||
!strings.HasSuffix(fp, "_common.go") &&
|
||||
!strings.HasSuffix(fp, "_nowebp.go") {
|
||||
count++
|
||||
s := strings.TrimPrefix(fp, "./")
|
||||
ref := "`" + s + "`"
|
||||
|
||||
@@ -77,7 +77,7 @@ func searchCli(w io.Writer, cmd *searchCmd, args []string) subcommands.ExitStatu
|
||||
case cmd.files:
|
||||
for _, p := range items {
|
||||
name := filepath.FromSlash(p.Name) + ".md\n"
|
||||
fmt.Fprintf(w, name)
|
||||
fmt.Fprint(w, name)
|
||||
}
|
||||
default:
|
||||
for _, p := range items {
|
||||
|
||||
56
sitemap.go
Normal file
56
sitemap.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// SitemapURL is a URL of the sitemap.
|
||||
type SitemapURL struct {
|
||||
// Loc is the actual location.
|
||||
Loc string
|
||||
}
|
||||
|
||||
// Sitemap is the sitemap itself, containing a list of URLs.
|
||||
type Sitemap struct {
|
||||
URL []*SitemapURL
|
||||
}
|
||||
|
||||
// sitemapHandler lists all the pages. See https://www.sitemaps.org/protocol.html for more. It takes the
|
||||
// ODDMU_FILTER environment variable into account.
|
||||
func sitemapHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/sitemap.xml" {
|
||||
http.NotFound(w, r)
|
||||
} else {
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>` + "\n"))
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
scheme += "s"
|
||||
}
|
||||
base := fmt.Sprintf("%s://%s/view/", scheme, r.Host)
|
||||
filter := os.Getenv("ODDMU_FILTER")
|
||||
renderTemplate(w, ".", "sitemap", sitemap(&index, base, filter))
|
||||
}
|
||||
}
|
||||
|
||||
// sitemap generates the list of URLs. A reference to the index needs to be provided to make it easier to write
|
||||
// tests. Exclude pages matching the filter.
|
||||
func sitemap(idx *indexStore, base, filter string) Sitemap {
|
||||
url := make([]*SitemapURL, 0)
|
||||
re, err := regexp.Compile(filter)
|
||||
if err != nil {
|
||||
log.Println("ODDMU_FILTER does not compile:", filter, err)
|
||||
return Sitemap{URL: url}
|
||||
}
|
||||
idx.RLock()
|
||||
defer idx.RUnlock()
|
||||
for name := range idx.titles {
|
||||
if filter == "" || !re.MatchString(name) {
|
||||
url = append(url, &SitemapURL{Loc: base + name})
|
||||
}
|
||||
}
|
||||
return Sitemap{URL: url}
|
||||
}
|
||||
3
sitemap.html
Normal file
3
sitemap.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
{{range .URL}}<url><loc>{{.Loc}}</loc></url>
|
||||
{{end}}</urlset>
|
||||
62
sitemap_cmd.go
Normal file
62
sitemap_cmd.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
type sitemapCmd struct {
|
||||
base string
|
||||
filter string
|
||||
}
|
||||
|
||||
func (cmd *sitemapCmd) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&cmd.base, "base", "http://localhost:8080/view/", "the base URL for the sitemap")
|
||||
f.StringVar(&cmd.filter, "filter", "", "a regular expression to filter pages")
|
||||
}
|
||||
|
||||
func (*sitemapCmd) Name() string { return "sitemap" }
|
||||
func (*sitemapCmd) Synopsis() string { return "list all the pages known in Sitemap format" }
|
||||
func (*sitemapCmd) Usage() string {
|
||||
return `sitemap [-base URL] [-filter regex]:
|
||||
Print all the pages known in Sitemap format.
|
||||
See https://www.sitemaps.org/ for more.
|
||||
`
|
||||
}
|
||||
|
||||
func (cmd *sitemapCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
n, err := index.load()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Index load: %s\n", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Indexed %d pages\n", n)
|
||||
return sitemapCli(os.Stdout, &index, cmd.base, cmd.filter)
|
||||
}
|
||||
|
||||
// sitemapCli implements the printing of a Sitemap. In order to make testing easier, it takes a Writer and an
|
||||
// indexStore. The Writer is important so that test code can provide a buffer instead of os.Stdout; the indexStore
|
||||
// is important so that test code can ensure no other test running in parallel can interfere with the list of known
|
||||
// pages (by adding or deleting pages).
|
||||
func sitemapCli(w io.Writer, idx *indexStore, base, filter string) subcommands.ExitStatus {
|
||||
initTemplates()
|
||||
template := "sitemap.html"
|
||||
t := templates.template[template]
|
||||
if t == nil {
|
||||
log.Println("Template not found:", template)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>` + "\n"))
|
||||
err := t.Execute(w, sitemap(idx, base, filter))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
18
sitemap_cmd_test.go
Normal file
18
sitemap_cmd_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSitemapCmd(t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
s := sitemapCli(b, minimalIndex(t), "https://example.org/view/", "^themes/")
|
||||
assert.Equal(t, subcommands.ExitSuccess, s)
|
||||
assert.Contains(t, b.String(), "https://example.org/view/index")
|
||||
assert.Contains(t, b.String(), "https://example.org/view/README")
|
||||
assert.NotContains(t, b.String(), "https://example.org/view/themes/")
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<meta name="generator" content="Oddμ <https://src.alexschroeder.ch/oddmu.git/>"/>
|
||||
<title>{{.Title}}</title>
|
||||
<style>
|
||||
html { max-width: 65ch; padding: 1ch; margin: auto; color: #111; background-color: #ffe }
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -15,15 +13,15 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/edwvee/exiffix"
|
||||
"github.com/gen2brain/webp"
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/ast"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
var shrinkWidth = 800
|
||||
var shrinkQuality = 30
|
||||
|
||||
type staticCmd struct {
|
||||
jobs int
|
||||
shrink bool
|
||||
@@ -69,7 +67,7 @@ func staticCli(source, target string, jobs int, glob string, shrink, verbose, qu
|
||||
index.RLock()
|
||||
defer index.RUnlock()
|
||||
loadLanguages()
|
||||
loadTemplates()
|
||||
initTemplates()
|
||||
tasks := make(chan args, 10000)
|
||||
results := make(chan error, jobs)
|
||||
done := make(chan bool, jobs)
|
||||
@@ -220,81 +218,6 @@ func staticProgressIndicator(results chan (error), stop chan (error), quiet bool
|
||||
return n, err
|
||||
}
|
||||
|
||||
// staticFile is used to walk the file trees and do the right thing for the destination directory: create
|
||||
// subdirectories, link files, render HTML files.
|
||||
func staticFile(source, target string, info fs.FileInfo, shrink bool) error {
|
||||
// render pages
|
||||
if strings.HasSuffix(source, ".md") {
|
||||
// target already has ".html" extension
|
||||
p, err := staticPage(source[:len(source)-3], target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return staticFeed(source[:len(source)-3], target[:len(target)-5]+".rss", p, info.ModTime())
|
||||
}
|
||||
if shrink {
|
||||
switch filepath.Ext(source) {
|
||||
case ".jpg", ".jpeg", ".webp":
|
||||
return shrinkImage(source, target, info)
|
||||
}
|
||||
}
|
||||
// delete before linking, ignore errors
|
||||
os.Remove(target)
|
||||
err := os.Link(source, target)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// in case of invalid cross-device link error, copy file instead
|
||||
src, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
dst, err := os.Create(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
_, err = io.Copy(dst, src)
|
||||
return err
|
||||
}
|
||||
|
||||
var shrinkWidth = 800
|
||||
var shrinkQuality = 10
|
||||
|
||||
// shrink Image shrinks images down and reduces the quality dramatically.
|
||||
func shrinkImage(source, target string, info fs.FileInfo) error {
|
||||
file, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
img, _, err := exiffix.Decode(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s cannot be decoded", source)
|
||||
}
|
||||
if img.Bounds().Dx() > shrinkWidth {
|
||||
res := imaging.Resize(img, shrinkWidth, 0, imaging.Lanczos) // preserve aspect ratio
|
||||
// imaging functions don't return errors but empty images…
|
||||
if res.Rect.Empty() {
|
||||
return fmt.Errorf("%s cannot be resized", source)
|
||||
}
|
||||
img = res
|
||||
}
|
||||
dst, err := os.Create(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
switch filepath.Ext(source) {
|
||||
case ".jpg", ".jpeg":
|
||||
err = jpeg.Encode(dst, img, &jpeg.Options{Quality: shrinkQuality})
|
||||
case ".webp":
|
||||
err = webp.Encode(dst, img, webp.Options{Quality: shrinkQuality})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// staticPage takes the filename of a page (ending in ".md") and generates a static HTML page.
|
||||
func staticPage(source, target string) (*Page, error) {
|
||||
p, err := loadPage(filepath.ToSlash(source))
|
||||
|
||||
89
static_cmd_common.go
Normal file
89
static_cmd_common.go
Normal file
@@ -0,0 +1,89 @@
|
||||
//go:build !openbsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/edwvee/exiffix"
|
||||
"github.com/gen2brain/webp"
|
||||
)
|
||||
|
||||
// staticFile is used to walk the file trees and do the right thing for the destination directory: create
|
||||
// subdirectories, link files, render HTML files.
|
||||
func staticFile(source, target string, info fs.FileInfo, shrink bool) error {
|
||||
// render pages
|
||||
if strings.HasSuffix(source, ".md") {
|
||||
// target already has ".html" extension
|
||||
p, err := staticPage(source[:len(source)-3], target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return staticFeed(source[:len(source)-3], target[:len(target)-5]+".rss", p, info.ModTime())
|
||||
}
|
||||
if shrink {
|
||||
switch filepath.Ext(source) {
|
||||
case ".jpg", ".jpeg", ".webp":
|
||||
return shrinkImage(source, target, info)
|
||||
}
|
||||
}
|
||||
// delete before linking, ignore errors
|
||||
os.Remove(target)
|
||||
err := os.Link(source, target)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// in case of invalid cross-device link error, copy file instead
|
||||
src, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
dst, err := os.Create(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
_, err = io.Copy(dst, src)
|
||||
return err
|
||||
}
|
||||
|
||||
// shrink Image shrinks images down and reduces the quality dramatically.
|
||||
func shrinkImage(source, target string, info fs.FileInfo) error {
|
||||
file, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
img, _, err := exiffix.Decode(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s cannot be decoded", source)
|
||||
}
|
||||
if img.Bounds().Dx() > shrinkWidth {
|
||||
res := imaging.Resize(img, shrinkWidth, 0, imaging.Lanczos) // preserve aspect ratio
|
||||
// imaging functions don't return errors but empty images…
|
||||
if res.Rect.Empty() {
|
||||
return fmt.Errorf("%s cannot be resized", source)
|
||||
}
|
||||
img = res
|
||||
}
|
||||
dst, err := os.Create(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
switch filepath.Ext(source) {
|
||||
case ".jpg", ".jpeg":
|
||||
err = jpeg.Encode(dst, img, &jpeg.Options{Quality: shrinkQuality})
|
||||
case ".webp":
|
||||
err = webp.Encode(dst, img, webp.Options{Quality: shrinkQuality})
|
||||
}
|
||||
return err
|
||||
}
|
||||
86
static_cmd_nowebp.go
Normal file
86
static_cmd_nowebp.go
Normal file
@@ -0,0 +1,86 @@
|
||||
//go:build openbsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/edwvee/exiffix"
|
||||
)
|
||||
|
||||
// staticFile is used to walk the file trees and do the right thing for the destination directory: create
|
||||
// subdirectories, link files, render HTML files.
|
||||
func staticFile(source, target string, info fs.FileInfo, shrink bool) error {
|
||||
// render pages
|
||||
if strings.HasSuffix(source, ".md") {
|
||||
// target already has ".html" extension
|
||||
p, err := staticPage(source[:len(source)-3], target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return staticFeed(source[:len(source)-3], target[:len(target)-5]+".rss", p, info.ModTime())
|
||||
}
|
||||
if shrink {
|
||||
switch filepath.Ext(source) {
|
||||
case ".jpg", ".jpeg":
|
||||
return shrinkImage(source, target, info)
|
||||
}
|
||||
}
|
||||
// delete before linking, ignore errors
|
||||
os.Remove(target)
|
||||
err := os.Link(source, target)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// in case of invalid cross-device link error, copy file instead
|
||||
src, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
dst, err := os.Create(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
_, err = io.Copy(dst, src)
|
||||
return err
|
||||
}
|
||||
|
||||
// shrink Image shrinks images down and reduces the quality dramatically.
|
||||
func shrinkImage(source, target string, info fs.FileInfo) error {
|
||||
file, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
img, _, err := exiffix.Decode(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s cannot be decoded", source)
|
||||
}
|
||||
if img.Bounds().Dx() > shrinkWidth {
|
||||
res := imaging.Resize(img, shrinkWidth, 0, imaging.Lanczos) // preserve aspect ratio
|
||||
// imaging functions don't return errors but empty images…
|
||||
if res.Rect.Empty() {
|
||||
return fmt.Errorf("%s cannot be resized", source)
|
||||
}
|
||||
img = res
|
||||
}
|
||||
dst, err := os.Create(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dst.Close()
|
||||
switch filepath.Ext(source) {
|
||||
case ".jpg", ".jpeg":
|
||||
err = jpeg.Encode(dst, img, &jpeg.Options{Quality: shrinkQuality})
|
||||
}
|
||||
return err
|
||||
}
|
||||
65
templates.go
65
templates.go
@@ -1,44 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// templateFiles are the various HTML template files used. These files must exist in the root directory for Oddmu to be
|
||||
// able to generate HTML output. This always requires a template.
|
||||
var templateFiles = []string{"edit.html", "add.html", "view.html", "preview.html",
|
||||
"diff.html", "search.html", "static.html", "upload.html", "feed.html",
|
||||
"list.html"}
|
||||
// A filesystem with a read-only copy of the templates at build time.
|
||||
//
|
||||
//go:embed *.html
|
||||
var templateDefaults embed.FS
|
||||
|
||||
// templateStore controls access to map of parsed HTML templates. Make sure to lock and unlock as appropriate. See
|
||||
// renderTemplate and loadTemplates.
|
||||
// templateFiles are the various HTML template files used. These files must exist in the root directory for Oddmu
|
||||
// to be able to generate HTML output. This always requires a template.
|
||||
var templateFiles = []string{}
|
||||
|
||||
// templateStore controls access to the map of parsed HTML templates. Make sure to lock and unlock as appropriate.
|
||||
// See renderTemplate and loadTemplates.
|
||||
type templateStore struct {
|
||||
sync.RWMutex
|
||||
|
||||
// template is a map of parsed HTML templates. The key is their filepath name. By default, the map only contains
|
||||
// top-level templates like "view.html". Subdirectories may contain their own templates which override the
|
||||
// templates in the root directory. If so, they are filepaths like "dir/view.html".
|
||||
// template is a map of parsed HTML templates. The key is their filepath name. By default, the map only
|
||||
// contains top-level templates like "view.html". Subdirectories may contain their own templates which
|
||||
// override the templates in the root directory. If so, they are filepaths like "dir/view.html". This is a
|
||||
// map because we need to add and remove templates as time passes.
|
||||
template map[string]*template.Template
|
||||
}
|
||||
|
||||
var templates templateStore
|
||||
|
||||
// loadTemplates loads the templates. If templates have already been loaded, return immediately.
|
||||
func loadTemplates() {
|
||||
// initTemplates loads the templates and writes them to disk if they are missing. If templates have already been
|
||||
// loaded, return immediately.
|
||||
func initTemplates() {
|
||||
if templates.template != nil {
|
||||
return
|
||||
}
|
||||
templates.Lock()
|
||||
defer templates.Unlock()
|
||||
// walk the directory, load templates and add directories
|
||||
templates.template = make(map[string]*template.Template)
|
||||
// load the defaults and make a list of the default names
|
||||
entries, err := templateDefaults.ReadDir(".")
|
||||
if err != nil {
|
||||
log.Println("An error in the build resulted in unreadable default templates:", err)
|
||||
}
|
||||
// if loading or parsing fails, continue as perhaps the files exist in the file-system
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
templateFiles = append(templateFiles, name)
|
||||
b, err := fs.ReadFile(templateDefaults, name)
|
||||
if err != nil {
|
||||
log.Printf("Cannot read built-in default template %s: %s\n", name, err)
|
||||
continue
|
||||
}
|
||||
t := template.New(name)
|
||||
templates.template[name], err = t.Parse(string(b))
|
||||
if err != nil {
|
||||
log.Printf("Cannot parse built-in default template %s: %s\n", name, err)
|
||||
}
|
||||
_, err = os.Stat(name)
|
||||
if err != nil {
|
||||
err = os.WriteFile(name, b, 0644)
|
||||
if err == nil {
|
||||
log.Printf("Wrote built-in default template %s\n", name)
|
||||
} else {
|
||||
log.Printf("Cannot write built-in default template %s: %s\n", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// walk the directory, load templates and add directories
|
||||
filepath.Walk(".", loadTemplate)
|
||||
log.Println(len(templates.template), "templates loaded")
|
||||
}
|
||||
@@ -54,7 +90,6 @@ func loadTemplate(fp string, info fs.FileInfo, err error) error {
|
||||
t, err := template.ParseFiles(fp)
|
||||
if err != nil {
|
||||
log.Println("Cannot parse template:", fp, err)
|
||||
// ignore error
|
||||
} else {
|
||||
templates.template[fp] = t
|
||||
}
|
||||
@@ -92,7 +127,7 @@ func removeTemplate(fp string) {
|
||||
// renderTemplate is the helper that is used to render the templates with data.
|
||||
// A template in the same directory is preferred, if it exists.
|
||||
func renderTemplate(w http.ResponseWriter, dir, tmpl string, data any) {
|
||||
loadTemplates()
|
||||
initTemplates()
|
||||
base := tmpl + ".html"
|
||||
templates.RLock()
|
||||
defer templates.RUnlock()
|
||||
|
||||
@@ -4,10 +4,7 @@ package main
|
||||
// This is why we import heic for side effects. For writing, the particular encoders have to be imported.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"log"
|
||||
"mime"
|
||||
@@ -23,8 +20,6 @@ import (
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/edwvee/exiffix"
|
||||
_ "github.com/gen2brain/heic"
|
||||
"github.com/gen2brain/webp"
|
||||
)
|
||||
|
||||
// Upload is the struct for the form where users upload files.
|
||||
@@ -249,16 +244,7 @@ func dropHandler(w http.ResponseWriter, r *http.Request, dir string) {
|
||||
}
|
||||
}
|
||||
// images are always reencoded, so image quality goes down
|
||||
switch to {
|
||||
case ".png":
|
||||
err = png.Encode(dst, img)
|
||||
case ".jpg", ".jpeg":
|
||||
err = jpeg.Encode(dst, img, &jpeg.Options{Quality: q})
|
||||
case ".webp":
|
||||
err = webp.Encode(dst, img, webp.Options{Quality: q}) // Quality of 100 implies Lossless.
|
||||
default:
|
||||
err = errors.New("Unsupported destination format for image conversion: " + to)
|
||||
}
|
||||
err = encodeImage(to, dst, img, q)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
27
upload_drop_common.go
Normal file
27
upload_drop_common.go
Normal file
@@ -0,0 +1,27 @@
|
||||
//go:build !openbsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
|
||||
_ "github.com/gen2brain/heic"
|
||||
"github.com/gen2brain/webp"
|
||||
)
|
||||
|
||||
func encodeImage(to string, dst *os.File, img image.Image, q int) error {
|
||||
switch to {
|
||||
case ".png":
|
||||
return png.Encode(dst, img)
|
||||
case ".jpg", ".jpeg":
|
||||
return jpeg.Encode(dst, img, &jpeg.Options{Quality: q})
|
||||
case ".webp":
|
||||
return webp.Encode(dst, img, webp.Options{Quality: q}) // Quality of 100 implies Lossless.
|
||||
default:
|
||||
return errors.New("Unsupported destination format for image conversion: " + to)
|
||||
}
|
||||
}
|
||||
53
upload_drop_heic_test.go
Normal file
53
upload_drop_heic_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
//go:build !openbsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUploadHeic(t *testing.T) {
|
||||
cleanup(t, "testdata/heic")
|
||||
// for uploads, the directory is not created automatically
|
||||
os.MkdirAll("testdata/heic", 0755)
|
||||
form := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(form)
|
||||
field, _ := writer.CreateFormField("filename")
|
||||
field.Write([]byte("ok.jpg")) // target
|
||||
file, _ := writer.CreateFormFile("file", "ok.heic") // source
|
||||
// convert -size 1x1 canvas: heic:- | base64
|
||||
imgBase64 := `
|
||||
AAAAGGZ0eXBoZWljAAAAAG1pZjFoZWljAAABqm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAHBpY3QA
|
||||
AAAAAAAAAAAAAAAAAAAADnBpdG0AAAAAAAIAAAAQaWRhdAAAAAAAAQABAAAAOGlsb2MBAAAAREAA
|
||||
AgABAAAAAAAAAcoAAQAAAAAAAAAtAAIAAQAAAAAAAAABAAAAAAAAAAgAAAA4aWluZgAAAAAAAgAA
|
||||
ABVpbmZlAgAAAQABAABodmMxAAAAABVpbmZlAgAAAAACAABncmlkAAAAANVpcHJwAAAAs2lwY28A
|
||||
AABzaHZjQwEDcAAAAAAAAAAAAB7wAPz9+PgAAA8DIAABABhAAQwB//8DcAAAAwCQAAADAAADAB66
|
||||
AkAhAAEAJ0IBAQNwAAADAJAAAAMAAAMAHqAggQWW6q6a5sCAAAADAIAAAAMAhCIAAQAGRAHBc8GJ
|
||||
AAAAFGlzcGUAAAAAAAAAQAAAAEAAAAAUaXNwZQAAAAAAAAABAAAAAQAAABBwaXhpAAAAAAMICAgA
|
||||
AAAaaXBtYQAAAAAAAAACAAECgQIAAgIDhAAAABppcmVmAAAAAAAAAA5kaW1nAAIAAQABAAAANW1k
|
||||
YXQAAAApKAGvEyE1mvXho5qH3STtzcWnOxedwNIXAKNDaJNqz3uONoCHeUhi/HA=`
|
||||
img, err := base64.StdEncoding.DecodeString(imgBase64)
|
||||
assert.NoError(t, err)
|
||||
file.Write(img)
|
||||
writer.Close()
|
||||
HTTPUploadAndRedirectTo(t, makeHandler(dropHandler, false, http.MethodPost), "/drop/testdata/heic/",
|
||||
writer.FormDataContentType(), form, "/upload/testdata/heic/?filename=ok.jpg&uploads=ok.jpg")
|
||||
fp := "testdata/heic/ok.jpg"
|
||||
fi, err := os.Open(fp)
|
||||
assert.NoError(t, err)
|
||||
b, err := mimetype.DetectReader(fi)
|
||||
assert.NoError(t, err)
|
||||
a := mime.TypeByExtension(filepath.Ext(fp))
|
||||
assert.Equal(t, a, b.String())
|
||||
}
|
||||
|
||||
22
upload_drop_nowebp.go
Normal file
22
upload_drop_nowebp.go
Normal file
@@ -0,0 +1,22 @@
|
||||
//go:build openbsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
)
|
||||
|
||||
func encodeImage(to string, dst *os.File, img image.Image, q int) error {
|
||||
switch to {
|
||||
case ".png":
|
||||
return png.Encode(dst, img)
|
||||
case ".jpg", ".jpeg":
|
||||
return jpeg.Encode(dst, img, &jpeg.Options{Quality: q})
|
||||
default:
|
||||
return errors.New("Unsupported destination format for image conversion: " + to)
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,15 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/gen2brain/webp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -75,87 +70,6 @@ func TestUploadJpg(t *testing.T) {
|
||||
writer.FormDataContentType(), form, "/upload/testdata/jpg/?filename=ok.jpg&uploads=ok.jpg")
|
||||
}
|
||||
|
||||
func TestUploadHeic(t *testing.T) {
|
||||
cleanup(t, "testdata/heic")
|
||||
// for uploads, the directory is not created automatically
|
||||
os.MkdirAll("testdata/heic", 0755)
|
||||
form := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(form)
|
||||
field, _ := writer.CreateFormField("filename")
|
||||
field.Write([]byte("ok.jpg")) // target
|
||||
file, _ := writer.CreateFormFile("file", "ok.heic") // source
|
||||
// convert -size 1x1 canvas: heic:- | base64
|
||||
imgBase64 := `
|
||||
AAAAGGZ0eXBoZWljAAAAAG1pZjFoZWljAAABqm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAHBpY3QA
|
||||
AAAAAAAAAAAAAAAAAAAADnBpdG0AAAAAAAIAAAAQaWRhdAAAAAAAAQABAAAAOGlsb2MBAAAAREAA
|
||||
AgABAAAAAAAAAcoAAQAAAAAAAAAtAAIAAQAAAAAAAAABAAAAAAAAAAgAAAA4aWluZgAAAAAAAgAA
|
||||
ABVpbmZlAgAAAQABAABodmMxAAAAABVpbmZlAgAAAAACAABncmlkAAAAANVpcHJwAAAAs2lwY28A
|
||||
AABzaHZjQwEDcAAAAAAAAAAAAB7wAPz9+PgAAA8DIAABABhAAQwB//8DcAAAAwCQAAADAAADAB66
|
||||
AkAhAAEAJ0IBAQNwAAADAJAAAAMAAAMAHqAggQWW6q6a5sCAAAADAIAAAAMAhCIAAQAGRAHBc8GJ
|
||||
AAAAFGlzcGUAAAAAAAAAQAAAAEAAAAAUaXNwZQAAAAAAAAABAAAAAQAAABBwaXhpAAAAAAMICAgA
|
||||
AAAaaXBtYQAAAAAAAAACAAECgQIAAgIDhAAAABppcmVmAAAAAAAAAA5kaW1nAAIAAQABAAAANW1k
|
||||
YXQAAAApKAGvEyE1mvXho5qH3STtzcWnOxedwNIXAKNDaJNqz3uONoCHeUhi/HA=`
|
||||
img, err := base64.StdEncoding.DecodeString(imgBase64)
|
||||
assert.NoError(t, err)
|
||||
file.Write(img)
|
||||
writer.Close()
|
||||
HTTPUploadAndRedirectTo(t, makeHandler(dropHandler, false, http.MethodPost), "/drop/testdata/heic/",
|
||||
writer.FormDataContentType(), form, "/upload/testdata/heic/?filename=ok.jpg&uploads=ok.jpg")
|
||||
fp := "testdata/heic/ok.jpg"
|
||||
fi, err := os.Open(fp)
|
||||
assert.NoError(t, err)
|
||||
b, err := mimetype.DetectReader(fi)
|
||||
assert.NoError(t, err)
|
||||
a := mime.TypeByExtension(filepath.Ext(fp))
|
||||
assert.Equal(t, a, b.String())
|
||||
}
|
||||
|
||||
func TestUploadWebp(t *testing.T) {
|
||||
cleanup(t, "testdata/webp")
|
||||
// for uploads, the directory is not created automatically
|
||||
os.MkdirAll("testdata/webp", 0755)
|
||||
form := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(form)
|
||||
field, _ := writer.CreateFormField("filename")
|
||||
field.Write([]byte("ok.jpg")) // target
|
||||
file, _ := writer.CreateFormFile("file", "ok.webp") // source
|
||||
img := image.NewRGBA(image.Rect(0, 0, 20, 20))
|
||||
webp.Encode(file, img)
|
||||
writer.Close()
|
||||
HTTPUploadAndRedirectTo(t, makeHandler(dropHandler, false, http.MethodPost), "/drop/testdata/webp/",
|
||||
writer.FormDataContentType(), form, "/upload/testdata/webp/?filename=ok.jpg&uploads=ok.jpg")
|
||||
fp := "testdata/webp/ok.jpg"
|
||||
fi, err := os.Open(fp)
|
||||
assert.NoError(t, err)
|
||||
b, err := mimetype.DetectReader(fi)
|
||||
assert.NoError(t, err)
|
||||
a := mime.TypeByExtension(filepath.Ext(fp))
|
||||
assert.Equal(t, a, b.String())
|
||||
}
|
||||
|
||||
func TestConvertToWebp(t *testing.T) {
|
||||
cleanup(t, "testdata/towebp")
|
||||
// for uploads, the directory is not created automatically
|
||||
os.MkdirAll("testdata/towebp", 0755)
|
||||
form := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(form)
|
||||
field, _ := writer.CreateFormField("filename")
|
||||
field.Write([]byte("ok.webp"))
|
||||
file, _ := writer.CreateFormFile("file", "ok.png")
|
||||
img := image.NewRGBA(image.Rect(0, 0, 20, 20))
|
||||
png.Encode(file, img)
|
||||
writer.Close()
|
||||
HTTPUploadAndRedirectTo(t, makeHandler(dropHandler, false, http.MethodPost), "/drop/testdata/towebp/",
|
||||
writer.FormDataContentType(), form, "/upload/testdata/towebp/?filename=ok.webp&uploads=ok.webp")
|
||||
fp := "testdata/towebp/ok.webp"
|
||||
fi, err := os.Open(fp)
|
||||
assert.NoError(t, err)
|
||||
b, err := mimetype.DetectReader(fi)
|
||||
assert.NoError(t, err)
|
||||
a := mime.TypeByExtension(filepath.Ext(fp))
|
||||
assert.Equal(t, a, b.String())
|
||||
}
|
||||
|
||||
func TestDeleteFile(t *testing.T) {
|
||||
cleanup(t, "testdata/delete")
|
||||
os.MkdirAll("testdata/delete", 0755)
|
||||
|
||||
65
upload_drop_webp_test.go
Normal file
65
upload_drop_webp_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
//go:build !openbsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/png"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/gen2brain/webp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUploadWebp(t *testing.T) {
|
||||
cleanup(t, "testdata/webp")
|
||||
// for uploads, the directory is not created automatically
|
||||
os.MkdirAll("testdata/webp", 0755)
|
||||
form := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(form)
|
||||
field, _ := writer.CreateFormField("filename")
|
||||
field.Write([]byte("ok.jpg")) // target
|
||||
file, _ := writer.CreateFormFile("file", "ok.webp") // source
|
||||
img := image.NewRGBA(image.Rect(0, 0, 20, 20))
|
||||
webp.Encode(file, img)
|
||||
writer.Close()
|
||||
HTTPUploadAndRedirectTo(t, makeHandler(dropHandler, false, http.MethodPost), "/drop/testdata/webp/",
|
||||
writer.FormDataContentType(), form, "/upload/testdata/webp/?filename=ok.jpg&uploads=ok.jpg")
|
||||
fp := "testdata/webp/ok.jpg"
|
||||
fi, err := os.Open(fp)
|
||||
assert.NoError(t, err)
|
||||
b, err := mimetype.DetectReader(fi)
|
||||
assert.NoError(t, err)
|
||||
a := mime.TypeByExtension(filepath.Ext(fp))
|
||||
assert.Equal(t, a, b.String())
|
||||
}
|
||||
|
||||
func TestConvertToWebp(t *testing.T) {
|
||||
cleanup(t, "testdata/towebp")
|
||||
// for uploads, the directory is not created automatically
|
||||
os.MkdirAll("testdata/towebp", 0755)
|
||||
form := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(form)
|
||||
field, _ := writer.CreateFormField("filename")
|
||||
field.Write([]byte("ok.webp"))
|
||||
file, _ := writer.CreateFormFile("file", "ok.png")
|
||||
img := image.NewRGBA(image.Rect(0, 0, 20, 20))
|
||||
png.Encode(file, img)
|
||||
writer.Close()
|
||||
HTTPUploadAndRedirectTo(t, makeHandler(dropHandler, false, http.MethodPost), "/drop/testdata/towebp/",
|
||||
writer.FormDataContentType(), form, "/upload/testdata/towebp/?filename=ok.webp&uploads=ok.webp")
|
||||
fp := "testdata/towebp/ok.webp"
|
||||
fi, err := os.Open(fp)
|
||||
assert.NoError(t, err)
|
||||
b, err := mimetype.DetectReader(fi)
|
||||
assert.NoError(t, err)
|
||||
a := mime.TypeByExtension(filepath.Ext(fp))
|
||||
assert.Equal(t, a, b.String())
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
||||
<meta name="generator" content="Oddμ <https://src.alexschroeder.ch/oddmu.git/>"/>
|
||||
<title>{{.Title}}</title>
|
||||
<style>
|
||||
html { max-width: 70ch; padding: 1ch; margin: auto; color: #111; background-color: #ffe }
|
||||
|
||||
3
wiki.go
3
wiki.go
@@ -208,6 +208,7 @@ func serve() {
|
||||
mux.HandleFunc("/upload/", makeHandler(uploadHandler, false, http.MethodGet))
|
||||
mux.HandleFunc("/drop/", makeHandler(dropHandler, false, http.MethodPost))
|
||||
mux.HandleFunc("/search/", makeHandler(searchHandler, false, http.MethodGet, http.MethodPost))
|
||||
mux.HandleFunc("/sitemap.xml", sitemapHandler)
|
||||
srv := &http.Server{
|
||||
ReadTimeout: 2 * time.Minute,
|
||||
WriteTimeout: 5 * time.Minute,
|
||||
@@ -229,6 +230,7 @@ func commands() {
|
||||
subcommands.Register(&exportCmd{}, "")
|
||||
subcommands.Register(&hashtagsCmd{}, "")
|
||||
subcommands.Register(&feedCmd{}, "")
|
||||
subcommands.Register(&manCmd{}, "")
|
||||
subcommands.Register(&htmlCmd{}, "")
|
||||
subcommands.Register(&listCmd{}, "")
|
||||
subcommands.Register(&linksCmd{}, "")
|
||||
@@ -239,6 +241,7 @@ func commands() {
|
||||
subcommands.Register(&staticCmd{}, "")
|
||||
subcommands.Register(&tocCmd{}, "")
|
||||
subcommands.Register(&versionCmd{}, "")
|
||||
subcommands.Register(&sitemapCmd{}, "")
|
||||
|
||||
flag.Parse()
|
||||
ctx := context.Background()
|
||||
|
||||
Reference in New Issue
Block a user