Compare commits

...

14 Commits

Author SHA1 Message Date
Alyx Batte
3313e17efa Changed default config for docker build
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
2025-10-07 16:23:26 +00:00
Sunny
f8bba997bf
Fix URL of dialog close icon (#267) 2025-09-03 18:16:49 +03:00
Timur Ismagilov
4cf4d1695f Rip qtpl from dependencies AT LONG LAST 2025-07-03 11:59:11 +03:00
Timur Ismagilov
1b5abe4de1 Liberate mycoopts from qtpl shackles 2025-07-03 11:51:15 +03:00
Timur Ismagilov
5dfbbdb775 Liberate history package from the qtpl shackles 2025-07-03 11:35:57 +03:00
Timur Ismagilov
7e1948c93f Ignore .idea dir 2025-07-03 10:57:02 +03:00
Timur Ismagilov
3718f6ec7c Fix mp3, add flac and wav support 2025-07-03 10:51:33 +03:00
Timur Ismagilov
ce108bc07d Replace Alt+arrows with Alt+Shift+arrows and increase shortcut help dialog max-width
Fixes: https://github.com/bouncepaw/mycorrhiza/issues/262
2025-07-03 10:24:44 +03:00
Chris Sexton
da84a76e79
Reorder OpenGraph search. (#265)
The previous ordering prevented the visitors from finding a description or
image.
2025-02-20 02:02:07 +03:00
dependabot[bot]
d679eb4661
Bump golang.org/x/crypto from 0.27.0 to 0.31.0 (#261)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.27.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.27.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-12 06:41:34 +03:00
novenary
0f7525a23c
Don't exit when removing socket fails (#260)
Prior to migrating to slog, errors when removing the socket were
ignored.
This is fine and desirable: the most likely error condition is that the
socket did not exist in the first place. Exiting on error in that case
effectively prevents Mycorrhiza to be used with unix sockets.
If the removal fails for another reason, then starting the server will
fail, so logging a warning is sufficient for troubleshooting.
2024-12-12 00:20:24 +03:00
Timur Ismagilov
727280147a Update README.md 2024-10-05 22:30:17 +03:00
Timur Ismagilov
a3e8654c5b Update README.md 2024-10-05 22:03:54 +03:00
Timur Ismagilov
a4cc67cd74
Migrate from log to slog #109 (#255)
* Migrate httpd.go

* Migrate history and main

* Migrate hypview

* Migrate interwiki

* Migrate misc

* Migrate utils

* Migrate backlinks

* Migrate categories

* Reformat some imports

* Migrate hyphae

* Migrate migration

* Reformat more imports

* Migrate user

* Migrate shroom

* Migrate viewutil

* Migrate web

* Migrate others

* Migrate main

* Wording concerns
2024-09-07 23:55:39 +03:00
63 changed files with 723 additions and 1095 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
mycorrhiza mycorrhiza
.idea

View File

@ -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
View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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]].

View File

@ -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.

View File

@ -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"
) )

View File

@ -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
} }

View File

@ -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,

View File

@ -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"
) )

View File

@ -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("&nbsp;")
}
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
} }

View File

@ -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 %}&nbsp;
{% 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 %}

View File

@ -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(`&nbsp;`)
//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
}

View File

@ -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
} }

View File

@ -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())
} }

View File

@ -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)

View File

@ -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

View File

@ -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()
} }

View File

@ -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{},

View File

@ -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 {

View File

@ -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.

View File

@ -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 {

View File

@ -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)
} }
} }

View File

@ -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))
} }

View File

@ -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)
} }
} }

View File

@ -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",
} }

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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()

View File

@ -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)
} }

View File

@ -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

View File

@ -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"
) )

View File

@ -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
} }

View File

@ -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)
} }
} }

View File

@ -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).

View File

@ -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)
} }
} }

View File

@ -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
} }

View File

@ -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"
) )

View File

@ -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")
} }

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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
View File

@ -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)
}
} }

View File

@ -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()
} }

View File

@ -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()
} }

View File

@ -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"
) )

View File

@ -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")),
)
}
}

View File

@ -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 %}

View File

@ -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
}

View File

@ -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
} }

View File

@ -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())
} }
} }

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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

View File

@ -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"
) )

View File

@ -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) {

View File

@ -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) {

View File

@ -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;

View File

@ -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 hyphas links'), new Shortcut('', $$('article .wikilink'), 'First 9 hyphas 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'),

View File

@ -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.

View File

@ -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)
} }
} }

View File

@ -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,