Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3313e17efa | ||
|
|
f8bba997bf | ||
|
|
4cf4d1695f | ||
|
|
1b5abe4de1 | ||
|
|
5dfbbdb775 | ||
|
|
7e1948c93f | ||
|
|
3718f6ec7c | ||
|
|
ce108bc07d | ||
|
|
da84a76e79 | ||
|
|
d679eb4661 | ||
|
|
0f7525a23c | ||
|
|
727280147a | ||
|
|
a3e8654c5b | ||
|
|
a4cc67cd74 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
mycorrhiza
|
mycorrhiza
|
||||||
|
.idea
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Mycorrhiza Wiki** is a lightweight file-system wiki engine that uses Git for keeping history. [Main wiki](https://mycorrhiza.wiki)
|
**Mycorrhiza Wiki** is a lightweight file-system wiki engine that uses Git for keeping history. [Main wiki](https://mycorrhiza.wiki)
|
||||||
|
|
||||||
<img src="https://mycorrhiza.wiki/binary/release/1.14/screenshot" alt="A screenshot of mycorrhiza.wiki's home page in the Safari browser" width="700">
|
<img src="https://mycorrhiza.wiki/binary/release/1.15.1/screenshot" alt="A screenshot of mycorrhiza.wiki's home page in the Safari browser" width="700">
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@ -30,7 +30,7 @@ Compare Mycorrhiza Wiki with other engines on [WikiMatrix](https://www.wikimatri
|
|||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
See [the deployment guide](https://mycorrhiza.wiki/hypha/deployment) on the wiki. Also, Mycorrhiza might be available in your repositories.
|
See [the deployment guide](https://mycorrhiza.wiki/hypha/deployment) on the wiki. Also, Mycorrhiza might be available in your repositories. See [Repology](https://repology.org/project/mycorrhiza/versions).
|
||||||
|
|
||||||
|
|
||||||
## Contributing and community
|
## Contributing and community
|
||||||
@ -46,3 +46,5 @@ If you want to contribute with code, open a pull request on GitHub or send a pat
|
|||||||
If you want to report an issue, open an issue on GitHub or contact us directly.
|
If you want to report an issue, open an issue on GitHub or contact us directly.
|
||||||
|
|
||||||
Consider supporting the development on [Boosty](https://boosty.to/bouncepaw).
|
Consider supporting the development on [Boosty](https://boosty.to/bouncepaw).
|
||||||
|
|
||||||
|
Check out [Betula](https://betula.mycorrhiza.wiki) as well.
|
||||||
41
flag.go
41
flag.go
@ -3,19 +3,20 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"golang.org/x/term"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
"github.com/bouncepaw/mycorrhiza/internal/files"
|
||||||
user2 "github.com/bouncepaw/mycorrhiza/internal/user"
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/version"
|
"github.com/bouncepaw/mycorrhiza/internal/version"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CLI options are read and parsed here.
|
// CLI options are read and parsed here.
|
||||||
@ -31,7 +32,7 @@ func printHelp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseCliArgs parses CLI options and sets several important global variables. Call it early.
|
// parseCliArgs parses CLI options and sets several important global variables. Call it early.
|
||||||
func parseCliArgs() {
|
func parseCliArgs() error {
|
||||||
var createAdminName string
|
var createAdminName string
|
||||||
var versionFlag bool
|
var versionFlag bool
|
||||||
|
|
||||||
@ -42,43 +43,53 @@ func parseCliArgs() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if versionFlag {
|
if versionFlag {
|
||||||
fmt.Println("Mycorrhiza Wiki", version.Long)
|
slog.Info("Running Mycorrhiza Wiki", "version", version.Long)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
log.Fatal("error: pass a wiki directory")
|
slog.Error("Pass a wiki directory")
|
||||||
|
return errors.New("wiki directory not passed")
|
||||||
}
|
}
|
||||||
|
|
||||||
wikiDir, err := filepath.Abs(args[0])
|
wikiDir, err := filepath.Abs(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to take absolute filepath of wiki directory",
|
||||||
|
"path", args[0], "err", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.WikiDir = wikiDir
|
cfg.WikiDir = wikiDir
|
||||||
|
|
||||||
if createAdminName != "" {
|
if createAdminName != "" {
|
||||||
createAdminCommand(createAdminName)
|
if err := createAdminCommand(createAdminName); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createAdminCommand(name string) {
|
func createAdminCommand(name string) error {
|
||||||
if err := files.PrepareWikiRoot(); err != nil {
|
if err := files.PrepareWikiRoot(); err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to prepare wiki root", "err", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
cfg.UseAuth = true
|
cfg.UseAuth = true
|
||||||
cfg.AllowRegistration = true
|
cfg.AllowRegistration = true
|
||||||
user2.InitUserDatabase()
|
user.InitUserDatabase()
|
||||||
|
|
||||||
password, err := askPass("Password")
|
password, err := askPass("Password")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to prompt password", "err", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if err := user2.Register(name, password, "admin", "local", true); err != nil {
|
if err := user.Register(name, password, "admin", "local", true); err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to register admin", "err", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func askPass(prompt string) (string, error) {
|
func askPass(prompt string) (string, error) {
|
||||||
|
|||||||
10
go.mod
10
go.mod
@ -7,16 +7,14 @@ require (
|
|||||||
github.com/go-ini/ini v1.67.0
|
github.com/go-ini/ini v1.67.0
|
||||||
github.com/gorilla/feeds v1.2.0
|
github.com/gorilla/feeds v1.2.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/valyala/quicktemplate v1.7.0
|
golang.org/x/crypto v0.31.0
|
||||||
golang.org/x/crypto v0.27.0
|
golang.org/x/term v0.27.0
|
||||||
golang.org/x/term v0.24.0
|
golang.org/x/text v0.21.0
|
||||||
golang.org/x/text v0.18.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/stretchr/testify v1.7.0 // indirect
|
github.com/stretchr/testify v1.7.0 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
golang.org/x/sys v0.25.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Use this trick to test local Mycomarkup changes, replace the path with yours,
|
// Use this trick to test local Mycomarkup changes, replace the path with yours,
|
||||||
|
|||||||
37
go.sum
37
go.sum
@ -1,18 +1,13 @@
|
|||||||
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0 h1:zAZwMF+6x8U/nunpqPRVYoDiqVUMBHI04PG8GsDrFOk=
|
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0 h1:zAZwMF+6x8U/nunpqPRVYoDiqVUMBHI04PG8GsDrFOk=
|
||||||
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0/go.mod h1:TCzFBqW11En4EjLfcQtJu8C/Ro7FIFR8vZ+nM9f6Q28=
|
git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0/go.mod h1:TCzFBqW11En4EjLfcQtJu8C/Ro7FIFR8vZ+nM9f6Q28=
|
||||||
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
|
||||||
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
|
github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
|
||||||
github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
|
github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
|
||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
|
||||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@ -24,30 +19,14 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
|
||||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
|
||||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
|
||||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
= Help
|
= Help
|
||||||
|
|
||||||
This is documentation for Mycorrhiza Wiki 1.15. Choose a topic from the list.
|
This is documentation for Mycorrhiza Wiki 1.15.1. Choose a topic from the list.
|
||||||
|
|
||||||
The documentation is incomplete. If you want to contribute to the documentation, open a pull request or an issue on [[https://github.com/bouncepaw/mycorrhiza | GitHub]] or [[https://lists.sr.ht/~bouncepaw/mycorrhiza-devel | send a patch]].
|
The documentation is incomplete. If you want to contribute to the documentation, open a pull request or an issue on [[https://github.com/bouncepaw/mycorrhiza | GitHub]] or [[https://lists.sr.ht/~bouncepaw/mycorrhiza-devel | send a patch]].
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ You can upload any media file, but only those listed below will be displayed on
|
|||||||
|
|
||||||
* **Images:** jpg, gif, png, webp, svg, ico
|
* **Images:** jpg, gif, png, webp, svg, ico
|
||||||
* **Video:** ogg, webm, mp4
|
* **Video:** ogg, webm, mp4
|
||||||
* **Audio:** ogg, webm, mp3
|
* **Audio:** ogg, webm, mp3, flac, wav
|
||||||
|
|
||||||
== How to upload media?
|
== How to upload media?
|
||||||
For non-existent hyphae, upload a file in the //Upload media// section.
|
For non-existent hyphae, upload a file in the //Upload media// section.
|
||||||
|
|||||||
@ -3,11 +3,12 @@ package history
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
|
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ package history
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log/slog"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -21,12 +21,14 @@ var renameMsgPattern = regexp.MustCompile(`^Rename ‘(.*)’ to ‘.*’`)
|
|||||||
var gitEnv = []string{"GIT_COMMITTER_NAME=wikimind", "GIT_COMMITTER_EMAIL=wikimind@mycorrhiza"}
|
var gitEnv = []string{"GIT_COMMITTER_NAME=wikimind", "GIT_COMMITTER_EMAIL=wikimind@mycorrhiza"}
|
||||||
|
|
||||||
// Start finds git and initializes git credentials.
|
// Start finds git and initializes git credentials.
|
||||||
func Start() {
|
func Start() error {
|
||||||
path, err := exec.LookPath("git")
|
path, err := exec.LookPath("git")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not find the git executable. Check your $PATH.")
|
slog.Error("Could not find the Git executable. Check your $PATH.")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
gitpath = path
|
gitpath = path
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitGitRepo checks a Git repository and initializes it if necessary.
|
// InitGitRepo checks a Git repository and initializes it if necessary.
|
||||||
@ -44,7 +46,7 @@ func InitGitRepo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !isGitRepo {
|
if !isGitRepo {
|
||||||
log.Println("Initializing Git repo at", files.HyphaeDir())
|
slog.Info("Initializing Git repo", "path", files.HyphaeDir())
|
||||||
gitsh("init")
|
gitsh("init")
|
||||||
gitsh("config", "core.quotePath", "false")
|
gitsh("config", "core.quotePath", "false")
|
||||||
}
|
}
|
||||||
@ -60,7 +62,7 @@ func gitsh(args ...string) (out bytes.Buffer, err error) {
|
|||||||
|
|
||||||
b, err := cmd.CombinedOutput()
|
b, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("gitsh:", err)
|
slog.Info("Git command failed", "err", err, "output", string(b))
|
||||||
}
|
}
|
||||||
return *bytes.NewBuffer(b), err
|
return *bytes.NewBuffer(b), err
|
||||||
}
|
}
|
||||||
@ -77,7 +79,9 @@ func silentGitsh(args ...string) (out bytes.Buffer, err error) {
|
|||||||
|
|
||||||
// Rename renames from `from` to `to` using `git mv`.
|
// Rename renames from `from` to `to` using `git mv`.
|
||||||
func Rename(from, to string) error {
|
func Rename(from, to string) error {
|
||||||
log.Println(util.ShorterPath(from), util.ShorterPath(to))
|
slog.Info("Renaming file with git mv",
|
||||||
|
"from", util.ShorterPath(from),
|
||||||
|
"to", util.ShorterPath(to))
|
||||||
_, err := gitsh("mv", "--force", from, to)
|
_, err := gitsh("mv", "--force", from, to)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,19 +4,21 @@ package histweb
|
|||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
viewutil2 "github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/files"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitHandlers(rtr *mux.Router) {
|
func InitHandlers(rtr *mux.Router) {
|
||||||
@ -30,9 +32,9 @@ func InitHandlers(rtr *mux.Router) {
|
|||||||
rtr.HandleFunc("/recent-changes-atom", handlerRecentChangesAtom)
|
rtr.HandleFunc("/recent-changes-atom", handlerRecentChangesAtom)
|
||||||
rtr.HandleFunc("/recent-changes-json", handlerRecentChangesJSON)
|
rtr.HandleFunc("/recent-changes-json", handlerRecentChangesJSON)
|
||||||
|
|
||||||
chainPrimitiveDiff = viewutil2.CopyEnRuWith(fs, "view_primitive_diff.html", ruTranslation)
|
chainPrimitiveDiff = viewutil.CopyEnRuWith(fs, "view_primitive_diff.html", ruTranslation)
|
||||||
chainRecentChanges = viewutil2.CopyEnRuWith(fs, "view_recent_changes.html", ruTranslation)
|
chainRecentChanges = viewutil.CopyEnRuWith(fs, "view_recent_changes.html", ruTranslation)
|
||||||
chainHistory = viewutil2.CopyEnRuWith(fs, "view_history.html", ruTranslation)
|
chainHistory = viewutil.CopyEnRuWith(fs, "view_history.html", ruTranslation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
|
func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
|
||||||
@ -45,12 +47,12 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
|
|||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
mycoFilePath string
|
mycoFilePath string
|
||||||
h = hyphae2.ByName(util.CanonicalName(slug))
|
h = hyphae.ByName(util.CanonicalName(slug))
|
||||||
)
|
)
|
||||||
switch h := h.(type) {
|
switch h := h.(type) {
|
||||||
case hyphae2.ExistingHypha:
|
case hyphae.ExistingHypha:
|
||||||
mycoFilePath = h.TextFilePath()
|
mycoFilePath = h.TextFilePath()
|
||||||
case *hyphae2.EmptyHypha:
|
case *hyphae.EmptyHypha:
|
||||||
mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco")
|
mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco")
|
||||||
}
|
}
|
||||||
text, err := history.PrimitiveDiffAtRevision(mycoFilePath, revHash)
|
text, err := history.PrimitiveDiffAtRevision(mycoFilePath, revHash)
|
||||||
@ -58,7 +60,7 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
|
|||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
primitiveDiff(viewutil2.MetaFrom(w, rq), h, revHash, text)
|
primitiveDiff(viewutil.MetaFrom(w, rq), h, revHash, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlerRecentChanges displays the /recent-changes/ page.
|
// handlerRecentChanges displays the /recent-changes/ page.
|
||||||
@ -68,7 +70,7 @@ func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
|
|||||||
if editCount > 100 {
|
if editCount > 100 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
recentChanges(viewutil2.MetaFrom(w, rq), editCount, history.RecentChanges(editCount))
|
recentChanges(viewutil.MetaFrom(w, rq), editCount, history.RecentChanges(editCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlerHistory lists all revisions of a hypha.
|
// handlerHistory lists all revisions of a hypha.
|
||||||
@ -81,9 +83,11 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
list = history.WithRevisions(hyphaName, revs)
|
list = history.WithRevisions(hyphaName, revs)
|
||||||
}
|
}
|
||||||
log.Println("Found", len(revs), "revisions for", hyphaName)
|
|
||||||
|
|
||||||
historyView(viewutil2.MetaFrom(w, rq), hyphaName, list)
|
// TODO: extra log, not needed?
|
||||||
|
slog.Info("Found revisions", "hyphaName", hyphaName, "n", len(revs), "err", err)
|
||||||
|
|
||||||
|
historyView(viewutil.MetaFrom(w, rq), hyphaName, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// genericHandlerOfFeeds is a helper function for the web feed handlers.
|
// genericHandlerOfFeeds is a helper function for the web feed handlers.
|
||||||
@ -135,20 +139,20 @@ var (
|
|||||||
{{define "n recent changes"}}{{.}} свеж{{if eq . 1}}ая правка{{else if le . 4}}их правок{{else}}их правок{{end}}{{end}}
|
{{define "n recent changes"}}{{.}} свеж{{if eq . 1}}ая правка{{else if le . 4}}их правок{{else}}их правок{{end}}{{end}}
|
||||||
{{define "recent empty"}}Правки не найдены.{{end}}
|
{{define "recent empty"}}Правки не найдены.{{end}}
|
||||||
`
|
`
|
||||||
chainPrimitiveDiff, chainRecentChanges, chainHistory viewutil2.Chain
|
chainPrimitiveDiff, chainRecentChanges, chainHistory viewutil.Chain
|
||||||
)
|
)
|
||||||
|
|
||||||
type recentChangesData struct {
|
type recentChangesData struct {
|
||||||
*viewutil2.BaseData
|
*viewutil.BaseData
|
||||||
EditCount int
|
EditCount int
|
||||||
Changes []history.Revision
|
Changes []history.Revision
|
||||||
UserHypha string
|
UserHypha string
|
||||||
Stops []int
|
Stops []int
|
||||||
}
|
}
|
||||||
|
|
||||||
func recentChanges(meta viewutil2.Meta, editCount int, changes []history.Revision) {
|
func recentChanges(meta viewutil.Meta, editCount int, changes []history.Revision) {
|
||||||
viewutil2.ExecutePage(meta, chainRecentChanges, recentChangesData{
|
viewutil.ExecutePage(meta, chainRecentChanges, recentChangesData{
|
||||||
BaseData: &viewutil2.BaseData{},
|
BaseData: &viewutil.BaseData{},
|
||||||
EditCount: editCount,
|
EditCount: editCount,
|
||||||
Changes: changes,
|
Changes: changes,
|
||||||
UserHypha: cfg.UserHypha,
|
UserHypha: cfg.UserHypha,
|
||||||
@ -157,13 +161,13 @@ func recentChanges(meta viewutil2.Meta, editCount int, changes []history.Revisio
|
|||||||
}
|
}
|
||||||
|
|
||||||
type primitiveDiffData struct {
|
type primitiveDiffData struct {
|
||||||
*viewutil2.BaseData
|
*viewutil.BaseData
|
||||||
HyphaName string
|
HyphaName string
|
||||||
Hash string
|
Hash string
|
||||||
Text template.HTML
|
Text template.HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
func primitiveDiff(meta viewutil2.Meta, h hyphae2.Hypha, hash, text string) {
|
func primitiveDiff(meta viewutil.Meta, h hyphae.Hypha, hash, text string) {
|
||||||
hunks := history.SplitPrimitiveDiff(text)
|
hunks := history.SplitPrimitiveDiff(text)
|
||||||
if len(hunks) > 0 {
|
if len(hunks) > 0 {
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
@ -198,8 +202,8 @@ func primitiveDiff(meta viewutil2.Meta, h hyphae2.Hypha, hash, text string) {
|
|||||||
text = fmt.Sprintf(
|
text = fmt.Sprintf(
|
||||||
`<pre class="codeblock"><code>%s</code></pre>`, text)
|
`<pre class="codeblock"><code>%s</code></pre>`, text)
|
||||||
}
|
}
|
||||||
viewutil2.ExecutePage(meta, chainPrimitiveDiff, primitiveDiffData{
|
viewutil.ExecutePage(meta, chainPrimitiveDiff, primitiveDiffData{
|
||||||
BaseData: &viewutil2.BaseData{},
|
BaseData: &viewutil.BaseData{},
|
||||||
HyphaName: h.CanonicalName(),
|
HyphaName: h.CanonicalName(),
|
||||||
Hash: hash,
|
Hash: hash,
|
||||||
Text: template.HTML(text),
|
Text: template.HTML(text),
|
||||||
@ -207,14 +211,14 @@ func primitiveDiff(meta viewutil2.Meta, h hyphae2.Hypha, hash, text string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type historyData struct {
|
type historyData struct {
|
||||||
*viewutil2.BaseData
|
*viewutil.BaseData
|
||||||
HyphaName string
|
HyphaName string
|
||||||
Contents string
|
Contents string
|
||||||
}
|
}
|
||||||
|
|
||||||
func historyView(meta viewutil2.Meta, hyphaName, contents string) {
|
func historyView(meta viewutil.Meta, hyphaName, contents string) {
|
||||||
viewutil2.ExecutePage(meta, chainHistory, historyData{
|
viewutil.ExecutePage(meta, chainHistory, historyData{
|
||||||
BaseData: &viewutil2.BaseData{
|
BaseData: &viewutil.BaseData{
|
||||||
Addr: "/history/" + util.CanonicalName(hyphaName),
|
Addr: "/history/" + util.CanonicalName(hyphaName),
|
||||||
},
|
},
|
||||||
HyphaName: hyphaName,
|
HyphaName: hyphaName,
|
||||||
|
|||||||
@ -4,11 +4,11 @@ package history
|
|||||||
// Things related to writing history.
|
// Things related to writing history.
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -2,18 +2,72 @@ package history
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"html"
|
||||||
|
"log/slog"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
"github.com/bouncepaw/mycorrhiza/internal/files"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Revision represents a revision, duh. Hash is usually short. Username is extracted from email.
|
// WithRevisions returns an HTML representation of `revs` that is meant to be inserted in a history page.
|
||||||
|
func WithRevisions(hyphaName string, revs []Revision) string {
|
||||||
|
var buf strings.Builder
|
||||||
|
|
||||||
|
for _, grp := range groupRevisionsByMonth(revs) {
|
||||||
|
currentYear := grp[0].Time.Year()
|
||||||
|
currentMonth := grp[0].Time.Month()
|
||||||
|
sectionId := fmt.Sprintf("%04d-%02d", currentYear, currentMonth)
|
||||||
|
|
||||||
|
buf.WriteString(fmt.Sprintf(
|
||||||
|
`<section class="history__month">
|
||||||
|
<a href="#%s" class="history__month-anchor">
|
||||||
|
<h2 id="%s" class="history__month-title">%d %s</h2>
|
||||||
|
</a>
|
||||||
|
<ul class="history__entries">`,
|
||||||
|
sectionId, sectionId, currentYear, currentMonth.String(),
|
||||||
|
))
|
||||||
|
|
||||||
|
for _, rev := range grp {
|
||||||
|
buf.WriteString(fmt.Sprintf(
|
||||||
|
`<li class="history__entry">
|
||||||
|
<a class="history-entry" href="/rev/%s/%s">
|
||||||
|
<time class="history-entry__time">%s</time>
|
||||||
|
</a>
|
||||||
|
<span class="history-entry__hash"><a href="/primitive-diff/%s/%s">%s</a></span>
|
||||||
|
<span class="history-entry__msg">%s</span>`,
|
||||||
|
rev.Hash, hyphaName,
|
||||||
|
rev.timeToDisplay(),
|
||||||
|
rev.Hash, hyphaName, rev.Hash,
|
||||||
|
html.EscapeString(rev.Message),
|
||||||
|
))
|
||||||
|
|
||||||
|
if rev.Username != "anon" {
|
||||||
|
buf.WriteString(fmt.Sprintf(
|
||||||
|
`<span class="history-entry__author">by <a href="/hypha/%s/%s" rel="author">%s</a></span>`,
|
||||||
|
cfg.UserHypha, rev.Username, rev.Username,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString("</li>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString(`</ul></section>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revision represents a revision of a hypha.
|
||||||
type Revision struct {
|
type Revision struct {
|
||||||
Hash string
|
// Hash is usually short.
|
||||||
|
Hash string
|
||||||
|
// Username is extracted from email.
|
||||||
Username string
|
Username string
|
||||||
Time time.Time
|
Time time.Time
|
||||||
Message string
|
Message string
|
||||||
@ -21,6 +75,62 @@ type Revision struct {
|
|||||||
hyphaeAffectedBuf []string
|
hyphaeAffectedBuf []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HyphaeDiffsHTML returns a comma-separated list of diffs links of current revision for every affected file as HTML string.
|
||||||
|
func (rev Revision) HyphaeDiffsHTML() string {
|
||||||
|
entries := rev.hyphaeAffected()
|
||||||
|
if len(entries) == 1 {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`<a href="/primitive-diff/%s/%s">%s</a>`,
|
||||||
|
rev.Hash, entries[0], rev.Hash,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf strings.Builder
|
||||||
|
for i, hyphaName := range entries {
|
||||||
|
if i > 0 {
|
||||||
|
buf.WriteString(`<span aria-hidden="true">, </span>`)
|
||||||
|
}
|
||||||
|
buf.WriteString(`<a href="/primitive-diff/`)
|
||||||
|
buf.WriteString(rev.Hash)
|
||||||
|
buf.WriteString(`/`)
|
||||||
|
buf.WriteString(hyphaName)
|
||||||
|
buf.WriteString(`">`)
|
||||||
|
if i == 0 {
|
||||||
|
buf.WriteString(rev.Hash)
|
||||||
|
buf.WriteString(" ")
|
||||||
|
}
|
||||||
|
buf.WriteString(hyphaName)
|
||||||
|
buf.WriteString(`</a>`)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// descriptionForFeed generates a good enough HTML contents for a web feed.
|
||||||
|
func (rev *Revision) descriptionForFeed() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`<p><b>%s</b> (by %s at %s)</p>
|
||||||
|
<p>Hyphae affected: %s</p>
|
||||||
|
<pre><code>%s</code></pre>`,
|
||||||
|
rev.Message, rev.Username, rev.TimeString(),
|
||||||
|
rev.HyphaeLinksHTML(),
|
||||||
|
rev.textDiff(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
|
||||||
|
func (rev Revision) HyphaeLinksHTML() string {
|
||||||
|
var buf strings.Builder
|
||||||
|
for i, hyphaName := range rev.hyphaeAffected() {
|
||||||
|
if i > 0 {
|
||||||
|
buf.WriteString(`<span aria-hidden="true">, <span>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
urlSafeHyphaName := url.PathEscape(hyphaName)
|
||||||
|
buf.WriteString(fmt.Sprintf(`<a href="/hypha/%s">%s</a>`, urlSafeHyphaName, hyphaName))
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
// gitLog calls `git log` and parses the results.
|
// gitLog calls `git log` and parses the results.
|
||||||
func gitLog(args ...string) ([]Revision, error) {
|
func gitLog(args ...string) ([]Revision, error) {
|
||||||
args = append([]string{
|
args = append([]string{
|
||||||
@ -71,7 +181,9 @@ func (stream *recentChangesStream) next(n int) []Revision {
|
|||||||
|
|
||||||
res, err := gitLog(args...)
|
res, err := gitLog(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
// TODO: return error
|
||||||
|
slog.Error("Failed to git log", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if len(res) != 0 {
|
if len(res) != 0 {
|
||||||
stream.currHash = res[len(res)-1].Hash
|
stream.currHash = res[len(res)-1].Hash
|
||||||
@ -103,14 +215,14 @@ func (stream recentChangesStream) iterator() func() (Revision, bool) {
|
|||||||
func RecentChanges(n int) []Revision {
|
func RecentChanges(n int) []Revision {
|
||||||
stream := newRecentChangesStream()
|
stream := newRecentChangesStream()
|
||||||
revs := stream.next(n)
|
revs := stream.next(n)
|
||||||
log.Printf("Found %d recent changes", len(revs))
|
slog.Info("Found recent changes", "n", len(revs))
|
||||||
return revs
|
return revs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revisions returns slice of revisions for the given hypha name, ordered most recent first.
|
// Revisions returns slice of revisions for the given hypha name, ordered most recent first.
|
||||||
func Revisions(hyphaName string) ([]Revision, error) {
|
func Revisions(hyphaName string) ([]Revision, error) {
|
||||||
revs, err := gitLog("--", hyphaName+".*")
|
revs, err := gitLog("--", hyphaName+".*")
|
||||||
log.Printf("Found %d revisions for ‘%s’\n", len(revs), hyphaName)
|
slog.Info("Found revisions", "hyphaName", hyphaName, "n", len(revs), "err", err)
|
||||||
return revs, err
|
return revs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,73 +0,0 @@
|
|||||||
{% import "fmt" %}
|
|
||||||
{% import "github.com/bouncepaw/mycorrhiza/internal/cfg" %}
|
|
||||||
|
|
||||||
HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
|
|
||||||
{% func (rev Revision) HyphaeLinksHTML() %}
|
|
||||||
{% stripspace %}
|
|
||||||
{% for i, hyphaName := range rev.hyphaeAffected() %}
|
|
||||||
{% if i > 0 %}
|
|
||||||
<span aria-hidden="true">, </span>
|
|
||||||
{% endif %}
|
|
||||||
<a href="/hypha/{%s hyphaName %}">{%s hyphaName %}</a>
|
|
||||||
{% endfor %}
|
|
||||||
{% endstripspace %}
|
|
||||||
{% endfunc %}
|
|
||||||
|
|
||||||
|
|
||||||
HyphaeDiffsHTML returns a comma-separated list of diffs links of current revision for every affected file as HTML string.
|
|
||||||
{% func (rev Revision) HyphaeDiffsHTML() %}
|
|
||||||
{% code entries := rev.hyphaeAffected() %}
|
|
||||||
{% stripspace %}
|
|
||||||
{% if len(entries) == 1 %}
|
|
||||||
<a href="/primitive-diff/{%s rev.Hash %}/{%s entries[0] %}">{%s rev.Hash %}</a>
|
|
||||||
{% else %}
|
|
||||||
{% for i, hyphaName := range entries %}
|
|
||||||
{% if i > 0 %}
|
|
||||||
<span aria-hidden="true">, </span>
|
|
||||||
{% endif %}
|
|
||||||
<a href="/primitive-diff/{%s rev.Hash %}/{%s hyphaName %}">
|
|
||||||
{% if i == 0 %}
|
|
||||||
{%s rev.Hash %}
|
|
||||||
{% endif %}
|
|
||||||
{%s hyphaName %}</a>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endstripspace %}
|
|
||||||
{% endfunc %}
|
|
||||||
|
|
||||||
descriptionForFeed generates a good enough HTML contents for a web feed.
|
|
||||||
{% func (rev *Revision) descriptionForFeed() %}
|
|
||||||
<p><b>{%s rev.Message %}</b> (by {%s rev.Username %} at {%s rev.TimeString() %})</p>
|
|
||||||
<p>Hyphae affected: {%= rev.HyphaeLinksHTML() %}</p>
|
|
||||||
<pre><code>{%s rev.textDiff() %}</code></pre>
|
|
||||||
{% endfunc %}
|
|
||||||
|
|
||||||
WithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
|
|
||||||
{% func WithRevisions(hyphaName string, revs []Revision) %}
|
|
||||||
{% for _, grp := range groupRevisionsByMonth(revs) %}
|
|
||||||
{% code
|
|
||||||
currentYear := grp[0].Time.Year()
|
|
||||||
currentMonth := grp[0].Time.Month()
|
|
||||||
sectionId := fmt.Sprintf("%04d-%02d", currentYear, currentMonth)
|
|
||||||
%}
|
|
||||||
<section class="history__month">
|
|
||||||
<a href="#{%s sectionId %}" class="history__month-anchor">
|
|
||||||
<h2 id="{%s sectionId %}" class="history__month-title">{%d currentYear %} {%s currentMonth.String() %}</h2>
|
|
||||||
</a>
|
|
||||||
<ul class="history__entries">
|
|
||||||
{% for _, rev := range grp %}
|
|
||||||
<li class="history__entry">
|
|
||||||
<a class="history-entry" href="/rev/{%s rev.Hash %}/{%s hyphaName %}">
|
|
||||||
<time class="history-entry__time">{%s rev.timeToDisplay() %}</time>
|
|
||||||
</a>
|
|
||||||
<span class="history-entry__hash"><a href="/primitive-diff/{%s rev.Hash %}/{%s hyphaName %}">{%s rev.Hash %}</a></span>
|
|
||||||
<span class="history-entry__msg">{%s rev.Message %}</span>
|
|
||||||
{% if rev.Username != "anon" %}
|
|
||||||
<span class="history-entry__author">by <a href="/hypha/{%s cfg.UserHypha %}/{%s rev.Username %}" rel="author">{%s rev.Username %}</a></span>
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfunc %}
|
|
||||||
@ -1,384 +0,0 @@
|
|||||||
// Code generated by qtc from "view.qtpl". DO NOT EDIT.
|
|
||||||
// See https://github.com/valyala/quicktemplate for details.
|
|
||||||
|
|
||||||
//line history/view.qtpl:1
|
|
||||||
package history
|
|
||||||
|
|
||||||
//line history/view.qtpl:1
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
//line history/view.qtpl:2
|
|
||||||
import "github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
|
|
||||||
// HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
|
|
||||||
|
|
||||||
//line history/view.qtpl:5
|
|
||||||
import (
|
|
||||||
qtio422016 "io"
|
|
||||||
|
|
||||||
qt422016 "github.com/valyala/quicktemplate"
|
|
||||||
)
|
|
||||||
|
|
||||||
//line history/view.qtpl:5
|
|
||||||
var (
|
|
||||||
_ = qtio422016.Copy
|
|
||||||
_ = qt422016.AcquireByteBuffer
|
|
||||||
)
|
|
||||||
|
|
||||||
//line history/view.qtpl:5
|
|
||||||
func (rev Revision) StreamHyphaeLinksHTML(qw422016 *qt422016.Writer) {
|
|
||||||
//line history/view.qtpl:5
|
|
||||||
qw422016.N().S(`
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:7
|
|
||||||
for i, hyphaName := range rev.hyphaeAffected() {
|
|
||||||
//line history/view.qtpl:8
|
|
||||||
if i > 0 {
|
|
||||||
//line history/view.qtpl:8
|
|
||||||
qw422016.N().S(`<span aria-hidden="true">, </span>`)
|
|
||||||
//line history/view.qtpl:10
|
|
||||||
}
|
|
||||||
//line history/view.qtpl:10
|
|
||||||
qw422016.N().S(`<a href="/hypha/`)
|
|
||||||
//line history/view.qtpl:11
|
|
||||||
qw422016.E().S(hyphaName)
|
|
||||||
//line history/view.qtpl:11
|
|
||||||
qw422016.N().S(`">`)
|
|
||||||
//line history/view.qtpl:11
|
|
||||||
qw422016.E().S(hyphaName)
|
|
||||||
//line history/view.qtpl:11
|
|
||||||
qw422016.N().S(`</a>`)
|
|
||||||
//line history/view.qtpl:12
|
|
||||||
}
|
|
||||||
//line history/view.qtpl:13
|
|
||||||
qw422016.N().S(`
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
}
|
|
||||||
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
func (rev Revision) WriteHyphaeLinksHTML(qq422016 qtio422016.Writer) {
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
rev.StreamHyphaeLinksHTML(qw422016)
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
qt422016.ReleaseWriter(qw422016)
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
}
|
|
||||||
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
func (rev Revision) HyphaeLinksHTML() string {
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
rev.WriteHyphaeLinksHTML(qb422016)
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
qs422016 := string(qb422016.B)
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
return qs422016
|
|
||||||
//line history/view.qtpl:14
|
|
||||||
}
|
|
||||||
|
|
||||||
// HyphaeDiffsHTML returns a comma-separated list of diffs links of current revision for every affected file as HTML string.
|
|
||||||
|
|
||||||
//line history/view.qtpl:18
|
|
||||||
func (rev Revision) StreamHyphaeDiffsHTML(qw422016 *qt422016.Writer) {
|
|
||||||
//line history/view.qtpl:18
|
|
||||||
qw422016.N().S(`
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:19
|
|
||||||
entries := rev.hyphaeAffected()
|
|
||||||
|
|
||||||
//line history/view.qtpl:19
|
|
||||||
qw422016.N().S(`
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:21
|
|
||||||
if len(entries) == 1 {
|
|
||||||
//line history/view.qtpl:21
|
|
||||||
qw422016.N().S(`<a href="/primitive-diff/`)
|
|
||||||
//line history/view.qtpl:22
|
|
||||||
qw422016.E().S(rev.Hash)
|
|
||||||
//line history/view.qtpl:22
|
|
||||||
qw422016.N().S(`/`)
|
|
||||||
//line history/view.qtpl:22
|
|
||||||
qw422016.E().S(entries[0])
|
|
||||||
//line history/view.qtpl:22
|
|
||||||
qw422016.N().S(`">`)
|
|
||||||
//line history/view.qtpl:22
|
|
||||||
qw422016.E().S(rev.Hash)
|
|
||||||
//line history/view.qtpl:22
|
|
||||||
qw422016.N().S(`</a>`)
|
|
||||||
//line history/view.qtpl:23
|
|
||||||
} else {
|
|
||||||
//line history/view.qtpl:24
|
|
||||||
for i, hyphaName := range entries {
|
|
||||||
//line history/view.qtpl:25
|
|
||||||
if i > 0 {
|
|
||||||
//line history/view.qtpl:25
|
|
||||||
qw422016.N().S(`<span aria-hidden="true">, </span>`)
|
|
||||||
//line history/view.qtpl:27
|
|
||||||
}
|
|
||||||
//line history/view.qtpl:27
|
|
||||||
qw422016.N().S(`<a href="/primitive-diff/`)
|
|
||||||
//line history/view.qtpl:28
|
|
||||||
qw422016.E().S(rev.Hash)
|
|
||||||
//line history/view.qtpl:28
|
|
||||||
qw422016.N().S(`/`)
|
|
||||||
//line history/view.qtpl:28
|
|
||||||
qw422016.E().S(hyphaName)
|
|
||||||
//line history/view.qtpl:28
|
|
||||||
qw422016.N().S(`">`)
|
|
||||||
//line history/view.qtpl:29
|
|
||||||
if i == 0 {
|
|
||||||
//line history/view.qtpl:30
|
|
||||||
qw422016.E().S(rev.Hash)
|
|
||||||
//line history/view.qtpl:30
|
|
||||||
qw422016.N().S(` `)
|
|
||||||
//line history/view.qtpl:31
|
|
||||||
}
|
|
||||||
//line history/view.qtpl:32
|
|
||||||
qw422016.E().S(hyphaName)
|
|
||||||
//line history/view.qtpl:32
|
|
||||||
qw422016.N().S(`</a>`)
|
|
||||||
//line history/view.qtpl:33
|
|
||||||
}
|
|
||||||
//line history/view.qtpl:34
|
|
||||||
}
|
|
||||||
//line history/view.qtpl:35
|
|
||||||
qw422016.N().S(`
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
}
|
|
||||||
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
func (rev Revision) WriteHyphaeDiffsHTML(qq422016 qtio422016.Writer) {
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
rev.StreamHyphaeDiffsHTML(qw422016)
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
qt422016.ReleaseWriter(qw422016)
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
}
|
|
||||||
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
func (rev Revision) HyphaeDiffsHTML() string {
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
rev.WriteHyphaeDiffsHTML(qb422016)
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
qs422016 := string(qb422016.B)
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
return qs422016
|
|
||||||
//line history/view.qtpl:36
|
|
||||||
}
|
|
||||||
|
|
||||||
// descriptionForFeed generates a good enough HTML contents for a web feed.
|
|
||||||
|
|
||||||
//line history/view.qtpl:39
|
|
||||||
func (rev *Revision) streamdescriptionForFeed(qw422016 *qt422016.Writer) {
|
|
||||||
//line history/view.qtpl:39
|
|
||||||
qw422016.N().S(`
|
|
||||||
<p><b>`)
|
|
||||||
//line history/view.qtpl:40
|
|
||||||
qw422016.E().S(rev.Message)
|
|
||||||
//line history/view.qtpl:40
|
|
||||||
qw422016.N().S(`</b> (by `)
|
|
||||||
//line history/view.qtpl:40
|
|
||||||
qw422016.E().S(rev.Username)
|
|
||||||
//line history/view.qtpl:40
|
|
||||||
qw422016.N().S(` at `)
|
|
||||||
//line history/view.qtpl:40
|
|
||||||
qw422016.E().S(rev.TimeString())
|
|
||||||
//line history/view.qtpl:40
|
|
||||||
qw422016.N().S(`)</p>
|
|
||||||
<p>Hyphae affected: `)
|
|
||||||
//line history/view.qtpl:41
|
|
||||||
rev.StreamHyphaeLinksHTML(qw422016)
|
|
||||||
//line history/view.qtpl:41
|
|
||||||
qw422016.N().S(`</p>
|
|
||||||
<pre><code>`)
|
|
||||||
//line history/view.qtpl:42
|
|
||||||
qw422016.E().S(rev.textDiff())
|
|
||||||
//line history/view.qtpl:42
|
|
||||||
qw422016.N().S(`</code></pre>
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
}
|
|
||||||
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
func (rev *Revision) writedescriptionForFeed(qq422016 qtio422016.Writer) {
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
rev.streamdescriptionForFeed(qw422016)
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
qt422016.ReleaseWriter(qw422016)
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
}
|
|
||||||
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
func (rev *Revision) descriptionForFeed() string {
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
rev.writedescriptionForFeed(qb422016)
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
qs422016 := string(qb422016.B)
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
return qs422016
|
|
||||||
//line history/view.qtpl:43
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
|
|
||||||
|
|
||||||
//line history/view.qtpl:46
|
|
||||||
func StreamWithRevisions(qw422016 *qt422016.Writer, hyphaName string, revs []Revision) {
|
|
||||||
//line history/view.qtpl:46
|
|
||||||
qw422016.N().S(`
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:47
|
|
||||||
for _, grp := range groupRevisionsByMonth(revs) {
|
|
||||||
//line history/view.qtpl:47
|
|
||||||
qw422016.N().S(`
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:49
|
|
||||||
currentYear := grp[0].Time.Year()
|
|
||||||
currentMonth := grp[0].Time.Month()
|
|
||||||
sectionId := fmt.Sprintf("%04d-%02d", currentYear, currentMonth)
|
|
||||||
|
|
||||||
//line history/view.qtpl:52
|
|
||||||
qw422016.N().S(`
|
|
||||||
<section class="history__month">
|
|
||||||
<a href="#`)
|
|
||||||
//line history/view.qtpl:54
|
|
||||||
qw422016.E().S(sectionId)
|
|
||||||
//line history/view.qtpl:54
|
|
||||||
qw422016.N().S(`" class="history__month-anchor">
|
|
||||||
<h2 id="`)
|
|
||||||
//line history/view.qtpl:55
|
|
||||||
qw422016.E().S(sectionId)
|
|
||||||
//line history/view.qtpl:55
|
|
||||||
qw422016.N().S(`" class="history__month-title">`)
|
|
||||||
//line history/view.qtpl:55
|
|
||||||
qw422016.N().D(currentYear)
|
|
||||||
//line history/view.qtpl:55
|
|
||||||
qw422016.N().S(` `)
|
|
||||||
//line history/view.qtpl:55
|
|
||||||
qw422016.E().S(currentMonth.String())
|
|
||||||
//line history/view.qtpl:55
|
|
||||||
qw422016.N().S(`</h2>
|
|
||||||
</a>
|
|
||||||
<ul class="history__entries">
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:58
|
|
||||||
for _, rev := range grp {
|
|
||||||
//line history/view.qtpl:58
|
|
||||||
qw422016.N().S(`
|
|
||||||
<li class="history__entry">
|
|
||||||
<a class="history-entry" href="/rev/`)
|
|
||||||
//line history/view.qtpl:60
|
|
||||||
qw422016.E().S(rev.Hash)
|
|
||||||
//line history/view.qtpl:60
|
|
||||||
qw422016.N().S(`/`)
|
|
||||||
//line history/view.qtpl:60
|
|
||||||
qw422016.E().S(hyphaName)
|
|
||||||
//line history/view.qtpl:60
|
|
||||||
qw422016.N().S(`">
|
|
||||||
<time class="history-entry__time">`)
|
|
||||||
//line history/view.qtpl:61
|
|
||||||
qw422016.E().S(rev.timeToDisplay())
|
|
||||||
//line history/view.qtpl:61
|
|
||||||
qw422016.N().S(`</time>
|
|
||||||
</a>
|
|
||||||
<span class="history-entry__hash"><a href="/primitive-diff/`)
|
|
||||||
//line history/view.qtpl:63
|
|
||||||
qw422016.E().S(rev.Hash)
|
|
||||||
//line history/view.qtpl:63
|
|
||||||
qw422016.N().S(`/`)
|
|
||||||
//line history/view.qtpl:63
|
|
||||||
qw422016.E().S(hyphaName)
|
|
||||||
//line history/view.qtpl:63
|
|
||||||
qw422016.N().S(`">`)
|
|
||||||
//line history/view.qtpl:63
|
|
||||||
qw422016.E().S(rev.Hash)
|
|
||||||
//line history/view.qtpl:63
|
|
||||||
qw422016.N().S(`</a></span>
|
|
||||||
<span class="history-entry__msg">`)
|
|
||||||
//line history/view.qtpl:64
|
|
||||||
qw422016.E().S(rev.Message)
|
|
||||||
//line history/view.qtpl:64
|
|
||||||
qw422016.N().S(`</span>
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:65
|
|
||||||
if rev.Username != "anon" {
|
|
||||||
//line history/view.qtpl:65
|
|
||||||
qw422016.N().S(`
|
|
||||||
<span class="history-entry__author">by <a href="/hypha/`)
|
|
||||||
//line history/view.qtpl:66
|
|
||||||
qw422016.E().S(cfg.UserHypha)
|
|
||||||
//line history/view.qtpl:66
|
|
||||||
qw422016.N().S(`/`)
|
|
||||||
//line history/view.qtpl:66
|
|
||||||
qw422016.E().S(rev.Username)
|
|
||||||
//line history/view.qtpl:66
|
|
||||||
qw422016.N().S(`" rel="author">`)
|
|
||||||
//line history/view.qtpl:66
|
|
||||||
qw422016.E().S(rev.Username)
|
|
||||||
//line history/view.qtpl:66
|
|
||||||
qw422016.N().S(`</a></span>
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:67
|
|
||||||
}
|
|
||||||
//line history/view.qtpl:67
|
|
||||||
qw422016.N().S(`
|
|
||||||
</li>
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:69
|
|
||||||
}
|
|
||||||
//line history/view.qtpl:69
|
|
||||||
qw422016.N().S(`
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:72
|
|
||||||
}
|
|
||||||
//line history/view.qtpl:72
|
|
||||||
qw422016.N().S(`
|
|
||||||
`)
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
}
|
|
||||||
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
func WriteWithRevisions(qq422016 qtio422016.Writer, hyphaName string, revs []Revision) {
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
StreamWithRevisions(qw422016, hyphaName, revs)
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
qt422016.ReleaseWriter(qw422016)
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
}
|
|
||||||
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
func WithRevisions(hyphaName string, revs []Revision) string {
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
WriteWithRevisions(qb422016, hyphaName, revs)
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
qs422016 := string(qb422016.B)
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
return qs422016
|
|
||||||
//line history/view.qtpl:73
|
|
||||||
}
|
|
||||||
62
httpd.go
62
httpd.go
@ -1,16 +1,18 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"errors"
|
||||||
"log"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveHTTP(handler http.Handler) {
|
func serveHTTP(handler http.Handler) (err error) {
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
ReadTimeout: 300 * time.Second,
|
ReadTimeout: 300 * time.Second,
|
||||||
WriteTimeout: 300 * time.Second,
|
WriteTimeout: 300 * time.Second,
|
||||||
@ -19,35 +21,51 @@ func serveHTTP(handler http.Handler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(cfg.ListenAddr, "/") {
|
if strings.HasPrefix(cfg.ListenAddr, "/") {
|
||||||
startUnixSocketServer(server, cfg.ListenAddr)
|
err = startUnixSocketServer(server, cfg.ListenAddr)
|
||||||
} else {
|
} else {
|
||||||
server.Addr = cfg.ListenAddr
|
server.Addr = cfg.ListenAddr
|
||||||
startHTTPServer(server)
|
err = startHTTPServer(server)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func startUnixSocketServer(server *http.Server, socketFile string) {
|
func startUnixSocketServer(server *http.Server, socketPath string) error {
|
||||||
os.Remove(socketFile)
|
err := os.Remove(socketPath)
|
||||||
|
|
||||||
listener, err := net.Listen("unix", socketFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to start a server: %v", err)
|
slog.Warn("Failed to clean up old socket", "err", err)
|
||||||
}
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
if err := os.Chmod(socketFile, 0666); err != nil {
|
|
||||||
log.Fatalf("Failed to set socket permissions: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Listening on Unix socket %s", cfg.ListenAddr)
|
listener, err := net.Listen("unix", socketPath)
|
||||||
if err := server.Serve(listener); err != http.ErrServerClosed {
|
if err != nil {
|
||||||
log.Fatalf("Failed to start a server: %v", err)
|
slog.Error("Failed to start the server", "err", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
defer func(listener net.Listener) {
|
||||||
|
_ = listener.Close()
|
||||||
|
}(listener)
|
||||||
|
|
||||||
|
if err := os.Chmod(socketPath, 0666); err != nil {
|
||||||
|
slog.Error("Failed to set socket permissions", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Listening Unix socket", "addr", socketPath)
|
||||||
|
|
||||||
|
if err := server.Serve(listener); !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
slog.Error("Failed to start the server", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startHTTPServer(server *http.Server) {
|
func startHTTPServer(server *http.Server) error {
|
||||||
log.Printf("Listening on %s", server.Addr)
|
slog.Info("Listening over HTTP", "addr", server.Addr)
|
||||||
if err := server.ListenAndServe(); err != http.ErrServerClosed {
|
|
||||||
log.Fatalf("Failed to start a server: %v", err)
|
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
slog.Error("Failed to start the server", "err", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package hypview
|
|||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
||||||
@ -66,7 +66,7 @@ func NaviTitle(meta viewutil.Meta, hyphaName string) template.HTML {
|
|||||||
HomeHypha: cfg.HomeHypha,
|
HomeHypha: cfg.HomeHypha,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error("Failed to render NaviTitle properly; using nevertheless", "err", err)
|
||||||
}
|
}
|
||||||
return template.HTML(buf.String())
|
return template.HTML(buf.String())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
package backlinks
|
package backlinks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
"log"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ import (
|
|||||||
func yieldHyphaBacklinks(hyphaName string) <-chan string {
|
func yieldHyphaBacklinks(hyphaName string) <-chan string {
|
||||||
hyphaName = util.CanonicalName(hyphaName)
|
hyphaName = util.CanonicalName(hyphaName)
|
||||||
out := make(chan string)
|
out := make(chan string)
|
||||||
sorted := hyphae2.PathographicSort(out)
|
sorted := hyphae.PathographicSort(out)
|
||||||
go func() {
|
go func() {
|
||||||
backlinks, exists := backlinkIndex[hyphaName]
|
backlinks, exists := backlinkIndex[hyphaName]
|
||||||
if exists {
|
if exists {
|
||||||
@ -43,7 +43,7 @@ var backlinkIndex = make(map[string]linkSet)
|
|||||||
// IndexBacklinks traverses all text hyphae, extracts links from them and forms an initial index. Call it when indexing and reindexing hyphae.
|
// IndexBacklinks traverses all text hyphae, extracts links from them and forms an initial index. Call it when indexing and reindexing hyphae.
|
||||||
func IndexBacklinks() {
|
func IndexBacklinks() {
|
||||||
// It is safe to ignore the mutex, because there is only one worker.
|
// It is safe to ignore the mutex, because there is only one worker.
|
||||||
for h := range hyphae2.FilterHyphaeWithText(hyphae2.YieldExistingHyphae()) {
|
for h := range hyphae.FilterHyphaeWithText(hyphae.YieldExistingHyphae()) {
|
||||||
foundLinks := extractHyphaLinksFromContent(h.CanonicalName(), fetchText(h))
|
foundLinks := extractHyphaLinksFromContent(h.CanonicalName(), fetchText(h))
|
||||||
for _, link := range foundLinks {
|
for _, link := range foundLinks {
|
||||||
if _, exists := backlinkIndex[link]; !exists {
|
if _, exists := backlinkIndex[link]; !exists {
|
||||||
@ -72,7 +72,7 @@ func BacklinksFor(hyphaName string) []string {
|
|||||||
|
|
||||||
func Orphans() []string {
|
func Orphans() []string {
|
||||||
var orphans []string
|
var orphans []string
|
||||||
for h := range hyphae2.YieldExistingHyphae() {
|
for h := range hyphae.YieldExistingHyphae() {
|
||||||
if BacklinksCount(h.CanonicalName()) == 0 {
|
if BacklinksCount(h.CanonicalName()) == 0 {
|
||||||
orphans = append(orphans, h.CanonicalName())
|
orphans = append(orphans, h.CanonicalName())
|
||||||
}
|
}
|
||||||
@ -92,14 +92,14 @@ func toLinkSet(xs []string) linkSet {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchText(h hyphae2.Hypha) string {
|
func fetchText(h hyphae.Hypha) string {
|
||||||
var path string
|
var path string
|
||||||
switch h := h.(type) {
|
switch h := h.(type) {
|
||||||
case *hyphae2.EmptyHypha:
|
case *hyphae.EmptyHypha:
|
||||||
return ""
|
return ""
|
||||||
case *hyphae2.TextualHypha:
|
case *hyphae.TextualHypha:
|
||||||
path = h.TextFilePath()
|
path = h.TextFilePath()
|
||||||
case *hyphae2.MediaHypha:
|
case *hyphae.MediaHypha:
|
||||||
if !h.HasTextFile() {
|
if !h.HasTextFile() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -108,7 +108,7 @@ func fetchText(h hyphae2.Hypha) string {
|
|||||||
|
|
||||||
text, err := os.ReadFile(path)
|
text, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error("Failed to read file", "path", path, "err", err, "hyphaName", h.CanonicalName())
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return string(text)
|
return string(text)
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
package backlinks
|
package backlinks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
||||||
|
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/links"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/links"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdateBacklinksAfterEdit is a creation/editing hook for backlinks index
|
// UpdateBacklinksAfterEdit is a creation/editing hook for backlinks index
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package categories
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
@ -16,12 +16,11 @@ var categoryToHyphae = map[string]*categoryNode{}
|
|||||||
var hyphaToCategories = map[string]*hyphaNode{}
|
var hyphaToCategories = map[string]*hyphaNode{}
|
||||||
|
|
||||||
// Init initializes the category system. Call it after the Structure is initialized. This function might terminate the program in case of a bad mood or filesystem faults.
|
// Init initializes the category system. Call it after the Structure is initialized. This function might terminate the program in case of a bad mood or filesystem faults.
|
||||||
func Init() {
|
func Init() error {
|
||||||
var (
|
record, err := readCategoriesFromDisk()
|
||||||
record, err = readCategoriesFromDisk()
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
slog.Error("Failed to read categories from disk", "err", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cat := range record.Categories {
|
for _, cat := range record.Categories {
|
||||||
@ -46,7 +45,8 @@ func Init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Found", len(categoryToHyphae), "categories")
|
slog.Info("Indexed categories", "n", len(categoryToHyphae))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type categoryNode struct {
|
type categoryNode struct {
|
||||||
@ -123,9 +123,7 @@ func readCategoriesFromDisk() (catFileRecord, error) {
|
|||||||
var fileMutex sync.Mutex
|
var fileMutex sync.Mutex
|
||||||
|
|
||||||
func saveToDisk() {
|
func saveToDisk() {
|
||||||
var (
|
var record catFileRecord
|
||||||
record catFileRecord
|
|
||||||
)
|
|
||||||
for name, node := range categoryToHyphae {
|
for name, node := range categoryToHyphae {
|
||||||
record.Categories = append(record.Categories, catRecord{
|
record.Categories = append(record.Categories, catRecord{
|
||||||
Name: name,
|
Name: name,
|
||||||
@ -134,13 +132,16 @@ func saveToDisk() {
|
|||||||
}
|
}
|
||||||
data, err := json.MarshalIndent(record, "", "\t")
|
data, err := json.MarshalIndent(record, "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err) // Better fail now, than later
|
slog.Error("Failed to marshal categories record", "err", err)
|
||||||
|
os.Exit(1) // Better fail now, than later
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make the data safer somehow?? Back it up before overwriting?
|
// TODO: make the data safer somehow?? Back it up before overwriting?
|
||||||
fileMutex.Lock()
|
fileMutex.Lock()
|
||||||
err = os.WriteFile(files.CategoriesJSON(), data, 0666)
|
err = os.WriteFile(files.CategoriesJSON(), data, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
slog.Error("Failed to write categories.json", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fileMutex.Unlock()
|
fileMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,22 +109,22 @@ type Telegram struct {
|
|||||||
// configuration. Call it sometime during the initialization.
|
// configuration. Call it sometime during the initialization.
|
||||||
func ReadConfigFile(path string) error {
|
func ReadConfigFile(path string) error {
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
WikiName: "Mycorrhiza Wiki",
|
WikiName: "Lyxi's Vault",
|
||||||
NaviTitleIcon: "🍄",
|
NaviTitleIcon: "🦇",
|
||||||
Hyphae: Hyphae{
|
Hyphae: Hyphae{
|
||||||
HomeHypha: "home",
|
HomeHypha: "home",
|
||||||
UserHypha: "u",
|
UserHypha: "u",
|
||||||
HeaderLinksHypha: "",
|
HeaderLinksHypha: "u/alyxbatte/header",
|
||||||
RedirectionCategory: "redirection",
|
RedirectionCategory: "redirection",
|
||||||
},
|
},
|
||||||
Network: Network{
|
Network: Network{
|
||||||
ListenAddr: "127.0.0.1:1737",
|
ListenAddr: "0.0.0.0:1737",
|
||||||
URL: "",
|
URL: "",
|
||||||
},
|
},
|
||||||
Authorization: Authorization{
|
Authorization: Authorization{
|
||||||
UseAuth: false,
|
UseAuth: true,
|
||||||
AllowRegistration: false,
|
AllowRegistration: false,
|
||||||
RegistrationLimit: 0,
|
RegistrationLimit: 1,
|
||||||
Locked: false,
|
Locked: false,
|
||||||
UseWhiteList: false,
|
UseWhiteList: false,
|
||||||
WhiteList: []string{},
|
WhiteList: []string{},
|
||||||
|
|||||||
@ -2,11 +2,12 @@
|
|||||||
package files
|
package files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/web/static"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/web/static"
|
||||||
)
|
)
|
||||||
|
|
||||||
var paths struct {
|
var paths struct {
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
package hyphae
|
package hyphae
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExistingHypha is not EmptyHypha. *MediaHypha and *TextualHypha implement this interface.
|
// ExistingHypha is not EmptyHypha. *MediaHypha and *TextualHypha implement this interface.
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
package hyphae
|
package hyphae
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/mimetype"
|
|
||||||
"log"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/mimetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Index finds all hypha files in the full `path` and saves them to the hypha storage.
|
// Index finds all hypha files in the full `path` and saves them to the hypha storage.
|
||||||
@ -51,7 +51,7 @@ func Index(path string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Println("Indexed", Count(), "hyphae")
|
slog.Info("Indexed hyphae", "n", Count())
|
||||||
}
|
}
|
||||||
|
|
||||||
// indexHelper finds all hypha files in the full `path` and sends them to the
|
// indexHelper finds all hypha files in the full `path` and sends them to the
|
||||||
@ -60,7 +60,8 @@ func Index(path string) {
|
|||||||
func indexHelper(path string, nestLevel uint, ch chan ExistingHypha) {
|
func indexHelper(path string, nestLevel uint, ch chan ExistingHypha) {
|
||||||
nodes, err := os.ReadDir(path)
|
nodes, err := os.ReadDir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to read directory", "path", path, "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
package migration
|
package migration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/files"
|
||||||
|
|
||||||
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
var headingMarkerPath string
|
var headingMarkerPath string
|
||||||
@ -29,7 +31,8 @@ func shouldMigrateHeadings() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("When checking if heading migration is needed:", err.Error())
|
slog.Error("Failed to check if heading migration is needed", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return false
|
return false
|
||||||
@ -42,6 +45,7 @@ func createHeadingMarker() {
|
|||||||
0766,
|
0766,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
slog.Error("Failed to create heading migration marker", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,14 +8,14 @@
|
|||||||
package migration
|
package migration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func genericLineMigrator(
|
func genericLineMigrator(
|
||||||
@ -37,7 +37,8 @@ func genericLineMigrator(
|
|||||||
file, err := os.OpenFile(hypha.TextFilePath(), os.O_RDWR, 0766)
|
file, err := os.OpenFile(hypha.TextFilePath(), os.O_RDWR, 0766)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hop.WithErrAbort(err)
|
hop.WithErrAbort(err)
|
||||||
log.Fatal("Something went wrong when opening ", hypha.TextFilePath(), ": ", err.Error())
|
slog.Error("Failed to open text part file", "path", hypha.TextFilePath(), "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
@ -45,7 +46,7 @@ func genericLineMigrator(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
hop.WithErrAbort(err)
|
hop.WithErrAbort(err)
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
log.Fatal("Something went wrong when reading ", hypha.TextFilePath(), ": ", err.Error())
|
slog.Error("Failed to read text part file", "path", hypha.TextFilePath(), "err", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -59,21 +60,24 @@ func genericLineMigrator(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
hop.WithErrAbort(err)
|
hop.WithErrAbort(err)
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
log.Fatal("Something went wrong when truncating ", hypha.TextFilePath(), ": ", err.Error())
|
slog.Error("Failed to truncate text part file", "path", hypha.TextFilePath(), "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = file.Seek(0, 0)
|
_, err = file.Seek(0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hop.WithErrAbort(err)
|
hop.WithErrAbort(err)
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
log.Fatal("Something went wrong when seeking in ", hypha.TextFilePath(), ": ", err.Error())
|
slog.Error("Failed to seek in text part file", "path", hypha.TextFilePath(), "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = file.WriteString(newText)
|
_, err = file.WriteString(newText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hop.WithErrAbort(err)
|
hop.WithErrAbort(err)
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
log.Fatal("Something went wrong when writing to ", hypha.TextFilePath(), ": ", err.Error())
|
slog.Error("Failed to write to text part file", "path", hypha.TextFilePath(), "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
@ -85,8 +89,8 @@ func genericLineMigrator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hop.WithFiles(mycoFiles...).Apply().HasErrors() {
|
if hop.WithFiles(mycoFiles...).Apply().HasErrors() {
|
||||||
log.Fatal(commitErrorMessage, hop.FirstErrorText())
|
slog.Error(commitErrorMessage + hop.FirstErrorText())
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Migrated", len(mycoFiles), "Mycomarkup documents")
|
slog.Info("Migrated Mycomarkup documents", "n", len(mycoFiles))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
package migration
|
package migration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/files"
|
||||||
|
|
||||||
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rocketMarkerPath string
|
var rocketMarkerPath string
|
||||||
@ -33,7 +35,8 @@ func shouldMigrateRockets() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("When checking if rocket migration is needed:", err.Error())
|
slog.Error("Failed to check if rocket migration is needed", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
return false
|
return false
|
||||||
@ -46,6 +49,7 @@ func createRocketLinkMarker() {
|
|||||||
0766,
|
0766,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
slog.Error("Failed to create rocket link migration marker")
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,20 +40,33 @@ func DataFromFilename(fullPath string) (name string, isText bool, skip bool) {
|
|||||||
|
|
||||||
var mapMime2Ext = map[string]string{
|
var mapMime2Ext = map[string]string{
|
||||||
"application/octet-stream": "bin",
|
"application/octet-stream": "bin",
|
||||||
"image/jpeg": "jpg",
|
|
||||||
"image/gif": "gif",
|
"image/jpeg": "jpg",
|
||||||
"image/png": "png",
|
"image/gif": "gif",
|
||||||
"image/webp": "webp",
|
"image/png": "png",
|
||||||
"image/svg+xml": "svg",
|
"image/webp": "webp",
|
||||||
"image/x-icon": "ico",
|
"image/svg+xml": "svg",
|
||||||
"application/ogg": "ogg",
|
"image/x-icon": "ico",
|
||||||
"video/webm": "webm",
|
|
||||||
"audio/mp3": "mp3",
|
"application/ogg": "ogg",
|
||||||
"video/mp4": "mp4",
|
"video/webm": "webm",
|
||||||
|
"audio/mp3": "mp3",
|
||||||
|
"audio/mpeg": "mp3",
|
||||||
|
"audio/mpeg3": "mp3",
|
||||||
|
"video/mp4": "mp4",
|
||||||
|
"audio/flac": "flac",
|
||||||
|
|
||||||
|
"audio/wav": "wav",
|
||||||
|
"audio/vnd.wav": "wav",
|
||||||
|
"audio/vnd.wave": "wav",
|
||||||
|
"audio/wave": "wav",
|
||||||
|
"audio/x-pn-wav": "wav",
|
||||||
|
"audio/x-wav": "wav",
|
||||||
}
|
}
|
||||||
|
|
||||||
var mapExt2Mime = map[string]string{
|
var mapExt2Mime = map[string]string{
|
||||||
".bin": "application/octet-stream",
|
".bin": "application/octet-stream",
|
||||||
|
|
||||||
".jpg": "image/jpeg",
|
".jpg": "image/jpeg",
|
||||||
".jpeg": "image/jpeg",
|
".jpeg": "image/jpeg",
|
||||||
".gif": "image/gif",
|
".gif": "image/gif",
|
||||||
@ -61,8 +74,12 @@ var mapExt2Mime = map[string]string{
|
|||||||
".webp": "image/webp",
|
".webp": "image/webp",
|
||||||
".svg": "image/svg+xml",
|
".svg": "image/svg+xml",
|
||||||
".ico": "image/x-icon",
|
".ico": "image/x-icon",
|
||||||
|
|
||||||
".ogg": "application/ogg",
|
".ogg": "application/ogg",
|
||||||
".webm": "video/webm",
|
".webm": "video/webm",
|
||||||
".mp3": "audio/mp3",
|
".mp3": "audio/mpeg",
|
||||||
".mp4": "video/mp4",
|
".mp4": "video/mp4",
|
||||||
|
".flac": "audio/flac",
|
||||||
|
|
||||||
|
"wav": "audio/wav",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,23 +2,23 @@ package shroom
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/l18n"
|
"github.com/bouncepaw/mycorrhiza/l18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: get rid of this abomination
|
// TODO: get rid of this abomination
|
||||||
|
|
||||||
func canFactory(
|
func canFactory(
|
||||||
rejectLogger func(hyphae2.Hypha, *user.User, string),
|
rejectLogger func(hyphae.Hypha, *user.User, string),
|
||||||
action string,
|
action string,
|
||||||
dispatcher func(hyphae2.Hypha, *user.User, *l18n.Localizer) (string, string),
|
dispatcher func(hyphae.Hypha, *user.User, *l18n.Localizer) (string, string),
|
||||||
noRightsMsg string,
|
noRightsMsg string,
|
||||||
notExistsMsg string,
|
notExistsMsg string,
|
||||||
mustExist bool,
|
mustExist bool,
|
||||||
) func(*user.User, hyphae2.Hypha, *l18n.Localizer) error {
|
) func(*user.User, hyphae.Hypha, *l18n.Localizer) error {
|
||||||
return func(u *user.User, h hyphae2.Hypha, lc *l18n.Localizer) error {
|
return func(u *user.User, h hyphae.Hypha, lc *l18n.Localizer) error {
|
||||||
if !u.CanProceed(action) {
|
if !u.CanProceed(action) {
|
||||||
rejectLogger(h, u, "no rights")
|
rejectLogger(h, u, "no rights")
|
||||||
return errors.New(noRightsMsg)
|
return errors.New(noRightsMsg)
|
||||||
@ -26,7 +26,7 @@ func canFactory(
|
|||||||
|
|
||||||
if mustExist {
|
if mustExist {
|
||||||
switch h.(type) {
|
switch h.(type) {
|
||||||
case *hyphae2.EmptyHypha:
|
case *hyphae.EmptyHypha:
|
||||||
rejectLogger(h, u, "does not exist")
|
rejectLogger(h, u, "does not exist")
|
||||||
return errors.New(notExistsMsg)
|
return errors.New(notExistsMsg)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,29 +2,30 @@ package shroom
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
||||||
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Delete deletes the hypha and makes a history record about that.
|
// Delete deletes the hypha and makes a history record about that.
|
||||||
func Delete(u *user.User, h hyphae2.ExistingHypha) error {
|
func Delete(u *user.User, h hyphae.ExistingHypha) error {
|
||||||
hop := history.
|
hop := history.
|
||||||
Operation(history.TypeDeleteHypha).
|
Operation(history.TypeDeleteHypha).
|
||||||
WithMsg(fmt.Sprintf("Delete ‘%s’", h.CanonicalName())).
|
WithMsg(fmt.Sprintf("Delete ‘%s’", h.CanonicalName())).
|
||||||
WithUser(u)
|
WithUser(u)
|
||||||
|
|
||||||
originalText, _ := hyphae2.FetchMycomarkupFile(h)
|
originalText, _ := hyphae.FetchMycomarkupFile(h)
|
||||||
switch h := h.(type) {
|
switch h := h.(type) {
|
||||||
case *hyphae2.MediaHypha:
|
case *hyphae.MediaHypha:
|
||||||
if h.HasTextFile() {
|
if h.HasTextFile() {
|
||||||
hop.WithFilesRemoved(h.MediaFilePath(), h.TextFilePath())
|
hop.WithFilesRemoved(h.MediaFilePath(), h.TextFilePath())
|
||||||
} else {
|
} else {
|
||||||
hop.WithFilesRemoved(h.MediaFilePath())
|
hop.WithFilesRemoved(h.MediaFilePath())
|
||||||
}
|
}
|
||||||
case *hyphae2.TextualHypha:
|
case *hyphae.TextualHypha:
|
||||||
hop.WithFilesRemoved(h.TextFilePath())
|
hop.WithFilesRemoved(h.TextFilePath())
|
||||||
}
|
}
|
||||||
if hop.Apply().HasErrors() {
|
if hop.Apply().HasErrors() {
|
||||||
@ -32,6 +33,6 @@ func Delete(u *user.User, h hyphae2.ExistingHypha) error {
|
|||||||
}
|
}
|
||||||
backlinks.UpdateBacklinksAfterDelete(h, originalText)
|
backlinks.UpdateBacklinksAfterDelete(h, originalText)
|
||||||
categories.RemoveHyphaFromAllCategories(h.CanonicalName())
|
categories.RemoveHyphaFromAllCategories(h.CanonicalName())
|
||||||
hyphae2.DeleteHypha(h)
|
hyphae.DeleteHypha(h)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,24 @@
|
|||||||
package shroom
|
package shroom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
||||||
|
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/blocks"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/blocks"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetHeaderLinks initializes header links by reading the configured hypha, if there is any, or resorting to default values.
|
// SetHeaderLinks initializes header links by reading the configured hypha, if there is any, or resorting to default values.
|
||||||
func SetHeaderLinks() {
|
func SetHeaderLinks() {
|
||||||
switch userLinksHypha := hyphae2.ByName(cfg.HeaderLinksHypha).(type) {
|
switch userLinksHypha := hyphae.ByName(cfg.HeaderLinksHypha).(type) {
|
||||||
case *hyphae2.EmptyHypha:
|
case *hyphae.EmptyHypha:
|
||||||
setDefaultHeaderLinks()
|
setDefaultHeaderLinks()
|
||||||
case hyphae2.ExistingHypha:
|
case hyphae.ExistingHypha:
|
||||||
contents, err := os.ReadFile(userLinksHypha.TextFilePath())
|
contents, err := os.ReadFile(userLinksHypha.TextFilePath())
|
||||||
if err != nil || len(contents) == 0 {
|
if err != nil || len(contents) == 0 {
|
||||||
setDefaultHeaderLinks()
|
setDefaultHeaderLinks()
|
||||||
|
|||||||
@ -1,20 +1,36 @@
|
|||||||
package shroom
|
package shroom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func rejectRenameLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
func rejectRenameLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
||||||
log.Printf("Reject rename ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg)
|
slog.Info("Reject rename",
|
||||||
|
"hyphaName", h.CanonicalName(),
|
||||||
|
"username", u.Name,
|
||||||
|
"errmsg", errmsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rejectRemoveMediaLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
func rejectRemoveMediaLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
||||||
log.Printf("Reject remove media ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg)
|
slog.Info("Reject remove media",
|
||||||
|
"hyphaName", h.CanonicalName(),
|
||||||
|
"username", u.Name,
|
||||||
|
"errmsg", errmsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rejectEditLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
func rejectEditLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
||||||
log.Printf("Reject edit ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg)
|
slog.Info("Reject edit",
|
||||||
|
"hyphaName", h.CanonicalName(),
|
||||||
|
"username", u.Name,
|
||||||
|
"errmsg", errmsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rejectUploadMediaLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
func rejectUploadMediaLog(h hyphae.Hypha, u *user.User, errmsg string) {
|
||||||
log.Printf("Reject upload media ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg)
|
slog.Info("Reject upload media",
|
||||||
|
"hyphaName", h.CanonicalName(),
|
||||||
|
"username", u.Name,
|
||||||
|
"errmsg", errmsg)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,36 +3,36 @@ package shroom
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/files"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rename renames the old hypha to the new name and makes a history record about that. Call if and only if the user has the permission to rename.
|
// Rename renames the old hypha to the new name and makes a history record about that. Call if and only if the user has the permission to rename.
|
||||||
func Rename(oldHypha hyphae2.ExistingHypha, newName string, recursive bool, leaveRedirections bool, u *user.User) error {
|
func Rename(oldHypha hyphae.ExistingHypha, newName string, recursive bool, leaveRedirections bool, u *user.User) error {
|
||||||
// * bouncepaw hates this function and related renaming functions
|
// * bouncepaw hates this function and related renaming functions
|
||||||
if newName == "" {
|
if newName == "" {
|
||||||
rejectRenameLog(oldHypha, u, "no new name given")
|
rejectRenameLog(oldHypha, u, "no new name given")
|
||||||
return errors.New("ui.rename_noname_tip")
|
return errors.New("ui.rename_noname_tip")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hyphae2.IsValidName(newName) {
|
if !hyphae.IsValidName(newName) {
|
||||||
rejectRenameLog(oldHypha, u, fmt.Sprintf("new name ‘%s’ invalid", newName))
|
rejectRenameLog(oldHypha, u, fmt.Sprintf("new name ‘%s’ invalid", newName))
|
||||||
return errors.New("ui.rename_badname_tip") // FIXME: There is a bug related to this.
|
return errors.New("ui.rename_badname_tip") // FIXME: There is a bug related to this.
|
||||||
}
|
}
|
||||||
|
|
||||||
switch targetHypha := hyphae2.ByName(newName); targetHypha.(type) {
|
switch targetHypha := hyphae.ByName(newName); targetHypha.(type) {
|
||||||
case hyphae2.ExistingHypha:
|
case hyphae.ExistingHypha:
|
||||||
if targetHypha.CanonicalName() == oldHypha.CanonicalName() {
|
if targetHypha.CanonicalName() == oldHypha.CanonicalName() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ func Rename(oldHypha hyphae2.ExistingHypha, newName string, recursive bool, leav
|
|||||||
oldName = h.CanonicalName()
|
oldName = h.CanonicalName()
|
||||||
newName = re.ReplaceAllString(oldName, newName)
|
newName = re.ReplaceAllString(oldName, newName)
|
||||||
)
|
)
|
||||||
hyphae2.RenameHyphaTo(h, newName, replaceName)
|
hyphae.RenameHyphaTo(h, newName, replaceName)
|
||||||
backlinks.UpdateBacklinksAfterRename(h, oldName)
|
backlinks.UpdateBacklinksAfterRename(h, oldName)
|
||||||
categories.RenameHyphaInAllCategories(oldName, newName)
|
categories.RenameHyphaInAllCategories(oldName, newName)
|
||||||
if leaveRedirections {
|
if leaveRedirections {
|
||||||
@ -104,12 +104,12 @@ const redirectionTemplate = `=> %[1]s | 👁️➡️ %[2]s
|
|||||||
func leaveRedirection(oldName, newName string, hop *history.Op) error {
|
func leaveRedirection(oldName, newName string, hop *history.Op) error {
|
||||||
var (
|
var (
|
||||||
text = fmt.Sprintf(redirectionTemplate, newName, util.BeautifulName(newName))
|
text = fmt.Sprintf(redirectionTemplate, newName, util.BeautifulName(newName))
|
||||||
emptyHypha = hyphae2.ByName(oldName)
|
emptyHypha = hyphae.ByName(oldName)
|
||||||
)
|
)
|
||||||
switch emptyHypha := emptyHypha.(type) {
|
switch emptyHypha := emptyHypha.(type) {
|
||||||
case *hyphae2.EmptyHypha:
|
case *hyphae.EmptyHypha:
|
||||||
h := hyphae2.ExtendEmptyToTextual(emptyHypha, filepath.Join(files.HyphaeDir(), oldName+".myco"))
|
h := hyphae.ExtendEmptyToTextual(emptyHypha, filepath.Join(files.HyphaeDir(), oldName+".myco"))
|
||||||
hyphae2.Insert(h)
|
hyphae.Insert(h)
|
||||||
categories.AddHyphaToCategory(oldName, cfg.RedirectionCategory)
|
categories.AddHyphaToCategory(oldName, cfg.RedirectionCategory)
|
||||||
defer backlinks.UpdateBacklinksAfterEdit(h, "")
|
defer backlinks.UpdateBacklinksAfterEdit(h, "")
|
||||||
return writeTextToDisk(h, []byte(text), hop)
|
return writeTextToDisk(h, []byte(text), hop)
|
||||||
@ -118,15 +118,15 @@ func leaveRedirection(oldName, newName string, hop *history.Op) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func findHyphaeToRename(superhypha hyphae2.ExistingHypha, recursive bool) []hyphae2.ExistingHypha {
|
func findHyphaeToRename(superhypha hyphae.ExistingHypha, recursive bool) []hyphae.ExistingHypha {
|
||||||
hyphaList := []hyphae2.ExistingHypha{superhypha}
|
hyphaList := []hyphae.ExistingHypha{superhypha}
|
||||||
if recursive {
|
if recursive {
|
||||||
hyphaList = append(hyphaList, hyphae2.Subhyphae(superhypha)...)
|
hyphaList = append(hyphaList, hyphae.Subhyphae(superhypha)...)
|
||||||
}
|
}
|
||||||
return hyphaList
|
return hyphaList
|
||||||
}
|
}
|
||||||
|
|
||||||
func renamingPairs(hyphaeToRename []hyphae2.ExistingHypha, replaceName func(string) string) (map[string]string, error) {
|
func renamingPairs(hyphaeToRename []hyphae.ExistingHypha, replaceName func(string) string) (map[string]string, error) {
|
||||||
var (
|
var (
|
||||||
renameMap = make(map[string]string)
|
renameMap = make(map[string]string)
|
||||||
newNames = make([]string, len(hyphaeToRename))
|
newNames = make([]string, len(hyphaeToRename))
|
||||||
@ -138,12 +138,12 @@ func renamingPairs(hyphaeToRename []hyphae2.ExistingHypha, replaceName func(stri
|
|||||||
renameMap[h.TextFilePath()] = replaceName(h.TextFilePath())
|
renameMap[h.TextFilePath()] = replaceName(h.TextFilePath())
|
||||||
}
|
}
|
||||||
switch h := h.(type) {
|
switch h := h.(type) {
|
||||||
case *hyphae2.MediaHypha:
|
case *hyphae.MediaHypha:
|
||||||
renameMap[h.MediaFilePath()] = replaceName(h.MediaFilePath())
|
renameMap[h.MediaFilePath()] = replaceName(h.MediaFilePath())
|
||||||
}
|
}
|
||||||
h.Unlock()
|
h.Unlock()
|
||||||
}
|
}
|
||||||
if firstFailure, ok := hyphae2.AreFreeNames(newNames...); !ok {
|
if firstFailure, ok := hyphae.AreFreeNames(newNames...); !ok {
|
||||||
return nil, errors.New("Hypha " + firstFailure + " already exists")
|
return nil, errors.New("Hypha " + firstFailure + " already exists")
|
||||||
}
|
}
|
||||||
return renameMap, nil
|
return renameMap, nil
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
package shroom
|
package shroom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -2,14 +2,14 @@ package shroom
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoveMedia removes media from the media hypha and makes a history record about that. If it only had media, the hypha will be deleted. If it also had text, the hypha will become textual.
|
// RemoveMedia removes media from the media hypha and makes a history record about that. If it only had media, the hypha will be deleted. If it also had text, the hypha will become textual.
|
||||||
func RemoveMedia(u *user.User, h *hyphae2.MediaHypha) error {
|
func RemoveMedia(u *user.User, h *hyphae.MediaHypha) error {
|
||||||
hop := history.
|
hop := history.
|
||||||
Operation(history.TypeRemoveMedia).
|
Operation(history.TypeRemoveMedia).
|
||||||
WithFilesRemoved(h.MediaFilePath()).
|
WithFilesRemoved(h.MediaFilePath()).
|
||||||
@ -24,9 +24,9 @@ func RemoveMedia(u *user.User, h *hyphae2.MediaHypha) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if h.HasTextFile() {
|
if h.HasTextFile() {
|
||||||
hyphae2.Insert(hyphae2.ShrinkMediaToTextual(h))
|
hyphae.Insert(hyphae.ShrinkMediaToTextual(h))
|
||||||
} else {
|
} else {
|
||||||
hyphae2.DeleteHypha(h)
|
hyphae.DeleteHypha(h)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log/slog"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -201,7 +201,7 @@ func UploadBinary(h hyphae.Hypha, mime string, file multipart.File, u *user.User
|
|||||||
if err := history.Rename(prevFilePath, uploadedFilePath); err != nil {
|
if err := history.Rename(prevFilePath, uploadedFilePath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Move ‘%s’ to ‘%s’\n", prevFilePath, uploadedFilePath)
|
slog.Info("Move file", "from", prevFilePath, "to", uploadedFilePath)
|
||||||
h.SetMediaFilePath(uploadedFilePath)
|
h.SetMediaFilePath(uploadedFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,13 +2,14 @@ package tree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tree returns the subhypha matrix as HTML and names of the next and previous hyphae (or empty strings).
|
// Tree returns the subhypha matrix as HTML and names of the next and previous hyphae (or empty strings).
|
||||||
|
|||||||
@ -3,10 +3,10 @@ package user
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"log/slog"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
"github.com/bouncepaw/mycorrhiza/internal/files"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
@ -32,19 +32,23 @@ func usersFromFile() []*User {
|
|||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to read users.json", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(contents, &users)
|
err = json.Unmarshal(contents, &users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to unmarshal users.json contents", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range users {
|
for _, u := range users {
|
||||||
u.Name = util.CanonicalName(u.Name)
|
u.Name = util.CanonicalName(u.Name)
|
||||||
if u.Source == "" {
|
if u.Source == "" {
|
||||||
u.Source = "local"
|
u.Source = "local"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Println("Found", len(users), "users")
|
slog.Info("Indexed users", "n", len(users))
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,20 +67,22 @@ func readTokensToUsers() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to read tokens.json", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp map[string]string
|
var tmp map[string]string
|
||||||
err = json.Unmarshal(contents, &tmp)
|
err = json.Unmarshal(contents, &tmp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to unmarshal tokens.json contents", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
for token, username := range tmp {
|
for token, username := range tmp {
|
||||||
tokens.Store(token, username)
|
tokens.Store(token, username)
|
||||||
// commenceSession(username, token)
|
// commenceSession(username, token)
|
||||||
}
|
}
|
||||||
log.Println("Found", len(tmp), "active sessions")
|
slog.Info("Indexed active sessions", "n", len(tmp))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveUserDatabase stores current user credentials into JSON file by configured path.
|
// SaveUserDatabase stores current user credentials into JSON file by configured path.
|
||||||
@ -94,13 +100,13 @@ func dumpUserCredentials() error {
|
|||||||
|
|
||||||
blob, err := json.MarshalIndent(userList, "", "\t")
|
blob, err := json.MarshalIndent(userList, "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error("Failed to marshal users.json", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(files.UserCredentialsJSON(), blob, 0666)
|
err = os.WriteFile(files.UserCredentialsJSON(), blob, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error("Failed to write users.json", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,11 +125,11 @@ func dumpTokens() {
|
|||||||
|
|
||||||
blob, err := json.MarshalIndent(tmp, "", "\t")
|
blob, err := json.MarshalIndent(tmp, "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error("Failed to marshal tokens.json", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = os.WriteFile(files.TokensJSON(), blob, 0666)
|
err = os.WriteFile(files.TokensJSON(), blob, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("an error occurred in dumpTokens function:", err)
|
slog.Error("Failed to write tokens.json", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,16 +6,16 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"log/slog"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CanProceed returns `true` if the user in `rq` has enough rights to access `route`.
|
// CanProceed returns `true` if the user in `rq` has enough rights to access `route`.
|
||||||
@ -91,17 +91,17 @@ func LoginDataHTTP(w http.ResponseWriter, username, password string) error {
|
|||||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||||
if !HasUsername(username) {
|
if !HasUsername(username) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
log.Println("Unknown username", username, "was entered")
|
slog.Info("Unknown username entered", "username", username)
|
||||||
return ErrUnknownUsername
|
return ErrUnknownUsername
|
||||||
}
|
}
|
||||||
if !CredentialsOK(username, password) {
|
if !CredentialsOK(username, password) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
log.Println("A wrong password was entered for username", username)
|
slog.Info("Wrong password entered", "username", username)
|
||||||
return ErrWrongPassword
|
return ErrWrongPassword
|
||||||
}
|
}
|
||||||
token, err := AddSession(username)
|
token, err := AddSession(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error("Failed to add session", "username", username, "err", err)
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ func AddSession(username string) (string, error) {
|
|||||||
token, err := util.RandomString(16)
|
token, err := util.RandomString(16)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
commenceSession(username, token)
|
commenceSession(username, token)
|
||||||
log.Println("New token for", username, "is", token)
|
slog.Info("Added session", "username", username)
|
||||||
}
|
}
|
||||||
return token, err
|
return token, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,13 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -4,29 +4,36 @@ package interwiki
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/options"
|
"log/slog"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/files"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
|
||||||
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init() {
|
func Init() error {
|
||||||
var (
|
record, err := readInterwiki()
|
||||||
record, err = readInterwiki()
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
slog.Error("Failed to read interwiki", "err", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, wiki := range record {
|
for _, wiki := range record {
|
||||||
wiki := wiki // This line is required
|
wiki := wiki // This line is required
|
||||||
wiki.canonize()
|
if err := wiki.canonize(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := addEntry(&wiki); err != nil {
|
if err := addEntry(&wiki); err != nil {
|
||||||
log.Fatalln(err.Error())
|
slog.Error("Failed to add interwiki entry", "err", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("Loaded %d interwiki entries\n", len(listOfEntries))
|
|
||||||
|
slog.Info("Indexed interwiki map", "n", len(listOfEntries))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dropEmptyStrings(ss []string) (clean []string) {
|
func dropEmptyStrings(ss []string) (clean []string) {
|
||||||
@ -100,7 +107,6 @@ func deleteEntry(wiki *Wiki) {
|
|||||||
for i, w := range listOfEntries {
|
for i, w := range listOfEntries {
|
||||||
i, w := i, w
|
i, w := i, w
|
||||||
if w.Name == wiki.Name {
|
if w.Name == wiki.Name {
|
||||||
log.Println("It came to delete")
|
|
||||||
// Drop ith element.
|
// Drop ith element.
|
||||||
listOfEntries[i] = listOfEntries[len(listOfEntries)-1]
|
listOfEntries[i] = listOfEntries[len(listOfEntries)-1]
|
||||||
listOfEntries = listOfEntries[:len(listOfEntries)-1]
|
listOfEntries = listOfEntries[:len(listOfEntries)-1]
|
||||||
@ -113,21 +119,22 @@ func deleteEntry(wiki *Wiki) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: There is something clearly wrong with error-returning in this function.
|
||||||
func addEntry(wiki *Wiki) error {
|
func addEntry(wiki *Wiki) error {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
wiki.Aliases = dropEmptyStrings(wiki.Aliases)
|
wiki.Aliases = dropEmptyStrings(wiki.Aliases)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
names = append(wiki.Aliases, wiki.Name)
|
names = append(wiki.Aliases, wiki.Name)
|
||||||
ok, name = areNamesFree(names)
|
ok, name = areNamesFree(names)
|
||||||
)
|
)
|
||||||
if !ok {
|
switch {
|
||||||
log.Printf("There are multiple uses of the same name ‘%s’\n", name)
|
case !ok:
|
||||||
|
slog.Error("There are multiple uses of the same name", "name", name)
|
||||||
return errors.New(name)
|
return errors.New(name)
|
||||||
}
|
case len(names) == 0:
|
||||||
if len(names) == 0 {
|
slog.Error("No names passed for a new interwiki entry")
|
||||||
log.Println("No names passed for a new interwiki entry")
|
|
||||||
// There is something clearly wrong with error-returning in this function.
|
|
||||||
return errors.New("")
|
return errors.New("")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,10 +183,13 @@ func readInterwiki() ([]Wiki, error) {
|
|||||||
func saveInterwikiJson() {
|
func saveInterwikiJson() {
|
||||||
// Trust me, wiki crashing when an admin takes an administrative action totally makes sense.
|
// Trust me, wiki crashing when an admin takes an administrative action totally makes sense.
|
||||||
if data, err := json.MarshalIndent(listOfEntries, "", "\t"); err != nil {
|
if data, err := json.MarshalIndent(listOfEntries, "", "\t"); err != nil {
|
||||||
log.Fatalln(err)
|
slog.Error("Failed to marshal interwiki entries", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
} else if err = os.WriteFile(files.InterwikiJSON(), data, 0666); err != nil {
|
} else if err = os.WriteFile(files.InterwikiJSON(), data, 0666); err != nil {
|
||||||
log.Fatalln(err)
|
slog.Error("Failed to write interwiki.json", "err", err)
|
||||||
} else {
|
os.Exit(1)
|
||||||
log.Println("Saved interwiki.json")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slog.Info("Saved interwiki.json")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,13 @@ package interwiki
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
"log/slog"
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -63,19 +65,24 @@ func handlerModifyEntry(w http.ResponseWriter, rq *http.Request) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if oldData, ok = entriesByName[name]; !ok {
|
if oldData, ok = entriesByName[name]; !ok {
|
||||||
log.Printf("Could not modify interwiki entry ‘%s’ because it does not exist", name)
|
slog.Info("Could not modify entry",
|
||||||
|
"name", name,
|
||||||
|
"reason", "does not exist")
|
||||||
viewutil.HandlerNotFound(w, rq)
|
viewutil.HandlerNotFound(w, rq)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := replaceEntry(oldData, &newData); err != nil {
|
if err := replaceEntry(oldData, &newData); err != nil {
|
||||||
log.Printf("Could not modify interwiki entry ‘%s’ because one of the proposed aliases/name is taken\n", name)
|
slog.Info("Could not modify entry",
|
||||||
|
"name", name,
|
||||||
|
"reason", "one of the proposed aliases or the name is taken",
|
||||||
|
"err", err)
|
||||||
viewNameTaken(viewutil.MetaFrom(w, rq), oldData, err.Error(), "modify-entry/"+name)
|
viewNameTaken(viewutil.MetaFrom(w, rq), oldData, err.Error(), "modify-entry/"+name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
saveInterwikiJson()
|
saveInterwikiJson()
|
||||||
log.Printf("Modified interwiki entry ‘%s’\n", name)
|
slog.Info("Modified entry", "name", name)
|
||||||
http.Redirect(w, rq, "/interwiki", http.StatusSeeOther)
|
http.Redirect(w, rq, "/interwiki", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
package interwiki
|
package interwiki
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WikiEngine is an enumeration of supported interwiki targets.
|
// WikiEngine is an enumeration of supported interwiki targets.
|
||||||
@ -47,14 +49,20 @@ type Wiki struct {
|
|||||||
Engine WikiEngine `json:"engine"`
|
Engine WikiEngine `json:"engine"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wiki) canonize() {
|
func (w *Wiki) canonize() error {
|
||||||
switch {
|
switch {
|
||||||
case w.Name == "":
|
case w.Name == "":
|
||||||
log.Fatalln("Cannot have a wiki in the interwiki map with no name")
|
slog.Error("A site in the interwiki map has no name")
|
||||||
|
return errors.New("site with no name")
|
||||||
case w.URL == "":
|
case w.URL == "":
|
||||||
log.Fatalf("Wiki ‘%s’ has no URL\n", w.Name)
|
slog.Error("Site in the interwiki map has no URL", "name", w.Name)
|
||||||
|
return errors.New("site with no URL")
|
||||||
case !w.Engine.Valid():
|
case !w.Engine.Valid():
|
||||||
log.Fatalf("Unknown engine ‘%s’ for wiki ‘%s’\n", w.Engine, w.Name)
|
slog.Error("Site in the interwiki map has an unknown engine",
|
||||||
|
"siteName", w.Name,
|
||||||
|
"engine", w.Engine,
|
||||||
|
)
|
||||||
|
return errors.New("unknown engine")
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Name = util.CanonicalName(w.Name)
|
w.Name = util.CanonicalName(w.Name)
|
||||||
@ -83,4 +91,6 @@ func (w *Wiki) canonize() {
|
|||||||
w.ImgSrcFormat = fmt.Sprintf("%s/{NAME}", w.URL)
|
w.ImgSrcFormat = fmt.Sprintf("%s/{NAME}", w.URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -78,7 +78,7 @@ func init() {
|
|||||||
|
|
||||||
var strings map[string]string
|
var strings map[string]string
|
||||||
if err := json.Unmarshal(contents, &strings); err != nil {
|
if err := json.Unmarshal(contents, &strings); err != nil {
|
||||||
log.Fatalf("error while parsing %s: %v", path, err)
|
slog.Error("Failed to unmarshal localization file", "path", path, "err", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range strings {
|
for key, value := range strings {
|
||||||
|
|||||||
45
main.go
45
main.go
@ -1,16 +1,13 @@
|
|||||||
// Command mycorrhiza is a program that runs a mycorrhiza wiki.
|
// Command mycorrhiza is a program that runs a mycorrhiza wiki.
|
||||||
//
|
|
||||||
//go:generate go run github.com/valyala/quicktemplate/qtc -dir=history
|
|
||||||
//go:generate go run github.com/valyala/quicktemplate/qtc -dir=mycoopts
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
"log/slog"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/files"
|
"github.com/bouncepaw/mycorrhiza/internal/files"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
@ -25,42 +22,58 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
parseCliArgs()
|
if err := parseCliArgs(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
if err := files.PrepareWikiRoot(); err != nil {
|
if err := files.PrepareWikiRoot(); err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to prepare wiki root", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cfg.ReadConfigFile(files.ConfigPath()); err != nil {
|
if err := cfg.ReadConfigFile(files.ConfigPath()); err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to read config", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Running Mycorrhiza Wiki", version.Short)
|
|
||||||
if err := os.Chdir(files.HyphaeDir()); err != nil {
|
if err := os.Chdir(files.HyphaeDir()); err != nil {
|
||||||
log.Fatal(err)
|
slog.Error("Failed to chdir to hyphae dir",
|
||||||
|
"err", err, "hyphaeDir", files.HyphaeDir())
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
log.Println("Wiki directory is", cfg.WikiDir)
|
slog.Info("Running Mycorrhiza Wiki",
|
||||||
|
"version", version.Short, "wikiDir", cfg.WikiDir)
|
||||||
|
|
||||||
// Init the subsystems:
|
// Init the subsystems:
|
||||||
|
// TODO: keep all crashes in main rather than somewhere there
|
||||||
viewutil.Init()
|
viewutil.Init()
|
||||||
hyphae.Index(files.HyphaeDir())
|
hyphae.Index(files.HyphaeDir())
|
||||||
backlinks.IndexBacklinks()
|
backlinks.IndexBacklinks()
|
||||||
go backlinks.RunBacklinksConveyor()
|
go backlinks.RunBacklinksConveyor()
|
||||||
user.InitUserDatabase()
|
user.InitUserDatabase()
|
||||||
history.Start()
|
if err := history.Start(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
history.InitGitRepo()
|
history.InitGitRepo()
|
||||||
migration.MigrateRocketsMaybe()
|
migration.MigrateRocketsMaybe()
|
||||||
migration.MigrateHeadingsMaybe()
|
migration.MigrateHeadingsMaybe()
|
||||||
shroom.SetHeaderLinks()
|
shroom.SetHeaderLinks()
|
||||||
categories.Init()
|
if err := categories.Init(); err != nil {
|
||||||
interwiki.Init()
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := interwiki.Init(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// Static files:
|
// Static files:
|
||||||
static.InitFS(files.StaticFiles())
|
static.InitFS(files.StaticFiles())
|
||||||
|
|
||||||
if !user.HasAnyAdmins() {
|
if !user.HasAnyAdmins() {
|
||||||
log.Println("Your wiki has no admin yet. Run Mycorrhiza with -create-admin <username> option to create an admin.")
|
slog.Error("Your wiki has no admin yet. Run Mycorrhiza with -create-admin <username> option to create an admin.")
|
||||||
}
|
}
|
||||||
|
|
||||||
serveHTTP(web.Handler())
|
err := serveHTTP(web.Handler())
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
package misc
|
package misc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template" // sic! TODO: make it html/template after the template library migration
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/version"
|
"github.com/bouncepaw/mycorrhiza/internal/version"
|
||||||
"github.com/bouncepaw/mycorrhiza/l18n"
|
"github.com/bouncepaw/mycorrhiza/l18n"
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
"text/template" // sic!
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type L10nEntry struct {
|
type L10nEntry struct {
|
||||||
@ -95,7 +97,8 @@ func AboutHTML(lc *l18n.Localizer) string {
|
|||||||
}
|
}
|
||||||
temp, err := template.New("about wiki").Funcs(template.FuncMap{"get": get}).Parse(aboutTemplateString)
|
temp, err := template.New("about wiki").Funcs(template.FuncMap{"get": get}).Parse(aboutTemplateString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
slog.Error("Failed to parse About template", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
data := aboutData
|
data := aboutData
|
||||||
data.Version = version.Short
|
data.Version = version.Short
|
||||||
@ -112,7 +115,8 @@ func AboutHTML(lc *l18n.Localizer) string {
|
|||||||
var out strings.Builder
|
var out strings.Builder
|
||||||
err = temp.Execute(&out, data)
|
err = temp.Execute(&out, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error("Failed to execute About template", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package misc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log/slog"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -73,11 +73,11 @@ func handlerReindex(w http.ResponseWriter, rq *http.Request) {
|
|||||||
if ok := user.CanProceed(rq, "reindex"); !ok {
|
if ok := user.CanProceed(rq, "reindex"); !ok {
|
||||||
var lc = l18n.FromRequest(rq)
|
var lc = l18n.FromRequest(rq)
|
||||||
viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.reindex_no_rights"))
|
viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.reindex_no_rights"))
|
||||||
log.Println("Rejected", rq.URL)
|
slog.Info("No rights to reindex")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hyphae.ResetCount()
|
hyphae.ResetCount()
|
||||||
log.Println("Reindexing hyphae in", files.HyphaeDir())
|
slog.Info("Reindexing hyphae", "hyphaeDir", files.HyphaeDir())
|
||||||
hyphae.Index(files.HyphaeDir())
|
hyphae.Index(files.HyphaeDir())
|
||||||
backlinks.IndexBacklinks()
|
backlinks.IndexBacklinks()
|
||||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||||
@ -89,9 +89,10 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
|
|||||||
if ok := user.CanProceed(rq, "update-header-links"); !ok {
|
if ok := user.CanProceed(rq, "update-header-links"); !ok {
|
||||||
var lc = l18n.FromRequest(rq)
|
var lc = l18n.FromRequest(rq)
|
||||||
viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.header_no_rights"))
|
viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.header_no_rights"))
|
||||||
log.Println("Rejected", rq.URL)
|
slog.Info("No rights to update header links")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
slog.Info("Updated header links")
|
||||||
shroom.SetHeaderLinks()
|
shroom.SetHeaderLinks()
|
||||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
@ -133,7 +134,7 @@ func handlerAbout(w http.ResponseWriter, rq *http.Request) {
|
|||||||
map[string]string{},
|
map[string]string{},
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error("Failed to write About template", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +149,7 @@ func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
|||||||
}
|
}
|
||||||
_, err = io.Copy(w, file)
|
_, err = io.Copy(w, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error("Failed to write stylesheet; proceeding anyway", "err", err)
|
||||||
}
|
}
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
}
|
}
|
||||||
@ -163,7 +164,7 @@ func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) {
|
|||||||
}
|
}
|
||||||
_, err = io.Copy(w, file)
|
_, err = io.Copy(w, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println()
|
slog.Error("Failed to write robots.txt; proceeding anyway", "err", err)
|
||||||
}
|
}
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package misc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,11 +2,17 @@ package mycoopts
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/options"
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/interwiki"
|
"github.com/bouncepaw/mycorrhiza/interwiki"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/l18n"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
|
||||||
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MarkupOptions(hyphaName string) options.Options {
|
func MarkupOptions(hyphaName string) options.Options {
|
||||||
@ -52,3 +58,58 @@ func MarkupOptions(hyphaName string) options.Options {
|
|||||||
ImgSrcFormatForInterwikiPrefix: interwiki.ImgSrcFormatFor,
|
ImgSrcFormatForInterwikiPrefix: interwiki.ImgSrcFormatFor,
|
||||||
}.FillTheRest()
|
}.FillTheRest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mediaRaw(h *hyphae.MediaHypha) string {
|
||||||
|
return Media(h, l18n.New("en", "en"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Media(h *hyphae.MediaHypha, lc *l18n.Localizer) string {
|
||||||
|
name := html.EscapeString(h.CanonicalName())
|
||||||
|
|
||||||
|
switch filepath.Ext(h.MediaFilePath()) {
|
||||||
|
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`<div class="binary-container binary-container_with-img">
|
||||||
|
<a href="/binary/%s"><img src="/binary/%s"/></a>
|
||||||
|
</div>`,
|
||||||
|
name, name,
|
||||||
|
)
|
||||||
|
|
||||||
|
case ".ogg", ".webm", ".mp4":
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`<div class="binary-container binary-container_with-video">
|
||||||
|
<video controls>
|
||||||
|
<source src="/binary/%s"/>
|
||||||
|
<p>%s <a href="/binary/%s">%s</a></p>
|
||||||
|
</video>
|
||||||
|
</div>`,
|
||||||
|
name,
|
||||||
|
html.EscapeString(lc.Get("ui.media_novideo")),
|
||||||
|
name,
|
||||||
|
html.EscapeString(lc.Get("ui.media_novideo_link")),
|
||||||
|
)
|
||||||
|
|
||||||
|
case ".mp3", ".wav", ".flac":
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`<div class="binary-container binary-container_with-audio">
|
||||||
|
<audio controls>
|
||||||
|
<source src="/binary/%s"/>
|
||||||
|
<p>%s <a href="/binary/%s">%s</a></p>
|
||||||
|
</audio>
|
||||||
|
</div>`,
|
||||||
|
name,
|
||||||
|
html.EscapeString(lc.Get("ui.media_noaudio")),
|
||||||
|
name,
|
||||||
|
html.EscapeString(lc.Get("ui.media_noaudio_link")),
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`<div class="binary-container binary-container_with-nothing">
|
||||||
|
<p><a href="/binary/%s">%s</a></p>
|
||||||
|
</div>`,
|
||||||
|
name,
|
||||||
|
html.EscapeString(lc.Get("ui.media_download")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,37 +0,0 @@
|
|||||||
{% import "path/filepath" %}
|
|
||||||
|
|
||||||
{% import "github.com/bouncepaw/mycorrhiza/internal/hyphae" %}
|
|
||||||
{% import "github.com/bouncepaw/mycorrhiza/l18n" %}
|
|
||||||
|
|
||||||
{% func mediaRaw(h *hyphae.MediaHypha) %}{%= Media(h, l18n.New("en", "en")) %}{% endfunc %}
|
|
||||||
|
|
||||||
{% func Media(h *hyphae.MediaHypha, lc *l18n.Localizer) %}
|
|
||||||
{% switch filepath.Ext(h.MediaFilePath()) %}
|
|
||||||
|
|
||||||
{% case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico" %}
|
|
||||||
<div class="binary-container binary-container_with-img">
|
|
||||||
<a href="/binary/{%s= h.CanonicalName() %}"><img src="/binary/{%s= h.CanonicalName() %}"/></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% case ".ogg", ".webm", ".mp4" %}
|
|
||||||
<div class="binary-container binary-container_with-video">
|
|
||||||
<video controls>
|
|
||||||
<source src="/binary/{%s= h.CanonicalName() %}"/>
|
|
||||||
<p>{%s lc.Get("ui.media_novideo") %} <a href="/binary/{%s= h.CanonicalName() %}">{%s lc.Get("ui.media_novideo_link") %}</a></p>
|
|
||||||
</video>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% case ".mp3" %}
|
|
||||||
<div class="binary-container binary-container_with-audio">
|
|
||||||
<audio controls>
|
|
||||||
<source src="/binary/{%s= h.CanonicalName() %}"/>
|
|
||||||
<p>{%s lc.Get("ui.media_noaudio") %} <a href="/binary/{%s= h.CanonicalName() %}">{%s lc.Get("ui.media_noaudio_link") %}</a></p>
|
|
||||||
</audio>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% default %}
|
|
||||||
<div class="binary-container binary-container_with-nothing">
|
|
||||||
<p><a href="/binary/{%s= h.CanonicalName() %}">{%s lc.Get("ui.media_download") %}</a></p>
|
|
||||||
</div>
|
|
||||||
{% endswitch %}
|
|
||||||
{% endfunc %}
|
|
||||||
@ -1,190 +0,0 @@
|
|||||||
// Code generated by qtc from "view.qtpl". DO NOT EDIT.
|
|
||||||
// See https://github.com/valyala/quicktemplate for details.
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:1
|
|
||||||
package mycoopts
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:1
|
|
||||||
import "path/filepath"
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:3
|
|
||||||
import "github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:4
|
|
||||||
import "github.com/bouncepaw/mycorrhiza/l18n"
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
import (
|
|
||||||
qtio422016 "io"
|
|
||||||
|
|
||||||
qt422016 "github.com/valyala/quicktemplate"
|
|
||||||
)
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
var (
|
|
||||||
_ = qtio422016.Copy
|
|
||||||
_ = qt422016.AcquireByteBuffer
|
|
||||||
)
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
func streammediaRaw(qw422016 *qt422016.Writer, h *hyphae.MediaHypha) {
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
StreamMedia(qw422016, h, l18n.New("en", "en"))
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
}
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
func writemediaRaw(qq422016 qtio422016.Writer, h *hyphae.MediaHypha) {
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
streammediaRaw(qw422016, h)
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
qt422016.ReleaseWriter(qw422016)
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
}
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
func mediaRaw(h *hyphae.MediaHypha) string {
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
writemediaRaw(qb422016, h)
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
qs422016 := string(qb422016.B)
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
return qs422016
|
|
||||||
//line mycoopts/view.qtpl:6
|
|
||||||
}
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:8
|
|
||||||
func StreamMedia(qw422016 *qt422016.Writer, h *hyphae.MediaHypha, lc *l18n.Localizer) {
|
|
||||||
//line mycoopts/view.qtpl:8
|
|
||||||
qw422016.N().S(`
|
|
||||||
`)
|
|
||||||
//line mycoopts/view.qtpl:9
|
|
||||||
switch filepath.Ext(h.MediaFilePath()) {
|
|
||||||
//line mycoopts/view.qtpl:11
|
|
||||||
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
|
|
||||||
//line mycoopts/view.qtpl:11
|
|
||||||
qw422016.N().S(`
|
|
||||||
<div class="binary-container binary-container_with-img">
|
|
||||||
<a href="/binary/`)
|
|
||||||
//line mycoopts/view.qtpl:13
|
|
||||||
qw422016.N().S(h.CanonicalName())
|
|
||||||
//line mycoopts/view.qtpl:13
|
|
||||||
qw422016.N().S(`"><img src="/binary/`)
|
|
||||||
//line mycoopts/view.qtpl:13
|
|
||||||
qw422016.N().S(h.CanonicalName())
|
|
||||||
//line mycoopts/view.qtpl:13
|
|
||||||
qw422016.N().S(`"/></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`)
|
|
||||||
//line mycoopts/view.qtpl:16
|
|
||||||
case ".ogg", ".webm", ".mp4":
|
|
||||||
//line mycoopts/view.qtpl:16
|
|
||||||
qw422016.N().S(`
|
|
||||||
<div class="binary-container binary-container_with-video">
|
|
||||||
<video controls>
|
|
||||||
<source src="/binary/`)
|
|
||||||
//line mycoopts/view.qtpl:19
|
|
||||||
qw422016.N().S(h.CanonicalName())
|
|
||||||
//line mycoopts/view.qtpl:19
|
|
||||||
qw422016.N().S(`"/>
|
|
||||||
<p>`)
|
|
||||||
//line mycoopts/view.qtpl:20
|
|
||||||
qw422016.E().S(lc.Get("ui.media_novideo"))
|
|
||||||
//line mycoopts/view.qtpl:20
|
|
||||||
qw422016.N().S(` <a href="/binary/`)
|
|
||||||
//line mycoopts/view.qtpl:20
|
|
||||||
qw422016.N().S(h.CanonicalName())
|
|
||||||
//line mycoopts/view.qtpl:20
|
|
||||||
qw422016.N().S(`">`)
|
|
||||||
//line mycoopts/view.qtpl:20
|
|
||||||
qw422016.E().S(lc.Get("ui.media_novideo_link"))
|
|
||||||
//line mycoopts/view.qtpl:20
|
|
||||||
qw422016.N().S(`</a></p>
|
|
||||||
</video>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`)
|
|
||||||
//line mycoopts/view.qtpl:24
|
|
||||||
case ".mp3":
|
|
||||||
//line mycoopts/view.qtpl:24
|
|
||||||
qw422016.N().S(`
|
|
||||||
<div class="binary-container binary-container_with-audio">
|
|
||||||
<audio controls>
|
|
||||||
<source src="/binary/`)
|
|
||||||
//line mycoopts/view.qtpl:27
|
|
||||||
qw422016.N().S(h.CanonicalName())
|
|
||||||
//line mycoopts/view.qtpl:27
|
|
||||||
qw422016.N().S(`"/>
|
|
||||||
<p>`)
|
|
||||||
//line mycoopts/view.qtpl:28
|
|
||||||
qw422016.E().S(lc.Get("ui.media_noaudio"))
|
|
||||||
//line mycoopts/view.qtpl:28
|
|
||||||
qw422016.N().S(` <a href="/binary/`)
|
|
||||||
//line mycoopts/view.qtpl:28
|
|
||||||
qw422016.N().S(h.CanonicalName())
|
|
||||||
//line mycoopts/view.qtpl:28
|
|
||||||
qw422016.N().S(`">`)
|
|
||||||
//line mycoopts/view.qtpl:28
|
|
||||||
qw422016.E().S(lc.Get("ui.media_noaudio_link"))
|
|
||||||
//line mycoopts/view.qtpl:28
|
|
||||||
qw422016.N().S(`</a></p>
|
|
||||||
</audio>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`)
|
|
||||||
//line mycoopts/view.qtpl:32
|
|
||||||
default:
|
|
||||||
//line mycoopts/view.qtpl:32
|
|
||||||
qw422016.N().S(`
|
|
||||||
<div class="binary-container binary-container_with-nothing">
|
|
||||||
<p><a href="/binary/`)
|
|
||||||
//line mycoopts/view.qtpl:34
|
|
||||||
qw422016.N().S(h.CanonicalName())
|
|
||||||
//line mycoopts/view.qtpl:34
|
|
||||||
qw422016.N().S(`">`)
|
|
||||||
//line mycoopts/view.qtpl:34
|
|
||||||
qw422016.E().S(lc.Get("ui.media_download"))
|
|
||||||
//line mycoopts/view.qtpl:34
|
|
||||||
qw422016.N().S(`</a></p>
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
//line mycoopts/view.qtpl:36
|
|
||||||
}
|
|
||||||
//line mycoopts/view.qtpl:36
|
|
||||||
qw422016.N().S(`
|
|
||||||
`)
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
}
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
func WriteMedia(qq422016 qtio422016.Writer, h *hyphae.MediaHypha, lc *l18n.Localizer) {
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
StreamMedia(qw422016, h, lc)
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
qt422016.ReleaseWriter(qw422016)
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
}
|
|
||||||
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
func Media(h *hyphae.MediaHypha, lc *l18n.Localizer) string {
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
WriteMedia(qb422016, h, lc)
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
qs422016 := string(qb422016.B)
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
return qs422016
|
|
||||||
//line mycoopts/view.qtpl:37
|
|
||||||
}
|
|
||||||
@ -3,7 +3,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"log"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ func HyphaNameFromRq(rq *http.Request, actions ...string) string {
|
|||||||
return CanonicalName(strings.TrimPrefix(p, "/"+action+"/"))
|
return CanonicalName(strings.TrimPrefix(p, "/"+action+"/"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Println("HyphaNameFromRq: this request is invalid, fall back to home hypha")
|
slog.Info("HyphaNameFromRq: this request is invalid, fall back to home hypha")
|
||||||
return cfg.HomeHypha
|
return cfg.HomeHypha
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log/slog"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -115,7 +115,7 @@ func handlerAdmin(w http.ResponseWriter, rq *http.Request) {
|
|||||||
// handlerAdminShutdown kills the wiki.
|
// handlerAdminShutdown kills the wiki.
|
||||||
func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) {
|
func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) {
|
||||||
if user.CanProceed(rq, "admin/shutdown") {
|
if user.CanProceed(rq, "admin/shutdown") {
|
||||||
log.Println("An admin commanded the wiki to shutdown")
|
slog.Info("An admin commanded the wiki to shutdown")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,7 +162,7 @@ func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) {
|
|||||||
u.Group = newGroup
|
u.Group = newGroup
|
||||||
if err := user.SaveUserDatabase(); err != nil {
|
if err := user.SaveUserDatabase(); err != nil {
|
||||||
u.Group = oldGroup
|
u.Group = oldGroup
|
||||||
log.Println(err)
|
slog.Info("Failed to save user database", "err", err)
|
||||||
f = f.WithError(err)
|
f = f.WithError(err)
|
||||||
} else {
|
} else {
|
||||||
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
|
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
|
||||||
@ -241,7 +241,7 @@ func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) {
|
|||||||
if !f.HasError() {
|
if !f.HasError() {
|
||||||
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
|
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
|
||||||
} else {
|
} else {
|
||||||
log.Println(f.Error())
|
slog.Info("Failed to delete user", "err", f.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
web/cats.go
11
web/cats.go
@ -1,14 +1,13 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
||||||
@ -66,7 +65,7 @@ func handlerCategory(w http.ResponseWriter, rq *http.Request) {
|
|||||||
// There is one hypha from the hypha field. Then there are n hyphae in fields prefixed by _. It seems like I have to do it myself. Compare with PHP which handles it for you. I hope I am doing this wrong.
|
// There is one hypha from the hypha field. Then there are n hyphae in fields prefixed by _. It seems like I have to do it myself. Compare with PHP which handles it for you. I hope I am doing this wrong.
|
||||||
func hyphaeFromRequest(rq *http.Request) (canonicalNames []string) {
|
func hyphaeFromRequest(rq *http.Request) (canonicalNames []string) {
|
||||||
if err := rq.ParseForm(); err != nil {
|
if err := rq.ParseForm(); err != nil {
|
||||||
log.Println(err)
|
slog.Info("Failed to parse form", "err", err)
|
||||||
}
|
}
|
||||||
if hyphaName := util.CanonicalName(rq.PostFormValue("hypha")); hyphaName != "" {
|
if hyphaName := util.CanonicalName(rq.PostFormValue("hypha")); hyphaName != "" {
|
||||||
canonicalNames = append(canonicalNames, hyphaName)
|
canonicalNames = append(canonicalNames, hyphaName)
|
||||||
@ -100,7 +99,8 @@ func handlerRemoveFromCategory(w http.ResponseWriter, rq *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(hyphaNames) == 0 || catName == "" {
|
if len(hyphaNames) == 0 || catName == "" {
|
||||||
log.Printf("%s passed no data for removal of hyphae from a category\n", u.Name)
|
slog.Info("No data for removal of hyphae from category passed",
|
||||||
|
"username", u.Name, "catName", catName)
|
||||||
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -108,7 +108,8 @@ func handlerRemoveFromCategory(w http.ResponseWriter, rq *http.Request) {
|
|||||||
// TODO: Make it more effective.
|
// TODO: Make it more effective.
|
||||||
categories.RemoveHyphaFromCategory(hyphaName, catName)
|
categories.RemoveHyphaFromCategory(hyphaName, catName)
|
||||||
}
|
}
|
||||||
log.Printf("%s removed %q from category %s\n", u.Name, hyphaNames, catName)
|
slog.Info("Remove hyphae from category",
|
||||||
|
"username", u.Name, "catName", catName, "hyphaNames", hyphaNames)
|
||||||
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
http.Redirect(w, rq, redirectTo, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,22 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
"html/template"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hypview"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
"github.com/bouncepaw/mycorrhiza/internal/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/shroom"
|
"github.com/bouncepaw/mycorrhiza/internal/shroom"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
"html/template"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/hypview"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/l18n"
|
"github.com/bouncepaw/mycorrhiza/l18n"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
||||||
|
|
||||||
|
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
||||||
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initMutators(r *mux.Router) {
|
func initMutators(r *mux.Router) {
|
||||||
@ -64,14 +63,16 @@ func handlerDelete(w http.ResponseWriter, rq *http.Request) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if !u.CanProceed("delete") {
|
if !u.CanProceed("delete") {
|
||||||
log.Printf("%s has no rights to delete ‘%s’\n", u.Name, h.CanonicalName())
|
slog.Info("No rights to delete hypha",
|
||||||
|
"username", u.Name, "hyphaName", h.CanonicalName())
|
||||||
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "No rights")
|
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "No rights")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch h.(type) {
|
switch h.(type) {
|
||||||
case *hyphae.EmptyHypha:
|
case *hyphae.EmptyHypha:
|
||||||
log.Printf("%s tries to delete empty hypha ‘%s’\n", u.Name, h.CanonicalName())
|
slog.Info("Trying to delete empty hyphae",
|
||||||
|
"username", u.Name, "hyphaName", h.CanonicalName())
|
||||||
// TODO: localize
|
// TODO: localize
|
||||||
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "Cannot delete an empty hypha")
|
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "Cannot delete an empty hypha")
|
||||||
return
|
return
|
||||||
@ -87,7 +88,7 @@ func handlerDelete(w http.ResponseWriter, rq *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := shroom.Delete(u, h.(hyphae.ExistingHypha)); err != nil {
|
if err := shroom.Delete(u, h.(hyphae.ExistingHypha)); err != nil {
|
||||||
log.Println(err)
|
slog.Error("Failed to delete hypha", "err", err)
|
||||||
viewutil.HttpErr(meta, http.StatusInternalServerError, h.CanonicalName(), err.Error())
|
viewutil.HttpErr(meta, http.StatusInternalServerError, h.CanonicalName(), err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -105,13 +106,15 @@ func handlerRename(w http.ResponseWriter, rq *http.Request) {
|
|||||||
|
|
||||||
switch h.(type) {
|
switch h.(type) {
|
||||||
case *hyphae.EmptyHypha:
|
case *hyphae.EmptyHypha:
|
||||||
log.Printf("%s tries to rename empty hypha ‘%s’", u.Name, h.CanonicalName())
|
slog.Info("Trying to rename empty hypha",
|
||||||
|
"username", u.Name, "hyphaName", h.CanonicalName())
|
||||||
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "Cannot rename an empty hypha") // TODO: localize
|
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "Cannot rename an empty hypha") // TODO: localize
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !u.CanProceed("rename") {
|
if !u.CanProceed("rename") {
|
||||||
log.Printf("%s has no rights to rename ‘%s’\n", u.Name, h.CanonicalName())
|
slog.Info("No rights to rename hypha",
|
||||||
|
"username", u.Name, "hyphaName", h.CanonicalName())
|
||||||
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "No rights")
|
viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "No rights")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -129,7 +132,8 @@ func handlerRename(w http.ResponseWriter, rq *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := shroom.Rename(oldHypha, newName, recursive, leaveRedirections, u); err != nil {
|
if err := shroom.Rename(oldHypha, newName, recursive, leaveRedirections, u); err != nil {
|
||||||
log.Printf("%s tries to rename ‘%s’: %s", u.Name, oldHypha.CanonicalName(), err.Error())
|
slog.Error("Failed to rename hypha",
|
||||||
|
"err", err, "username", u.Name, "hyphaName", oldHypha.CanonicalName())
|
||||||
viewutil.HttpErr(meta, http.StatusForbidden, oldHypha.CanonicalName(), lc.Get(err.Error())) // TODO: localize
|
viewutil.HttpErr(meta, http.StatusForbidden, oldHypha.CanonicalName(), lc.Get(err.Error())) // TODO: localize
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -163,7 +167,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
|||||||
default:
|
default:
|
||||||
content, err = hyphae.FetchMycomarkupFile(h)
|
content, err = hyphae.FetchMycomarkupFile(h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Error("Failed to fetch Mycomarkup file", "err", err)
|
||||||
viewutil.HttpErr(meta, http.StatusInternalServerError, hyphaName, lc.Get("ui.error_text_fetch"))
|
viewutil.HttpErr(meta, http.StatusInternalServerError, hyphaName, lc.Get("ui.error_text_fetch"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,12 @@ package newtmpl
|
|||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
||||||
"html/template"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed *.html
|
//go:embed *.html
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/web/newtmpl"
|
"github.com/bouncepaw/mycorrhiza/web/newtmpl"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,12 +2,13 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) {
|
func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
|||||||
@ -2,7 +2,17 @@ package web
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
"github.com/bouncepaw/mycorrhiza/hypview"
|
"github.com/bouncepaw/mycorrhiza/hypview"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
"github.com/bouncepaw/mycorrhiza/internal/categories"
|
||||||
@ -12,26 +22,15 @@ import (
|
|||||||
"github.com/bouncepaw/mycorrhiza/internal/mimetype"
|
"github.com/bouncepaw/mycorrhiza/internal/mimetype"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/tree"
|
"github.com/bouncepaw/mycorrhiza/internal/tree"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/l18n"
|
||||||
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
"github.com/bouncepaw/mycorrhiza/mycoopts"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
|
"git.sr.ht/~bouncepaw/mycomarkup/v5"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext"
|
||||||
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
"git.sr.ht/~bouncepaw/mycomarkup/v5/tools"
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/gorilla/mux"
|
||||||
"github.com/bouncepaw/mycorrhiza/l18n"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func initReaders(r *mux.Router) {
|
func initReaders(r *mux.Router) {
|
||||||
@ -109,6 +108,7 @@ func handlerRevisionText(w http.ResponseWriter, rq *http.Request) {
|
|||||||
h = hyphae.ByName(hyphaName)
|
h = hyphae.ByName(hyphaName)
|
||||||
)
|
)
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
|
||||||
switch h := h.(type) {
|
switch h := h.(type) {
|
||||||
case *hyphae.EmptyHypha:
|
case *hyphae.EmptyHypha:
|
||||||
var mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco")
|
var mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco")
|
||||||
@ -116,27 +116,32 @@ func handlerRevisionText(w http.ResponseWriter, rq *http.Request) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
log.Printf("While serving text of ‘%s’ at revision ‘%s’: %s\n", hyphaName, revHash, err.Error())
|
slog.Error("Failed to serve text part",
|
||||||
|
"err", err, "hyphaName", hyphaName, "revHash", revHash)
|
||||||
_, _ = io.WriteString(w, "Error: "+err.Error())
|
_, _ = io.WriteString(w, "Error: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("Serving text of ‘%s’ from ‘%s’ at revision ‘%s’\n", hyphaName, mycoFilePath, revHash)
|
slog.Info("Serving text part",
|
||||||
|
"hyphaName", hyphaName, "revHash", revHash, "mycoFilePath", mycoFilePath)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
_, _ = io.WriteString(w, textContents)
|
_, _ = io.WriteString(w, textContents)
|
||||||
|
|
||||||
case hyphae.ExistingHypha:
|
case hyphae.ExistingHypha:
|
||||||
if !h.HasTextFile() {
|
if !h.HasTextFile() {
|
||||||
log.Printf(`Media hypha ‘%s’ has no text`)
|
slog.Info("Media hypha has no text part; cannot serve it",
|
||||||
|
"hyphaName", h.CanonicalName())
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
var textContents, err = history.FileAtRevision(h.TextFilePath(), revHash)
|
var textContents, err = history.FileAtRevision(h.TextFilePath(), revHash)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
log.Printf("While serving text of ‘%s’ at revision ‘%s’: %s\n", hyphaName, revHash, err.Error())
|
slog.Error("Failed to serve text part",
|
||||||
|
"err", err, "hyphaName", h.CanonicalName(), "revHash", revHash)
|
||||||
_, _ = io.WriteString(w, "Error: "+err.Error())
|
_, _ = io.WriteString(w, "Error: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("Serving text of ‘%s’ from ‘%s’ at revision ‘%s’\n", hyphaName, h.TextFilePath(), revHash)
|
slog.Info("Serving text part", "hyphaName", h.CanonicalName(), "revHash", revHash)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
_, _ = io.WriteString(w, textContents)
|
_, _ = io.WriteString(w, textContents)
|
||||||
}
|
}
|
||||||
@ -188,7 +193,7 @@ func handlerText(w http.ResponseWriter, rq *http.Request) {
|
|||||||
hyphaName := util.HyphaNameFromRq(rq, "text")
|
hyphaName := util.HyphaNameFromRq(rq, "text")
|
||||||
switch h := hyphae.ByName(hyphaName).(type) {
|
switch h := hyphae.ByName(hyphaName).(type) {
|
||||||
case hyphae.ExistingHypha:
|
case hyphae.ExistingHypha:
|
||||||
log.Println("Serving", h.TextFilePath())
|
slog.Info("Serving text part", "path", h.TextFilePath())
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
http.ServeFile(w, rq, h.TextFilePath())
|
http.ServeFile(w, rq, h.TextFilePath())
|
||||||
}
|
}
|
||||||
@ -201,9 +206,10 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) {
|
|||||||
switch h := hyphae.ByName(hyphaName).(type) {
|
switch h := hyphae.ByName(hyphaName).(type) {
|
||||||
case *hyphae.EmptyHypha, *hyphae.TextualHypha:
|
case *hyphae.EmptyHypha, *hyphae.TextualHypha:
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
log.Printf("Textual hypha ‘%s’ has no media, cannot serve\n", h.CanonicalName())
|
slog.Info("Textual hypha has no media file; cannot serve it",
|
||||||
|
"hyphaName", h.CanonicalName())
|
||||||
case *hyphae.MediaHypha:
|
case *hyphae.MediaHypha:
|
||||||
log.Println("Serving", h.MediaFilePath())
|
slog.Info("Serving media file", "path", h.MediaFilePath())
|
||||||
w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.MediaFilePath())))
|
w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.MediaFilePath())))
|
||||||
http.ServeFile(w, rq, h.MediaFilePath())
|
http.ServeFile(w, rq, h.MediaFilePath())
|
||||||
}
|
}
|
||||||
@ -252,8 +258,8 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
ctx, _ := mycocontext.ContextFromStringInput(string(fileContentsT), mycoopts.MarkupOptions(hyphaName))
|
ctx, _ := mycocontext.ContextFromStringInput(string(fileContentsT), mycoopts.MarkupOptions(hyphaName))
|
||||||
getOpenGraph, descVisitor, imgVisitor := tools.OpenGraphVisitors(ctx)
|
getOpenGraph, descVisitor, imgVisitor := tools.OpenGraphVisitors(ctx)
|
||||||
openGraph = template.HTML(getOpenGraph())
|
|
||||||
ast := mycomarkup.BlockTree(ctx, descVisitor, imgVisitor)
|
ast := mycomarkup.BlockTree(ctx, descVisitor, imgVisitor)
|
||||||
|
openGraph = template.HTML(getOpenGraph())
|
||||||
contents = template.HTML(mycomarkup.BlocksToHTML(ctx, ast))
|
contents = template.HTML(mycomarkup.BlocksToHTML(ctx, ast))
|
||||||
}
|
}
|
||||||
switch h := h.(type) {
|
switch h := h.(type) {
|
||||||
|
|||||||
@ -317,7 +317,7 @@ kbd {
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 800px;
|
max-width: 1000px;
|
||||||
margin: 96px auto;
|
margin: 96px auto;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, 0);
|
||||||
@ -354,7 +354,7 @@ kbd {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border: none;
|
border: none;
|
||||||
background: url(/web/static/icon/x.svg) no-repeat 8px 8px / 16px 16px;
|
background: url(/static/icon/x.svg) no-repeat 8px 8px / 16px 16px;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@ -285,8 +285,8 @@ rrh.shortcuts.addGroup(new ShortcutGroup('Common', null, [
|
|||||||
if (document.body.dataset.rrhAddr.startsWith('/hypha')) {
|
if (document.body.dataset.rrhAddr.startsWith('/hypha')) {
|
||||||
rrh.shortcuts.addGroup(new ShortcutGroup('Hypha', null, [
|
rrh.shortcuts.addGroup(new ShortcutGroup('Hypha', null, [
|
||||||
new Shortcut('', $$('article .wikilink'), 'First 9 hypha′s links'),
|
new Shortcut('', $$('article .wikilink'), 'First 9 hypha′s links'),
|
||||||
new Shortcut(['p', 'Alt+ArrowLeft', 'Ctrl+Alt+ArrowLeft'], $('.prevnext__prev'), 'Previous hypha'),
|
new Shortcut(['p', 'Alt+Shift+ArrowLeft', 'Ctrl+Alt+ArrowLeft'], $('.prevnext__prev'), 'Previous hypha'),
|
||||||
new Shortcut(['n', 'Alt+ArrowRight', 'Ctrl+Alt+ArrowRight'], $('.prevnext__next'), 'Next hypha'),
|
new Shortcut(['n', 'Alt+Shift+ArrowRight', 'Ctrl+Alt+ArrowRight'], $('.prevnext__next'), 'Next hypha'),
|
||||||
new Shortcut(['s', 'Alt+ArrowUp', 'Ctrl+Alt+ArrowUp'], $$('.navi-title a').slice(1, -1).slice(-1)[0], 'Parent hypha'),
|
new Shortcut(['s', 'Alt+ArrowUp', 'Ctrl+Alt+ArrowUp'], $$('.navi-title a').slice(1, -1).slice(-1)[0], 'Parent hypha'),
|
||||||
new Shortcut(['c', 'Alt+ArrowDown', 'Ctrl+Alt+ArrowDown'], $('.subhyphae__link'), 'First child hypha'),
|
new Shortcut(['c', 'Alt+ArrowDown', 'Ctrl+Alt+ArrowDown'], $('.subhyphae__link'), 'First child hypha'),
|
||||||
new Shortcut(['e', isMac ? 'Meta+Enter' : 'Ctrl+Enter'], $('.btn__link_navititle[href^="/edit/"]'), 'Edit this hypha'),
|
new Shortcut(['e', isMac ? 'Meta+Enter' : 'Ctrl+Enter'], $('.btn__link_navititle[href^="/edit/"]'), 'Edit this hypha'),
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
package viewutil
|
package viewutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/l18n"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/l18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Meta is a bundle of common stuffs used by views, templates.
|
// Meta is a bundle of common stuffs used by views, templates.
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template" // TODO: save the world
|
"text/template" // TODO: save the world
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ func Base(meta Meta, title, body string, bodyAttributes map[string]string, headE
|
|||||||
BodyAttributes: bodyAttributes,
|
BodyAttributes: bodyAttributes,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
slog.Info("Failed to execute the legacy Base template; proceeding anyway", "err", err)
|
||||||
}
|
}
|
||||||
return w.String()
|
return w.String()
|
||||||
}
|
}
|
||||||
@ -149,7 +149,7 @@ func ExecutePage(meta Meta, chain Chain, data interface {
|
|||||||
}) {
|
}) {
|
||||||
data.withBaseValues(meta, HeaderLinks, cfg.CommonScripts)
|
data.withBaseValues(meta, HeaderLinks, cfg.CommonScripts)
|
||||||
if err := chain.Get(meta).ExecuteTemplate(meta.W, "page", data); err != nil {
|
if err := chain.Get(meta).ExecuteTemplate(meta.W, "page", data); err != nil {
|
||||||
log.Println(err)
|
slog.Info("Failed to execute page; proceeding anyway", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
web/web.go
15
web/web.go
@ -4,12 +4,7 @@ package web
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/internal/user"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/l18n"
|
|
||||||
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -19,11 +14,15 @@ import (
|
|||||||
"github.com/bouncepaw/mycorrhiza/help"
|
"github.com/bouncepaw/mycorrhiza/help"
|
||||||
"github.com/bouncepaw/mycorrhiza/history/histweb"
|
"github.com/bouncepaw/mycorrhiza/history/histweb"
|
||||||
"github.com/bouncepaw/mycorrhiza/hypview"
|
"github.com/bouncepaw/mycorrhiza/hypview"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/cfg"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/internal/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/interwiki"
|
"github.com/bouncepaw/mycorrhiza/interwiki"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/l18n"
|
||||||
"github.com/bouncepaw/mycorrhiza/misc"
|
"github.com/bouncepaw/mycorrhiza/misc"
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/web/viewutil"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler initializes and returns the HTTP router based on the configuration.
|
// Handler initializes and returns the HTTP router based on the configuration.
|
||||||
@ -308,7 +307,7 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) {
|
|||||||
|
|
||||||
errmsg := user.LoginDataHTTP(w, username, "")
|
errmsg := user.LoginDataHTTP(w, username, "")
|
||||||
if errmsg != nil {
|
if errmsg != nil {
|
||||||
log.Printf("Failed to login ‘%s’ using Telegram: %s", username, err.Error())
|
slog.Error("Failed to login using Telegram", "err", err, "username", username)
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
_, _ = io.WriteString(
|
_, _ = io.WriteString(
|
||||||
w,
|
w,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user