Compare commits
37 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a96b5b1cd2 | ||
|
|
d8292bb9be | ||
|
|
3e1e3868f4 | ||
|
|
a91b08547b | ||
|
|
624b7e4847 | ||
|
|
c935ffabea | ||
|
|
f5a423f2d5 | ||
|
|
c5a53c79da | ||
|
|
669749c12e | ||
|
|
dd58cb7e55 | ||
|
|
2bea76c7f0 | ||
|
|
a689604470 | ||
|
|
6c80f67535 | ||
|
|
463b3fe49d | ||
|
|
3fe31bd5b3 | ||
|
|
1858f1aec0 | ||
|
|
dd4f76a393 | ||
|
|
f7c2910b07 | ||
|
|
556cc785f5 | ||
|
|
d5a96181d2 | ||
|
|
3ebf9b3793 | ||
|
|
3ce346d815 | ||
|
|
9525c1ff4d | ||
|
|
b6ce673292 | ||
|
|
ed7d9422d7 | ||
|
|
5d778161ec | ||
|
|
1eec1a7274 | ||
|
|
8c7aabab72 | ||
|
|
0f4242e61e | ||
|
|
9782144dcb | ||
|
|
23d08a730c | ||
|
|
2d8a3d2315 | ||
|
|
1dd8476fae | ||
|
|
8d31cbada5 | ||
|
|
37e62d2e5f | ||
|
|
2b0eefa172 | ||
|
|
ddf25011cc |
26 changed files with 1057 additions and 290 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
mlmym
|
||||
VERSION
|
||||
*.toml
|
||||
*.txt
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ WORKDIR /app
|
|||
COPY go.* ./
|
||||
RUN go mod download
|
||||
COPY . ./
|
||||
RUN git describe --tag > VERSION
|
||||
RUN go build -v -o mlmym
|
||||
|
||||
FROM debian:bullseye-slim
|
||||
|
|
@ -14,4 +15,5 @@ RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -
|
|||
COPY --from=builder /app/mlmym /app/mlmym
|
||||
COPY --from=builder /app/templates /app/templates
|
||||
COPY --from=builder /app/public /app/public
|
||||
COPY --from=builder /app/VERSION /app/VERSION
|
||||
CMD ["./mlmym", "--addr", "0.0.0.0:8080"]
|
||||
|
|
|
|||
23
Makefile
23
Makefile
|
|
@ -1,17 +1,18 @@
|
|||
.PHONY: dev reload serve style
|
||||
.PHONY: dev reload serve VERSION
|
||||
|
||||
all:
|
||||
$(MAKE) -j3 --no-print-directory dev
|
||||
all: mlmym
|
||||
|
||||
dev: reload serve style
|
||||
mlmym: VERSION
|
||||
go build -v -o mlmym
|
||||
|
||||
dev:
|
||||
$(MAKE) -j2 --no-print-directory reload serve
|
||||
|
||||
reload:
|
||||
#websocketd --port=8080 watchexec -w public echo reload &>/dev/null
|
||||
websocketd --loglevel=fatal --port=8009 watchexec --no-vcs-ignore -e html,css,js -d 500 -w public 'echo "$$WATCHEXEC_WRITTEN_PATH"'
|
||||
websocketd --loglevel=fatal --port=8009 watchexec --no-vcs-ignore -e html,css,js 'echo "$$WATCHEXEC_WRITTEN_PATH"'
|
||||
|
||||
serve:
|
||||
#python -m http.server --directory ./public 8081 &>/dev/null
|
||||
watchexec -e go -r "go run . --addr 0.0.0.0:8008 -w"
|
||||
VERSION:
|
||||
git describe --tag > $@
|
||||
|
||||
style:
|
||||
npm run watchcss > /dev/null 2>&1
|
||||
serve: VERSION
|
||||
DEBUG=true watchexec --no-vcs-ignore -e go -r "go run . --addr 0.0.0.0:8008 -w"
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -3,14 +3,23 @@ a familiar desktop experience for [lemmy](https://join-lemmy.org).
|
|||
|
||||

|
||||
|
||||
### deployment
|
||||
## deployment
|
||||
|
||||
```bash
|
||||
docker run -it -p "8080:8080" ghcr.io/rystaf/mlmym:latest
|
||||
```
|
||||
|
||||
### config
|
||||
## config
|
||||
Set the environment variable `LEMMY_DOMAIN` to run in single instance mode
|
||||
```bash
|
||||
docker run -it -e LEMMY_DOMAIN='lemmydomain.com' -p "8080:8080" ghcr.io/rystaf/mlmym:latest
|
||||
```
|
||||
#### default user settings
|
||||
| environment variable | default |
|
||||
| -------------------- | ------- |
|
||||
| DARK | false |
|
||||
| HIDE_THUMBNAILS | false |
|
||||
| LISTING | All |
|
||||
| SORT | Hot |
|
||||
| COMMENT_SORT | Hot |
|
||||
|
||||
|
|
|
|||
24
go.mod
24
go.mod
|
|
@ -3,16 +3,16 @@ module mlmym
|
|||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/gorilla/sessions v1.2.1 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
||||
github.com/k3a/html2text v1.2.1 // indirect
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230704005320-c4b010dd339b // indirect
|
||||
github.com/yuin/goldmark v1.5.4 // indirect
|
||||
go.elara.ws/go-lemmy v0.17.3 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/k3a/html2text v1.2.1
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230720221045-c6d79b98e968
|
||||
github.com/yuin/goldmark v1.5.4
|
||||
golang.org/x/text v0.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
)
|
||||
|
|
|
|||
31
go.sum
31
go.sum
|
|
@ -2,45 +2,28 @@ github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+M
|
|||
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/k3a/html2text v1.2.1 h1:nvnKgBvBR/myqrwfLuiqecUtaK1lB9hGziIJKatNFVY=
|
||||
github.com/k3a/html2text v1.2.1/go.mod h1:ieEXykM67iT8lTvEWBh6fhpH4B23kB9OMKPdIBmgUqA=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230622213726-c394de37235c h1:VxOcsDMWaqoBKbhoiSBxPl1zZ62YZ/VAW2nxlBRJiow=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230622213726-c394de37235c/go.mod h1:nRSkTD+ARAHXtqlSPdf5q3hjHLP1ALsS1m5D3o86o+4=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230622214853-5f2ab0756865 h1:xitFpcTOSP8RlZWR569yY75B2/7WX08rQQVG+0Mi4SA=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230622214853-5f2ab0756865/go.mod h1:nRSkTD+ARAHXtqlSPdf5q3hjHLP1ALsS1m5D3o86o+4=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230622215253-d38b61ec174f h1:EueAC5v+8oX9xK9bT36Tpgbz+c66wUZx5zmyxePurbw=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230622215253-d38b61ec174f/go.mod h1:nRSkTD+ARAHXtqlSPdf5q3hjHLP1ALsS1m5D3o86o+4=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230622222647-983d49e1d285 h1:tihBOF3ejTXzYVftaflwqRAXnaY4W9q3iNiE3YMF+D8=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230622222647-983d49e1d285/go.mod h1:nRSkTD+ARAHXtqlSPdf5q3hjHLP1ALsS1m5D3o86o+4=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230622230518-ee2cfdf288a4 h1:++T5SoZzghtfNJprWlXiRSpPPdnMSSZgIWWAnPoGx/w=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230622230518-ee2cfdf288a4/go.mod h1:nRSkTD+ARAHXtqlSPdf5q3hjHLP1ALsS1m5D3o86o+4=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230623185656-962f9bf8359d h1:ORS2KIBuT+wBn4wJncF1SoLDCVCAUPHASHpQ+Y3TnRI=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230623185656-962f9bf8359d/go.mod h1:nRSkTD+ARAHXtqlSPdf5q3hjHLP1ALsS1m5D3o86o+4=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230623191111-7ff8c74b1935 h1:zmzUz6PGRB8yQTT6BRaZNTgNlrk6L7e72dzTnWJTw+I=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230623191111-7ff8c74b1935/go.mod h1:nRSkTD+ARAHXtqlSPdf5q3hjHLP1ALsS1m5D3o86o+4=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230623191350-f39e3c8bdcb5 h1:MoI87uid2KqpLdUMZGK2HBOuxJMnPOJaar/4Og2PshM=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230623191350-f39e3c8bdcb5/go.mod h1:nRSkTD+ARAHXtqlSPdf5q3hjHLP1ALsS1m5D3o86o+4=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230704005320-c4b010dd339b h1:6z+gOUUvKwKQfgqEbxXS229gjr5V3HYg9bYbL9VHFdQ=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230704005320-c4b010dd339b/go.mod h1:nRSkTD+ARAHXtqlSPdf5q3hjHLP1ALsS1m5D3o86o+4=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230720221045-c6d79b98e968 h1:wfyB6wQzYMH2U8xQvdamExbyCyPhe4o8HP47FMZM5Jk=
|
||||
github.com/rystaf/go-lemmy v0.0.0-20230720221045-c6d79b98e968/go.mod h1:nRSkTD+ARAHXtqlSPdf5q3hjHLP1ALsS1m5D3o86o+4=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
|
||||
github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.elara.ws/go-lemmy v0.17.3 h1:644k23BS2xqKJHJ9cHd8eyt1INpb5myqsBQQL2chBiA=
|
||||
go.elara.ws/go-lemmy v0.17.3/go.mod h1:rurQND/HT3yWfX/T4w+hb6vEwRAeAlV+9bSGFkkx5rA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
|
|||
45
main.go
45
main.go
|
|
@ -7,12 +7,14 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
)
|
||||
|
||||
var version string
|
||||
var watch = flag.Bool("w", false, "watch for file changes")
|
||||
var addr = flag.String("addr", ":80", "http service address")
|
||||
var md goldmark.Markdown
|
||||
|
|
@ -54,7 +56,7 @@ func init() {
|
|||
))
|
||||
templates = make(map[string]*template.Template)
|
||||
if !*watch {
|
||||
for _, name := range []string{"index.html", "login.html", "frontpage.html", "root.html", "settings.html", "xhr.html"} {
|
||||
for _, name := range []string{"index.html", "login.html", "frontpage.html", "root.html", "settings.html", "xhr.html", "create_comment.html"} {
|
||||
t := template.New(name).Funcs(funcMap)
|
||||
glob, err := t.ParseGlob("templates/*")
|
||||
if err != nil {
|
||||
|
|
@ -64,6 +66,47 @@ func init() {
|
|||
templates[name] = glob
|
||||
}
|
||||
}
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
test()
|
||||
}
|
||||
if data, err := os.ReadFile("VERSION"); err == nil {
|
||||
version = string(data)
|
||||
}
|
||||
}
|
||||
func test() {
|
||||
links := [][]string{
|
||||
[]string{"https://lemmy.local/u/dude", "/lemmy.local/u/dude", "/u/dude"},
|
||||
[]string{"https://lemmy.local/u/dude@lemmy.local", "/lemmy.local/u/dude", "/u/dude"},
|
||||
[]string{"/u/dude", "/lemmy.local/u/dude", "/u/dude"},
|
||||
[]string{"/u/dude@lemmy.world", "/lemmy.local/u/dude@lemmy.world", "/u/dude@lemmy.world"},
|
||||
[]string{"/u/dude@lemmy.local", "/lemmy.local/u/dude", "/u/dude"},
|
||||
[]string{"https://lemmy.world/c/dude", "/lemmy.local/c/dude@lemmy.world", "/c/dude@lemmy.world"},
|
||||
[]string{"https://lemmy.world/u/dude", "/lemmy.local/u/dude@lemmy.world", "/u/dude@lemmy.world"},
|
||||
[]string{"https://lemmy.world/u/dude@lemmy.world", "/lemmy.local/u/dude@lemmy.world", "/u/dude@lemmy.world"},
|
||||
[]string{"https://lemmy.world/post/123", "/lemmy.local/post/123@lemmy.world", "/post/123@lemmy.world"},
|
||||
[]string{"https://lemmy.world/post/123#123", "https://lemmy.world/post/123#123", "https://lemmy.world/post/123#123"},
|
||||
[]string{"/post/123", "/lemmy.local/post/123", "/post/123"},
|
||||
[]string{"/comment/123", "/lemmy.local/comment/123", "/comment/123"},
|
||||
[]string{"https://lemmy.local/comment/123", "/lemmy.local/comment/123", "/comment/123"},
|
||||
}
|
||||
for _, url := range links {
|
||||
output := LemmyLinkRewrite(`href="`+url[0]+`"`, "lemmy.local", "")
|
||||
success := (output == (`href="` + url[1] + `"`))
|
||||
if !success {
|
||||
fmt.Println("\n!!!! multi instance link rewrite failure !!!!")
|
||||
fmt.Println(url)
|
||||
fmt.Println(output)
|
||||
fmt.Println("")
|
||||
}
|
||||
output = LemmyLinkRewrite(`href="`+url[0]+`"`, ".", "lemmy.local")
|
||||
success = (output == (`href="` + url[2] + `"`))
|
||||
if !success {
|
||||
fmt.Println("\n!!!! single instance link rewrite failure !!!!")
|
||||
fmt.Println(success, url)
|
||||
fmt.Println(output)
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
}
|
||||
func middleware(n httprouter.Handle) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
|
|
|
|||
17
public/noscript.css
Normal file
17
public/noscript.css
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
.scripting,
|
||||
.expando-button,
|
||||
.minimize,
|
||||
#showimages,
|
||||
#lmc,
|
||||
.hidechildren {
|
||||
display: none !important;
|
||||
}
|
||||
.post .expando .image img {
|
||||
visibility: visible;
|
||||
}
|
||||
div.pager {
|
||||
display: block;
|
||||
}
|
||||
.savecomment input[type=file] {
|
||||
display: inline-block;
|
||||
}
|
||||
124
public/style.css
124
public/style.css
|
|
@ -115,7 +115,8 @@ summary {
|
|||
.title a p {
|
||||
display: inline;
|
||||
}
|
||||
.post.distinguished .title a {
|
||||
.post.distinguished .title a, .post.announcement .title a,
|
||||
.dark .post.distinguished .title a:visited, .post.announcement .title a:visited {
|
||||
color: #228822;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
@ -218,7 +219,28 @@ summary {
|
|||
font-size: 10px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.meta a {
|
||||
.preview h3 {
|
||||
background-color: #f0f3fc;
|
||||
border: 0px solid #e6e6e6;
|
||||
border-bottom-width: 1px;
|
||||
color: black;
|
||||
margin: 0px;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.dark .preview h3 {
|
||||
background-color: #333;
|
||||
border-color: #333;
|
||||
color: #ccc;
|
||||
}
|
||||
.preview .comment {
|
||||
margin-top: 5px;
|
||||
padding: 0px;
|
||||
}
|
||||
.preview .comment .content {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
.meta a, .activity .meta a {
|
||||
color: #369;
|
||||
text-decoration: none;
|
||||
font-size: 10px;
|
||||
|
|
@ -227,6 +249,13 @@ summary {
|
|||
.dark .meta a {
|
||||
color: #6a98af;
|
||||
}
|
||||
.comment .meta a.distinguished.admin, .post.distinguished a.admin, .post.announcement a.admin {
|
||||
background-color: #ff0011;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border-radius: 3px;
|
||||
padding: 0px 2px;
|
||||
}
|
||||
.meta a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
@ -254,6 +283,7 @@ summary {
|
|||
}
|
||||
form.savecomment {
|
||||
margin: 0px 0px 10px 0px;
|
||||
width: 500px;
|
||||
}
|
||||
.comment > .children > form.savecomment {
|
||||
margin: 0px 0px 10px 20px;
|
||||
|
|
@ -262,9 +292,34 @@ form.savecomment {
|
|||
margin: 5px 0px 10px 15px;
|
||||
}
|
||||
.savecomment textarea {
|
||||
width: 500px;
|
||||
margin: 5px 0px;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.savecomment .upload label div {
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
width: 32px;
|
||||
position: relative;
|
||||
background-color: #999;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.savecomment .upload input {
|
||||
display: none;
|
||||
}
|
||||
.savecomment .right {
|
||||
float:right;
|
||||
}
|
||||
.savecomment .right a {
|
||||
line-height: 28px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.comment .meta a.minimize {
|
||||
color: #369;
|
||||
font-size: 10px;
|
||||
|
|
@ -284,6 +339,7 @@ form.savecomment {
|
|||
.children .morecomments {
|
||||
}
|
||||
.morecomments {
|
||||
height: 20px;
|
||||
clear: left;
|
||||
margin: 0px 0px 10px 0px;
|
||||
font-size: 10px;
|
||||
|
|
@ -296,11 +352,11 @@ form.savecomment {
|
|||
.morecomments a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.member {
|
||||
.member, .block {
|
||||
display: inline-block;
|
||||
margin-bottom:3px;
|
||||
}
|
||||
.member input {
|
||||
.member input, .block input {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
|
|
@ -312,10 +368,10 @@ form.savecomment {
|
|||
bottom: 1px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.join input {
|
||||
.join input, .block input {
|
||||
background-color: green;
|
||||
}
|
||||
.leave input {
|
||||
.leave input, .block.unblock input {
|
||||
background-color: #cf6165;
|
||||
}
|
||||
.pending input {
|
||||
|
|
@ -471,7 +527,7 @@ form.nsfw div {
|
|||
.expando-button:hover{
|
||||
background-color: #466599;
|
||||
}
|
||||
.expando-button.hidden, .children.hidden .comment {
|
||||
.expando-button.hidden, .children.hidden .comment, .children.hidden .morecomments {
|
||||
display: none;
|
||||
}
|
||||
.hidechildren .show {
|
||||
|
|
@ -493,21 +549,52 @@ form.nsfw div {
|
|||
position: relative;
|
||||
color: #000;
|
||||
}
|
||||
#settingspopup {
|
||||
#mycommunities, #settingspopup {
|
||||
background-color: white;
|
||||
border: 1px solid #888;
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
}
|
||||
#mycommunities {
|
||||
top: 17px;
|
||||
padding: 5px 0px;
|
||||
border-width: 0px 1px 1px 0px;
|
||||
}
|
||||
#mycommunities div {
|
||||
margin: 0px 5px;
|
||||
}
|
||||
#mycommunities a:first-child {
|
||||
text-align: right;
|
||||
}
|
||||
#mycommunities a {
|
||||
text-decoration: none;
|
||||
color: #369;
|
||||
text-transform: uppercase;
|
||||
font-size: 9px;
|
||||
display: block;
|
||||
padding: 0px 3px;
|
||||
}
|
||||
.dark #mycommunities a {
|
||||
color: #8cb3d9;
|
||||
}
|
||||
.dark #mycommunities a:hover {
|
||||
background-color: #3e3e3e;
|
||||
}
|
||||
#mycommunities a:hover {
|
||||
background-color: #c7def7;
|
||||
}
|
||||
#settingspopup {
|
||||
right: 10px;
|
||||
top: 45px;
|
||||
}
|
||||
#settingspopup form {
|
||||
margin: 0px;
|
||||
}
|
||||
.dark #settingspopup {
|
||||
.dark #settingspopup, .dark #mycommunities {
|
||||
background-color: #262626;
|
||||
}
|
||||
#settingspopup.open {
|
||||
#settingspopup.open, #mycommunities.open {
|
||||
display: inline-block;
|
||||
}
|
||||
.expando.open{
|
||||
|
|
@ -798,11 +885,11 @@ nav .communities a.more {
|
|||
color: orangered !important;
|
||||
}
|
||||
|
||||
nav a {
|
||||
nav .communities a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
nav > a:hover {
|
||||
nav .title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
|
@ -916,11 +1003,11 @@ nav .right a.mailbox {
|
|||
top: 4px;
|
||||
color: gray;
|
||||
}
|
||||
nav .right a, .right input[type=submit] {
|
||||
nav .right a, nav .right input[type=submit] {
|
||||
color: #369;
|
||||
text-decoration: none;
|
||||
}
|
||||
.dark nav .right a, .dark .right input[type=submit]{
|
||||
.dark nav .right a, .dark nav .right input[type=submit]{
|
||||
color: #dadada;
|
||||
}
|
||||
nav .right form, .comment form, form.link-btn {
|
||||
|
|
@ -1017,9 +1104,14 @@ form.create input[type=file], form.create select {
|
|||
font-size: 13px;
|
||||
margin: 10px;
|
||||
}
|
||||
.preferences div:last-child label {
|
||||
text-align: left;
|
||||
font-size: 10px;
|
||||
}
|
||||
.preferences label{
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
width: 150px;
|
||||
margin-right: 5px;
|
||||
text-align: right;
|
||||
|
||||
}
|
||||
|
|
|
|||
109
public/utils.js
109
public/utils.js
|
|
@ -10,8 +10,6 @@ function request(url, params, callback, errorcallback = function(){}) {
|
|||
var method = "GET"
|
||||
if (params) method = "POST"
|
||||
xmlHttp.open(method, url, true);
|
||||
if (method = "POST")
|
||||
xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
xmlHttp.send(params);
|
||||
}
|
||||
function postClick(e) {
|
||||
|
|
@ -34,25 +32,31 @@ function postClick(e) {
|
|||
}
|
||||
}
|
||||
}
|
||||
function uptil (el, f) {
|
||||
if (el) return f(el) ? el : uptil(el.parentNode, f)
|
||||
}
|
||||
function commentClick(e) {
|
||||
e = e || window.event;
|
||||
var targ = e.currentTarget || e.srcElement || e;
|
||||
if (targ.nodeType == 3) targ = targ.parentNode;
|
||||
if (e.target.name=="submit") {
|
||||
e.preventDefault()
|
||||
var form = e.target.parentNode
|
||||
var form = uptil(e.target, function(el){ return el.tagName == "FORM" })
|
||||
if (form) {
|
||||
data = new FormData(form)
|
||||
data.set(e.target.name, e.target.value)
|
||||
data.set("xhr", 1)
|
||||
if (("c"+data.get("commentid")) == targ.id) {
|
||||
|
||||
targ.action = form.action
|
||||
if (e.target.value == "preview") {
|
||||
targ = form
|
||||
}
|
||||
console.log("ok")
|
||||
} else if (("c"+data.get("parentid")) == targ.id) {
|
||||
targ = form
|
||||
} else { return }
|
||||
params = new URLSearchParams(data).toString()
|
||||
params += "&" + e.target.name + "=" + e.target.value
|
||||
params += "&xhr=1"
|
||||
e.target.disabled = "disabled"
|
||||
request(targ.target || "", params,
|
||||
request(targ.action || "", data,
|
||||
function(res){
|
||||
targ.outerHTML = res
|
||||
setup()
|
||||
|
|
@ -91,8 +95,7 @@ function commentClick(e) {
|
|||
}
|
||||
return false
|
||||
}
|
||||
if ((e.target.className.indexOf("loadmore") != -1) ||
|
||||
(e.target.className.indexOf("edit") != -1) ||
|
||||
if ((e.target.className.indexOf("edit") != -1) ||
|
||||
(e.target.className.indexOf("source") != -1) ||
|
||||
(e.target.className.indexOf("reply") != -1)) {
|
||||
var id = targ.id
|
||||
|
|
@ -104,6 +107,32 @@ function commentClick(e) {
|
|||
})
|
||||
return false
|
||||
}
|
||||
if (e.target.className.indexOf("loadmore") != -1) {
|
||||
var id = targ.id
|
||||
if (e.target.getAttribute("for") != id) { return }
|
||||
e.preventDefault()
|
||||
var comments = targ.getElementsByClassName("comment")
|
||||
var skip = []
|
||||
for (var i = 0; i < comments.length; i++) {
|
||||
skip.push(comments[i].id)
|
||||
}
|
||||
request(e.target.href+"&xhr",false, function(res){
|
||||
var parent = e.target.parentNode
|
||||
parent.innerHTML = res
|
||||
parent.innerHTML = parent.getElementsByClassName("children")[0].innerHTML
|
||||
var comments = parent.getElementsByClassName("comment")
|
||||
for (var i = 0; i < skip.length; i++) {
|
||||
for (var c = 0; c < comments.length; c++) {
|
||||
if (skip[i] == comments[c].id) {
|
||||
comments[c].remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
parent.outerHTML = parent.innerHTML
|
||||
setup()
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function loadMoreComments(e) {
|
||||
|
|
@ -120,7 +149,7 @@ function loadMoreComments(e) {
|
|||
e.target.parentNode.outerHTML = res + '<div class="morecomments"><a id="lmc" href="" data-page="'+(parseInt(page)+1)+'">load more comments</a></div>'
|
||||
setup()
|
||||
} else {
|
||||
e.target.parentNode.outerHTML = ""
|
||||
e.target.parentNode.innerHTML = ""
|
||||
}
|
||||
}, function() {
|
||||
e.target.innerHTML = "loading failed"
|
||||
|
|
@ -192,12 +221,15 @@ function formSubmit(e) {
|
|||
var targ = e.currentTarget || e.srcElement || e;
|
||||
e.preventDefault()
|
||||
var data = new FormData(targ)
|
||||
params = new URLSearchParams(data).toString()
|
||||
params += "&" + e.submitter.name + "=" + e.submitter.value
|
||||
params += "&xhr=1"
|
||||
data.set(e.submitter.name, e.submitter.value)
|
||||
data.set("xhr", "1")
|
||||
e.submitter.disabled = "disabled"
|
||||
request(targ.target, params,
|
||||
request(targ.target, data,
|
||||
function(res){
|
||||
if (data.get("op") == "read_post") {
|
||||
document.getElementById("p"+data.get("postid")).remove()
|
||||
return
|
||||
}
|
||||
targ.outerHTML = res
|
||||
setup()
|
||||
},
|
||||
|
|
@ -208,9 +240,33 @@ function formSubmit(e) {
|
|||
return false
|
||||
}
|
||||
|
||||
function toggleMyCommunities(e) {
|
||||
e.preventDefault()
|
||||
var mycommunities = document.getElementById("mycommunities")
|
||||
if (mycommunities.className.indexOf("open") > -1) {
|
||||
mycommunities.className = ""
|
||||
return false
|
||||
}
|
||||
mycommunities.className = "open"
|
||||
if (mycommunities.innerHTML == "") {
|
||||
mycommunities.innerHTML = "<div>loading</div>"
|
||||
request(e.target.href + "&xhr=1", "", function(res) {
|
||||
mycommunities.innerHTML = '<div><a href="'+e.target.href+'">view all »</a>'
|
||||
mycommunities.innerHTML += res
|
||||
}, function() {
|
||||
mycommunities.className = ""
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function openSettings(e) {
|
||||
e.preventDefault()
|
||||
var settings = document.getElementById("settingspopup")
|
||||
if (settings.className == "open") {
|
||||
settings.className = ""
|
||||
return false
|
||||
}
|
||||
settings.className = "open"
|
||||
request(e.target.href + "?xhr=1", "", function(res) {
|
||||
settings.innerHTML = res
|
||||
|
|
@ -240,8 +296,7 @@ function saveSettings(e) {
|
|||
var targ = e.currentTarget || e.srcElement || e;
|
||||
var data = new FormData(targ)
|
||||
e.preventDefault()
|
||||
var params = new URLSearchParams(data).toString()
|
||||
request(targ.target, params, function(res) {
|
||||
request(targ.target, data, function(res) {
|
||||
["endlessScrolling", "autoLoad"].map(function(x) {
|
||||
localStorage.setItem(x, data.get(x)=="on")
|
||||
})
|
||||
|
|
@ -296,6 +351,16 @@ function toggleImages(open) {
|
|||
}
|
||||
}
|
||||
|
||||
function insertImg(e) {
|
||||
e = e || window.event;
|
||||
var form = uptil(e.target, function(el){ return el.tagName == "FORM" })
|
||||
form.querySelector("input[value=preview]").click()
|
||||
var inputs = form.getElementsByTagName("input")
|
||||
for (var i = 0; i < inputs.length; i++) {
|
||||
inputs[i].disabled = "disabled"
|
||||
}
|
||||
}
|
||||
|
||||
function setup() {
|
||||
if (showimages = document.getElementById("se")) {
|
||||
showimages.addEventListener("click", showImages)
|
||||
|
|
@ -303,15 +368,23 @@ function setup() {
|
|||
if (settings = document.getElementById("opensettings")) {
|
||||
settings.addEventListener("click", openSettings)
|
||||
}
|
||||
if (settings = document.getElementById("openmycommunities")) {
|
||||
settings.addEventListener("click", toggleMyCommunities)
|
||||
}
|
||||
if (hidechildren = document.getElementById("hidechildren")){
|
||||
hidechildren.addEventListener("click", hideAllChildComments)
|
||||
}
|
||||
if (lmc = document.getElementById("lmc")){
|
||||
if (pager = document.getElementsByClassName("pager")){
|
||||
var pager = document.getElementsByClassName("pager")
|
||||
if (pager.length) {
|
||||
pager[0].style.display = "none";
|
||||
}
|
||||
lmc.addEventListener("click", loadMoreComments)
|
||||
}
|
||||
var imgUpload = document.getElementsByClassName("imgupload")
|
||||
for (var i = 0; i < imgUpload.length; i++) {
|
||||
imgUpload[i].addEventListener("change", insertImg)
|
||||
}
|
||||
var posts = document.getElementsByClassName("post")
|
||||
for (var i = 0; i < posts.length; i++) {
|
||||
posts[i].addEventListener("click", postClick)
|
||||
|
|
|
|||
33
public/ws.js
Normal file
33
public/ws.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
var ok = false
|
||||
var t = 800
|
||||
var ws
|
||||
var prot = location.protocol == 'https:' ? "wss://":"ws://"
|
||||
let port = ":8009"
|
||||
|
||||
function start(){
|
||||
let url = prot + document.location.hostname + port + document.location.pathname + document.location.search;
|
||||
console.log('connecting to ', url);
|
||||
ws = new WebSocket(url);
|
||||
ws.onopen = function(){
|
||||
console.log("open");
|
||||
t = 800
|
||||
}
|
||||
ws.onmessage = function(msg){
|
||||
console.log("reload:", msg.data)
|
||||
if (msg.data == "") {
|
||||
return
|
||||
}
|
||||
window.location.reload()
|
||||
}
|
||||
ws.onclose = function(){
|
||||
console.log("close");
|
||||
setTimeout(function(){
|
||||
//start()
|
||||
if (t < 10 * 1000) t += 200
|
||||
}, t);
|
||||
};
|
||||
}
|
||||
console.log("ws");
|
||||
if (typeof WebSocket != 'undefined') {
|
||||
start()
|
||||
}
|
||||
346
routes.go
346
routes.go
|
|
@ -32,12 +32,12 @@ var funcMap = template.FuncMap{
|
|||
}
|
||||
return host
|
||||
},
|
||||
"proxy": func(s string) string {
|
||||
"localize": func(s string) string {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return s
|
||||
}
|
||||
return "/" + u.Host + u.Path
|
||||
return "." + u.Path + "@" + u.Host
|
||||
},
|
||||
"printer": func(n any) string {
|
||||
p := message.NewPrinter(language.English)
|
||||
|
|
@ -119,9 +119,9 @@ var funcMap = template.FuncMap{
|
|||
if re.MatchString(p.URL.String()) {
|
||||
return p.URL.String() + "?format=jpg&thumbnail=96"
|
||||
}
|
||||
re = regexp.MustCompile(`^https:\/\/i.imgur.com\/([a-zA-Z0-9]+)\.([a-z]+)$`)
|
||||
re = regexp.MustCompile(`^https:\/\/(i\.)?imgur.com\/([a-zA-Z0-9]{5,})(\.[a-zA-Z0-9]+)?`)
|
||||
if re.MatchString(p.URL.String()) {
|
||||
return re.ReplaceAllString(p.URL.String(), "https://i.imgur.com/${1}s.$2")
|
||||
return re.ReplaceAllString(p.URL.String(), "https://i.imgur.com/${2}s.jpg")
|
||||
}
|
||||
if p.URL.IsValid() {
|
||||
return "/_/static/link.png"
|
||||
|
|
@ -133,23 +133,17 @@ var funcMap = template.FuncMap{
|
|||
var buf bytes.Buffer
|
||||
re := regexp.MustCompile(`\s---\s`)
|
||||
body = re.ReplaceAllString(body, "\n***\n")
|
||||
// community bangs
|
||||
body = RegReplace(body, `([^\[])!([a-zA-Z0-9_]+)@([a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)+)`, `$1[!$2@$3](/c/$2@$3)`)
|
||||
if err := md.Convert([]byte(body), &buf); err != nil {
|
||||
fmt.Println(err)
|
||||
return template.HTML(body)
|
||||
}
|
||||
converted := buf.String()
|
||||
converted = strings.Replace(converted, `<img `, `<img loading="lazy" `, -1)
|
||||
if os.Getenv("LEMMY_DOMAIN") == "" {
|
||||
re := regexp.MustCompile(`href="https:\/\/([a-zA-Z0-9\.\-]+\/(c\/[a-zA-Z0-9]+|(post|comment)\/\d+))`)
|
||||
converted = re.ReplaceAllString(converted, `href="/$1`)
|
||||
}
|
||||
re = regexp.MustCompile(`href="\/(c\/[a-zA-Z0-9\-]+|(post|comment)\/\d+)`)
|
||||
converted = re.ReplaceAllString(converted, `href="/`+host+`/$1`)
|
||||
re = regexp.MustCompile(` !([a-zA-Z0-9]+)@([a-zA-Z0-9\.\-]+) `)
|
||||
converted = re.ReplaceAllString(converted, ` <a href="/$2/c/$1">!$1@$2</a> `)
|
||||
re = regexp.MustCompile(`::: spoiler (.*?)\n([\S\s]*?):::`)
|
||||
converted = re.ReplaceAllString(converted, "<details><summary>$1</summary>$2</details>")
|
||||
return template.HTML(converted)
|
||||
body = buf.String()
|
||||
body = strings.Replace(body, `<img `, `<img loading="lazy" `, -1)
|
||||
body = LemmyLinkRewrite(body, host, os.Getenv("LEMMY_DOMAIN"))
|
||||
body = RegReplace(body, `::: ?spoiler (.*?)\n([\S\s]*?):::`, "<details><summary>$1</summary>$2</details>")
|
||||
return template.HTML(body)
|
||||
},
|
||||
"rmmarkdown": func(body string) string {
|
||||
var buf bytes.Buffer
|
||||
|
|
@ -158,20 +152,79 @@ var funcMap = template.FuncMap{
|
|||
return body
|
||||
}
|
||||
text := html2text.HTML2TextWithOptions(buf.String(), html2text.WithLinksInnerText())
|
||||
re := regexp.MustCompile(`\<https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)\>`)
|
||||
re := regexp.MustCompile(`\<(https?:\/\/|mailto)(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)\>`)
|
||||
return re.ReplaceAllString(text, "")
|
||||
},
|
||||
"contains": strings.Contains,
|
||||
"sub": func(a int32, b int) int {
|
||||
return int(a) - b
|
||||
},
|
||||
"add": func(a int32, b int) int {
|
||||
return int(a) + b
|
||||
},
|
||||
}
|
||||
|
||||
func LemmyLinkRewrite(input string, host string, lemmy_domain string) (body string) {
|
||||
body = input
|
||||
// localize community and user links
|
||||
body = RegReplace(body, `href="https:\/\/([a-zA-Z0-9\.\-]+)\/((c|u|comment|post)\/[^#\?]*?)"`, `href="/$2@$1"`)
|
||||
// remove extra instance tag
|
||||
body = RegReplace(body, `href="(https:\/)?(\/[a-zA-Z0-9\.\-]+)?\/((c|u)\/[a-zA-Z0-9]+@[a-zA-Z0-9\.\-]+)@([a-zA-Z0-9\.\-]+)"`, `href="/$3"`)
|
||||
if lemmy_domain == "" {
|
||||
// add domain to relative links
|
||||
body = RegReplace(body, `href="\/((c|u|post|comment)\/(.*?)")`, `href="/`+host+`/$1`)
|
||||
// convert links to relative
|
||||
body = RegReplace(body, `href="https:\/\/([a-zA-Z0-9\.\-]+\/((c|u|post|comment)\/[a-zA-Z0-9]+"))`, `href="/$1`)
|
||||
} else {
|
||||
// convert local links to relative
|
||||
body = RegReplace(body, `href="https:\/\/`+lemmy_domain+`\/(c\/[a-zA-Z0-9]+"|(c|u|post|comment)\/(.*?)")`, `href="/$1`)
|
||||
body = RegReplace(body, `href="(.*)@`+lemmy_domain+`"`, `href="$1"`)
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`href="\/?([a-zA-Z0-9\.\-]*)\/(c|u|post|comment)\/(.*?)@(.*?)"`)
|
||||
// assume "old." subdomain is mlmym and remove
|
||||
matches := re.FindAllStringSubmatch(body, -1)
|
||||
for _, match := range matches {
|
||||
if match[4][0:4] == "old." {
|
||||
s := 1
|
||||
if match[1] == "" {
|
||||
s += 1
|
||||
}
|
||||
body = strings.Replace(body, match[0], `href="/`+strings.Join(match[s:4], "/")+"@"+match[4][4:]+`"`, -1)
|
||||
}
|
||||
}
|
||||
// remove redundant instance tag
|
||||
matches = re.FindAllStringSubmatch(body, -1)
|
||||
for _, match := range matches {
|
||||
if match[1] == match[4] {
|
||||
body = strings.Replace(body, match[0], `href="/`+strings.Join(match[1:4], "/")+`"`, -1)
|
||||
}
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
func RegReplace(input string, match string, replace string) string {
|
||||
re := regexp.MustCompile(match)
|
||||
return re.ReplaceAllString(input, replace)
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
value := os.Getenv(key)
|
||||
if len(value) == 0 {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func Initialize(Host string, r *http.Request) (State, error) {
|
||||
state := State{
|
||||
Host: Host,
|
||||
Page: 1,
|
||||
Status: http.StatusOK,
|
||||
Host: Host,
|
||||
Page: 1,
|
||||
Status: http.StatusOK,
|
||||
Version: version,
|
||||
}
|
||||
if watch != nil {
|
||||
state.Watch = *watch
|
||||
}
|
||||
lemmyDomain := os.Getenv("LEMMY_DOMAIN")
|
||||
if lemmyDomain != "" {
|
||||
|
|
@ -206,14 +259,28 @@ func Initialize(Host string, r *http.Request) (State, error) {
|
|||
}
|
||||
state.Listing = getCookie(r, "DefaultListingType")
|
||||
state.Sort = getCookie(r, "DefaultSortType")
|
||||
state.Dark = getCookie(r, "Dark") != ""
|
||||
state.CommentSort = getCookie(r, "DefaultCommentSortType")
|
||||
if dark := getCookie(r, "Dark"); dark != "" {
|
||||
state.Dark = dark != "0"
|
||||
} else {
|
||||
state.Dark = os.Getenv("DARK") != ""
|
||||
}
|
||||
state.ShowNSFW = getCookie(r, "ShowNSFW") != ""
|
||||
state.HideInstanceNames = getCookie(r, "HideInstanceNames") != ""
|
||||
if hide := getCookie(r, "HideThumbnails"); hide != "" {
|
||||
state.HideThumbnails = hide != "0"
|
||||
} else {
|
||||
state.HideThumbnails = os.Getenv("HIDE_THUMBNAILS") != ""
|
||||
}
|
||||
state.ParseQuery(r.URL.RawQuery)
|
||||
if state.Sort == "" {
|
||||
state.Sort = "Hot"
|
||||
state.Sort = getenv("SORT", "Hot")
|
||||
}
|
||||
if state.CommentSort == "" {
|
||||
state.CommentSort = getenv("COMMENT_SORT", "Hot")
|
||||
}
|
||||
if state.Listing == "" || state.Session == nil && state.Listing == "Subscribed" {
|
||||
state.Listing = "All"
|
||||
state.Listing = getenv("LISTING", "All")
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
|
@ -382,17 +449,84 @@ func GetFrontpage(w http.ResponseWriter, r *http.Request, ps httprouter.Params)
|
|||
}
|
||||
}
|
||||
|
||||
func GetCommunities(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
r.URL.Path = "/search"
|
||||
if ps.ByName("host") != "" {
|
||||
r.URL.Path = "/" + ps.ByName("host") + "/search"
|
||||
}
|
||||
r.URL.RawQuery = "searchtype=Communities&sort=TopMonth"
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
}
|
||||
|
||||
func ResolveId(r *http.Request, class string, id string, host string) string {
|
||||
remoteAddr := r.RemoteAddr
|
||||
if r.Header.Get("CF-Connecting-IP") != "" {
|
||||
remoteAddr = r.Header.Get("CF-Connecting-IP")
|
||||
}
|
||||
client := http.Client{Transport: NewAddHeaderTransport(remoteAddr)}
|
||||
c, err := lemmy.NewWithClient("https://"+host, &client)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
idn, _ := strconv.Atoi(id)
|
||||
if class == "post" {
|
||||
resp, err := c.Post(context.Background(), types.GetPost{
|
||||
ID: types.NewOptional(idn),
|
||||
})
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return resp.PostView.Post.ApID
|
||||
}
|
||||
resp, err := c.Comment(context.Background(), types.GetComment{
|
||||
ID: idn,
|
||||
})
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return resp.CommentView.Comment.ApID
|
||||
}
|
||||
|
||||
func GetPost(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
state, err := Initialize(ps.ByName("host"), r)
|
||||
if err != nil {
|
||||
Render(w, "index.html", state)
|
||||
return
|
||||
}
|
||||
if path := strings.Split(ps.ByName("postid"), "@"); len(path) > 1 {
|
||||
apid := ResolveId(r, "post", path[0], path[1])
|
||||
if apid != "" {
|
||||
resp, err := state.Client.ResolveObject(context.Background(), types.ResolveObject{
|
||||
Q: apid,
|
||||
})
|
||||
if err != nil {
|
||||
dest := apid
|
||||
if os.Getenv("LEMMY_DOMAIN") == "" {
|
||||
dest = RegReplace(dest, `https:\/\/([a-zA-Z0-9\.\-]+\/post\/\d+)`, `/$1`)
|
||||
}
|
||||
http.Redirect(w, r, dest, 302)
|
||||
return
|
||||
}
|
||||
post, _ := resp.Post.Value()
|
||||
if post.Post.ID > 0 {
|
||||
dest := RegReplace(r.URL.String(), `(([a-zA-Z0-9\.\-]+)?/post/)([a-zA-Z0-9\-\.@]+)`, `$1`)
|
||||
dest += strconv.Itoa(post.Post.ID)
|
||||
http.Redirect(w, r, dest, 302)
|
||||
return
|
||||
} else {
|
||||
http.Redirect(w, r, apid, 302)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
m, _ := url.ParseQuery(r.URL.RawQuery)
|
||||
if len(m["edit"]) > 0 {
|
||||
state.Op = "edit_post"
|
||||
state.GetSite()
|
||||
}
|
||||
if len(m["content"]) > 0 {
|
||||
state.Content = m["content"][0]
|
||||
}
|
||||
postid, _ := strconv.Atoi(ps.ByName("postid"))
|
||||
state.GetPost(postid)
|
||||
state.GetComments()
|
||||
|
|
@ -404,6 +538,32 @@ func GetComment(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
Render(w, "index.html", state)
|
||||
return
|
||||
}
|
||||
if path := strings.Split(ps.ByName("commentid"), "@"); len(path) > 1 {
|
||||
apid := ResolveId(r, "comment", path[0], path[1])
|
||||
if apid != "" {
|
||||
resp, err := state.Client.ResolveObject(context.Background(), types.ResolveObject{
|
||||
Q: apid,
|
||||
})
|
||||
if err != nil {
|
||||
dest := apid
|
||||
if os.Getenv("LEMMY_DOMAIN") == "" {
|
||||
dest = RegReplace(dest, `https:\/\/([a-zA-Z0-9\.\-]+\/comment\/\d+)`, `/$1`)
|
||||
}
|
||||
http.Redirect(w, r, dest, 302)
|
||||
return
|
||||
}
|
||||
comment, _ := resp.Comment.Value()
|
||||
if comment.Comment.ID > 0 {
|
||||
dest := RegReplace(r.URL.String(), `(([a-zA-Z0-9\.\-]+)?/comment/)([a-zA-Z0-9\-\.@]+)`, `$1`)
|
||||
dest += strconv.Itoa(comment.Comment.ID)
|
||||
http.Redirect(w, r, dest, 302)
|
||||
return
|
||||
} else {
|
||||
http.Redirect(w, r, apid, 302)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
m, _ := url.ParseQuery(r.URL.RawQuery)
|
||||
if len(m["reply"]) > 0 {
|
||||
state.Op = "reply"
|
||||
|
|
@ -411,11 +571,22 @@ func GetComment(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
if len(m["edit"]) > 0 {
|
||||
state.Op = "edit"
|
||||
}
|
||||
if r.Method == "POST" && len(m["content"]) > 0 {
|
||||
state.Content = m["content"][0]
|
||||
}
|
||||
if len(m["source"]) > 0 {
|
||||
state.Op = "source"
|
||||
}
|
||||
if len(m["context"]) > 0 {
|
||||
ctx, _ := strconv.Atoi(m["context"][0])
|
||||
state.Context = ctx
|
||||
}
|
||||
commentid, _ := strconv.Atoi(ps.ByName("commentid"))
|
||||
state.GetComment(commentid)
|
||||
if state.XHR && len(m["content"]) > 0 {
|
||||
Render(w, "create_comment.html", state)
|
||||
return
|
||||
}
|
||||
state.GetPost(state.PostID)
|
||||
Render(w, "index.html", state)
|
||||
}
|
||||
|
|
@ -533,9 +704,10 @@ func Settings(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
Render(w, "index.html", state)
|
||||
return
|
||||
}
|
||||
state.GetSite()
|
||||
switch r.Method {
|
||||
case "POST":
|
||||
for _, name := range []string{"DefaultSortType", "DefaultListingType"} {
|
||||
for _, name := range []string{"DefaultSortType", "DefaultListingType", "DefaultCommentSortType"} {
|
||||
deleteCookie(w, state.Host, name)
|
||||
setCookie(w, "", name, r.FormValue(name))
|
||||
}
|
||||
|
|
@ -543,8 +715,7 @@ func Settings(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
setCookie(w, "", "Dark", "1")
|
||||
state.Dark = true
|
||||
} else {
|
||||
deleteCookie(w, state.Host, "Dark")
|
||||
deleteCookie(w, "", "Dark")
|
||||
setCookie(w, "", "Dark", "0")
|
||||
state.Dark = false
|
||||
}
|
||||
if r.FormValue("shownsfw") != "" {
|
||||
|
|
@ -555,8 +726,23 @@ func Settings(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
deleteCookie(w, "", "ShowNSFW")
|
||||
state.ShowNSFW = false
|
||||
}
|
||||
if r.FormValue("hideInstanceNames") != "" {
|
||||
setCookie(w, "", "HideInstanceNames", "1")
|
||||
state.HideInstanceNames = true
|
||||
} else {
|
||||
deleteCookie(w, "", "HideInstanceNames")
|
||||
state.HideInstanceNames = false
|
||||
}
|
||||
if r.FormValue("hideThumbnails") != "" {
|
||||
setCookie(w, "", "HideThumbnails", "1")
|
||||
state.HideInstanceNames = true
|
||||
} else {
|
||||
setCookie(w, "", "HideThumbnails", "0")
|
||||
state.HideInstanceNames = false
|
||||
}
|
||||
state.Listing = r.FormValue("DefaultListingType")
|
||||
state.Sort = r.FormValue("DefaultSortType")
|
||||
state.CommentSort = r.FormValue("DefaultCommentSortType")
|
||||
// TODO save user settings
|
||||
case "GET":
|
||||
if state.Session != nil {
|
||||
|
|
@ -733,6 +919,18 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
CommunityID: communityid,
|
||||
Follow: true,
|
||||
})
|
||||
case "block":
|
||||
communityid, _ := strconv.Atoi(r.FormValue("communityid"))
|
||||
state.Client.BlockCommunity(context.Background(), types.BlockCommunity{
|
||||
CommunityID: communityid,
|
||||
Block: true,
|
||||
})
|
||||
case "unblock":
|
||||
communityid, _ := strconv.Atoi(r.FormValue("communityid"))
|
||||
state.Client.BlockCommunity(context.Background(), types.BlockCommunity{
|
||||
CommunityID: communityid,
|
||||
Block: false,
|
||||
})
|
||||
case "logout":
|
||||
deleteCookie(w, state.Host, "jwt")
|
||||
deleteCookie(w, state.Host, "user")
|
||||
|
|
@ -748,10 +946,15 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
if err != nil {
|
||||
if strings.Contains(fmt.Sprintf("%v", err), "missing_totp_token") {
|
||||
state.Op = "2fa"
|
||||
Render(w, "login.html", state)
|
||||
return
|
||||
}
|
||||
state.GetSite()
|
||||
if state.Site != nil && state.Site.SiteView.LocalSite.CaptchaEnabled {
|
||||
state.GetCaptcha()
|
||||
}
|
||||
state.Status = http.StatusUnauthorized
|
||||
state.Error = err
|
||||
Render(w, "login.html", state)
|
||||
return
|
||||
} else if resp.JWT.IsValid() {
|
||||
state.GetUser(r.FormValue("username"))
|
||||
if state.User != nil {
|
||||
|
|
@ -963,6 +1166,22 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
r.URL.Path = "/" + state.Host + "/c/" + resp.PostView.Community.Name
|
||||
r.URL.RawQuery = ""
|
||||
}
|
||||
case "read_post":
|
||||
postid, _ := strconv.Atoi(r.FormValue("postid"))
|
||||
post := types.MarkPostAsRead{
|
||||
PostID: postid,
|
||||
Read: true,
|
||||
}
|
||||
if r.FormValue("submit") == "mark unread" {
|
||||
post.Read = false
|
||||
}
|
||||
_, err := state.Client.MarkPostAsRead(context.Background(), post)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else if r.FormValue("xhr") != "" {
|
||||
w.Write([]byte{})
|
||||
return
|
||||
}
|
||||
case "vote_post":
|
||||
var score int16
|
||||
score = 1
|
||||
|
|
@ -1016,9 +1235,20 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
parentid, _ := strconv.Atoi(r.FormValue("parentid"))
|
||||
state.GetComment(parentid)
|
||||
}
|
||||
content := r.FormValue("content")
|
||||
file, handler, err := r.FormFile("file")
|
||||
if err == nil {
|
||||
pres, err := state.UploadImage(file, handler)
|
||||
if err != nil {
|
||||
state.Error = err
|
||||
Render(w, "index.html", state)
|
||||
return
|
||||
}
|
||||
content += ("")
|
||||
}
|
||||
if r.FormValue("submit") == "save" {
|
||||
createComment := types.CreateComment{
|
||||
Content: r.FormValue("content"),
|
||||
Content: content,
|
||||
PostID: state.PostID,
|
||||
}
|
||||
if state.CommentID > 0 {
|
||||
|
|
@ -1040,6 +1270,22 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
} else {
|
||||
fmt.Println(err)
|
||||
}
|
||||
} else if r.FormValue("submit") == "preview" {
|
||||
q := r.URL.Query()
|
||||
q.Set("content", content)
|
||||
q.Set("reply", "")
|
||||
if r.FormValue("xhr") != "" {
|
||||
q.Set("xhr", "1")
|
||||
}
|
||||
r.URL.RawQuery = q.Encode()
|
||||
if ps.ByName("postid") != "" {
|
||||
GetPost(w, r, ps)
|
||||
return
|
||||
}
|
||||
if ps.ByName("commentid") != "" {
|
||||
GetComment(w, r, ps)
|
||||
return
|
||||
}
|
||||
} else if r.FormValue("xhr") != "" {
|
||||
w.Write([]byte{})
|
||||
return
|
||||
|
|
@ -1049,10 +1295,23 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
}
|
||||
case "edit_comment":
|
||||
commentid, _ := strconv.Atoi(r.FormValue("commentid"))
|
||||
q := r.URL.Query()
|
||||
content := r.FormValue("content")
|
||||
file, handler, err := r.FormFile("file")
|
||||
if err == nil {
|
||||
pres, err := state.UploadImage(file, handler)
|
||||
if err != nil {
|
||||
state.Error = err
|
||||
Render(w, "index.html", state)
|
||||
return
|
||||
}
|
||||
content += ("")
|
||||
}
|
||||
|
||||
if r.FormValue("submit") == "save" {
|
||||
resp, err := state.Client.EditComment(context.Background(), types.EditComment{
|
||||
CommentID: commentid,
|
||||
Content: types.NewOptional(r.FormValue("content")),
|
||||
Content: types.NewOptional(content),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
|
@ -1061,6 +1320,29 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
r.URL.Fragment = "c" + commentid
|
||||
r.URL.RawQuery = ""
|
||||
}
|
||||
} else if r.FormValue("submit") == "preview" {
|
||||
q.Set("content", content)
|
||||
q.Set("edit", "")
|
||||
if r.FormValue("xhr") != "" {
|
||||
q.Set("xhr", "1")
|
||||
}
|
||||
r.URL.RawQuery = q.Encode()
|
||||
if ps.ByName("commentid") != "" {
|
||||
GetComment(w, r, ps)
|
||||
return
|
||||
}
|
||||
} else if r.FormValue("submit") == "cancel" {
|
||||
if ps.ByName("commentid") != "" {
|
||||
if r.FormValue("xhr") != "" {
|
||||
q.Set("xhr", "1")
|
||||
}
|
||||
r.URL.RawQuery = q.Encode()
|
||||
GetComment(w, r, ps)
|
||||
return
|
||||
}
|
||||
} else if r.FormValue("xhr") != "" {
|
||||
w.Write([]byte{})
|
||||
return
|
||||
}
|
||||
if r.FormValue("xhr") != "" {
|
||||
state.XHR = true
|
||||
|
|
@ -1129,6 +1411,7 @@ func GetRouter() *httprouter.Router {
|
|||
router.POST("/:host/create_post", middleware(UserOp))
|
||||
router.GET("/:host/create_community", middleware(GetCreateCommunity))
|
||||
router.POST("/:host/create_community", middleware(UserOp))
|
||||
router.GET("/:host/communities", middleware(GetCommunities))
|
||||
} else {
|
||||
router.ServeFiles("/_/static/*filepath", http.Dir("public"))
|
||||
router.GET("/", middleware(GetFrontpage))
|
||||
|
|
@ -1160,6 +1443,7 @@ func GetRouter() *httprouter.Router {
|
|||
router.POST("/create_post", middleware(UserOp))
|
||||
router.GET("/create_community", middleware(GetCreateCommunity))
|
||||
router.POST("/create_community", middleware(UserOp))
|
||||
router.GET("/communities", middleware(GetCommunities))
|
||||
}
|
||||
return router
|
||||
}
|
||||
|
|
|
|||
222
state.go
222
state.go
|
|
@ -11,6 +11,8 @@ import (
|
|||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -33,6 +35,12 @@ func (c *Comment) Submitter() bool {
|
|||
return c.P.Comment.CreatorID == c.P.Post.CreatorID
|
||||
}
|
||||
|
||||
func (c *Comment) ParentID() int {
|
||||
path := strings.Split(c.P.Comment.Path, ".")
|
||||
id, _ := strconv.Atoi(path[len(path)-2])
|
||||
return id
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
types.PersonViewSafe
|
||||
}
|
||||
|
|
@ -51,46 +59,85 @@ type Post struct {
|
|||
}
|
||||
|
||||
type Session struct {
|
||||
UserName string
|
||||
UserID int
|
||||
UserName string
|
||||
UserID int
|
||||
Communities []types.CommunityView
|
||||
}
|
||||
|
||||
type State struct {
|
||||
Client *lemmy.Client
|
||||
HTTPClient *http.Client
|
||||
Session *Session
|
||||
Status int
|
||||
Error error
|
||||
Alert string
|
||||
Host string
|
||||
CommunityName string
|
||||
Community *types.GetCommunityResponse
|
||||
TopCommunities []types.CommunityView
|
||||
Communities []types.CommunityView
|
||||
UnreadCount int64
|
||||
Sort string
|
||||
Listing string
|
||||
Page int
|
||||
Parts []string
|
||||
Posts []Post
|
||||
Comments []Comment
|
||||
Activities []Activity
|
||||
CommentCount int
|
||||
PostID int
|
||||
CommentID int
|
||||
UserName string
|
||||
User *types.GetPersonDetailsResponse
|
||||
Now int64
|
||||
XHR bool
|
||||
Op string
|
||||
Site *types.GetSiteResponse
|
||||
Query string
|
||||
SearchType string
|
||||
Captcha *types.CaptchaResponse
|
||||
Dark bool
|
||||
ShowNSFW bool
|
||||
Watch bool
|
||||
Version string
|
||||
Client *lemmy.Client
|
||||
HTTPClient *http.Client
|
||||
Session *Session
|
||||
Status int
|
||||
Error error
|
||||
Alert string
|
||||
Host string
|
||||
CommunityName string
|
||||
Community *types.GetCommunityResponse
|
||||
TopCommunities []types.CommunityView
|
||||
Communities []types.CommunityView
|
||||
UnreadCount int64
|
||||
Sort string
|
||||
CommentSort string
|
||||
Listing string
|
||||
Page int
|
||||
Parts []string
|
||||
Posts []Post
|
||||
Comments []Comment
|
||||
Activities []Activity
|
||||
CommentCount int
|
||||
PostID int
|
||||
CommentID int
|
||||
Context int
|
||||
UserName string
|
||||
User *types.GetPersonDetailsResponse
|
||||
Now int64
|
||||
XHR bool
|
||||
Op string
|
||||
Site *types.GetSiteResponse
|
||||
Query string
|
||||
Content string
|
||||
SearchType string
|
||||
Captcha *types.CaptchaResponse
|
||||
Dark bool
|
||||
ShowNSFW bool
|
||||
HideInstanceNames bool
|
||||
HideThumbnails bool
|
||||
}
|
||||
|
||||
func (s State) Unknown() string {
|
||||
fmt.Println(fmt.Sprintf("%v", s.Error))
|
||||
re := regexp.MustCompile(`(.*?)@(.*?)@`)
|
||||
if strings.Contains(fmt.Sprintf("%v", s.Error), "couldnt_find_community") {
|
||||
matches := re.FindAllStringSubmatch(s.CommunityName+"@", -1)
|
||||
if len(matches) < 1 || len(matches[0]) < 3 {
|
||||
return ""
|
||||
}
|
||||
if matches[0][2] != s.Host {
|
||||
remote := "/" + matches[0][2] + "/c/" + matches[0][1]
|
||||
if os.Getenv("LEMMY_DOMAIN") != "" {
|
||||
remote = "https:/" + remote
|
||||
}
|
||||
return remote
|
||||
}
|
||||
}
|
||||
if strings.Contains(fmt.Sprintf("%v", s.Error), "couldnt_find_that_username_or_email") {
|
||||
matches := re.FindAllStringSubmatch(s.UserName+"@", -1)
|
||||
if len(matches) < 1 || len(matches[0]) < 3 {
|
||||
return ""
|
||||
}
|
||||
if matches[0][2] != s.Host {
|
||||
remote := "/" + matches[0][2] + "/u/" + matches[0][1]
|
||||
if os.Getenv("LEMMY_DOMAIN") != "" {
|
||||
remote = "https:/" + remote
|
||||
}
|
||||
return remote
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (p State) SortBy(v string) string {
|
||||
var q string
|
||||
if p.Query != "" || p.SearchType == "Communities" {
|
||||
|
|
@ -156,6 +203,7 @@ func (state *State) ParseQuery(RawQuery string) {
|
|||
}
|
||||
if len(m["sort"]) > 0 {
|
||||
state.Sort = m["sort"][0]
|
||||
state.CommentSort = m["sort"][0]
|
||||
}
|
||||
if len(m["communityname"]) > 0 {
|
||||
state.CommunityName = m["communityname"][0]
|
||||
|
|
@ -214,17 +262,27 @@ func (state *State) GetCaptcha() {
|
|||
}
|
||||
}
|
||||
func (state *State) GetSite() {
|
||||
token := state.Client.Token
|
||||
state.Client.Token = ""
|
||||
resp, err := state.Client.Site(context.Background(), types.GetSite{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
state.Status = http.StatusInternalServerError
|
||||
state.Host = "."
|
||||
state.Error = errors.New("site unreachable")
|
||||
state.Error = errors.New("unable to retrieve site")
|
||||
return
|
||||
}
|
||||
state.Client.Token = token
|
||||
state.Site = resp
|
||||
if !state.Site.MyUser.IsValid() {
|
||||
return
|
||||
}
|
||||
for _, c := range state.Site.MyUser.MustValue().Follows {
|
||||
state.Session.Communities = append(state.Session.Communities, types.CommunityView{
|
||||
Community: c.Community,
|
||||
Subscribed: "Subscribed",
|
||||
})
|
||||
}
|
||||
sort.Slice(state.Session.Communities, func(a, b int) bool {
|
||||
return state.Session.Communities[a].Community.Name < state.Session.Communities[b].Community.Name
|
||||
})
|
||||
}
|
||||
|
||||
func (state *State) GetComment(commentid int) {
|
||||
|
|
@ -234,7 +292,7 @@ func (state *State) GetComment(commentid int) {
|
|||
state.CommentID = commentid
|
||||
cresp, err := state.Client.Comments(context.Background(), types.GetComments{
|
||||
ParentID: types.NewOptional(state.CommentID),
|
||||
Sort: types.NewOptional(types.CommentSortType(state.Sort)),
|
||||
Sort: types.NewOptional(types.CommentSortType(state.CommentSort)),
|
||||
Type: types.NewOptional(types.ListingType("All")),
|
||||
Limit: types.NewOptional(int64(50)),
|
||||
})
|
||||
|
|
@ -258,6 +316,33 @@ func (state *State) GetComment(commentid int) {
|
|||
state.Comments = append(state.Comments, comment)
|
||||
}
|
||||
}
|
||||
if len(state.Comments) == 0 {
|
||||
return
|
||||
}
|
||||
ctx, err := state.GetContext(state.Context, state.Comments[0])
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
state.Comments = []Comment{ctx}
|
||||
}
|
||||
}
|
||||
func (state *State) GetContext(depth int, comment Comment) (ctx Comment, err error) {
|
||||
if depth < 1 || comment.ParentID() == 0 {
|
||||
return comment, nil
|
||||
}
|
||||
cresp, err := state.Client.Comment(context.Background(), types.GetComment{
|
||||
ID: comment.ParentID(),
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ctx, err = state.GetContext(depth-1, Comment{
|
||||
P: cresp.CommentView,
|
||||
State: state,
|
||||
C: []Comment{comment},
|
||||
ChildCount: comment.ChildCount + 1,
|
||||
})
|
||||
return
|
||||
}
|
||||
func (state *State) GetComments() {
|
||||
if state.Sort != "Hot" && state.Sort != "Top" && state.Sort != "Old" && state.Sort != "New" {
|
||||
|
|
@ -265,7 +350,7 @@ func (state *State) GetComments() {
|
|||
}
|
||||
cresp, err := state.Client.Comments(context.Background(), types.GetComments{
|
||||
PostID: types.NewOptional(state.PostID),
|
||||
Sort: types.NewOptional(types.CommentSortType(state.Sort)),
|
||||
Sort: types.NewOptional(types.CommentSortType(state.CommentSort)),
|
||||
Type: types.NewOptional(types.ListingType("All")),
|
||||
Limit: types.NewOptional(int64(50)),
|
||||
Page: types.NewOptional(int64(state.Page)),
|
||||
|
|
@ -377,6 +462,7 @@ func (state *State) GetUser(username string) {
|
|||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
state.Error = err
|
||||
state.Status = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
|
|
@ -461,7 +547,18 @@ func (state *State) GetPosts() {
|
|||
|
||||
func (state *State) Search(searchtype string) {
|
||||
if state.Query == "" && searchtype == "Communities" {
|
||||
if state.Listing == "Subscribed" {
|
||||
if state.Page > 1 {
|
||||
return
|
||||
}
|
||||
if state.Site == nil {
|
||||
state.GetSite()
|
||||
}
|
||||
state.Communities = state.Session.Communities
|
||||
return
|
||||
}
|
||||
resp, err := state.Client.Communities(context.Background(), types.ListCommunities{
|
||||
Type: types.NewOptional(types.ListingType(state.Listing)),
|
||||
Sort: types.NewOptional(types.SortType(state.Sort)),
|
||||
Limit: types.NewOptional(int64(25)),
|
||||
Page: types.NewOptional(int64(state.Page)),
|
||||
|
|
@ -476,7 +573,7 @@ func (state *State) Search(searchtype string) {
|
|||
search := types.Search{
|
||||
Q: state.Query,
|
||||
Sort: types.NewOptional(types.SortType(state.Sort)),
|
||||
ListingType: types.NewOptional(types.ListingType("All")),
|
||||
ListingType: types.NewOptional(types.ListingType(state.Listing)),
|
||||
Type: types.NewOptional(types.SearchType(searchtype)),
|
||||
Limit: types.NewOptional(int64(25)),
|
||||
Page: types.NewOptional(int64(state.Page)),
|
||||
|
|
@ -504,9 +601,13 @@ func (state *State) Search(searchtype string) {
|
|||
})
|
||||
}
|
||||
for _, c := range resp.Comments {
|
||||
state.Comments = append(state.Comments, Comment{
|
||||
comment := Comment{
|
||||
P: c,
|
||||
State: state,
|
||||
}
|
||||
state.Activities = append(state.Activities, Activity{
|
||||
Timestamp: c.Comment.Published.Time,
|
||||
Comment: &comment,
|
||||
})
|
||||
}
|
||||
state.Communities = resp.Communities
|
||||
|
|
@ -524,22 +625,22 @@ func (state *State) GetPost(postid int) {
|
|||
})
|
||||
if err != nil {
|
||||
state.Status = http.StatusInternalServerError
|
||||
state.Error = err
|
||||
return
|
||||
} else {
|
||||
state.Posts = []Post{Post{
|
||||
PostView: resp.PostView,
|
||||
State: state,
|
||||
}}
|
||||
if state.CommentID > 0 && len(state.Posts) > 0 {
|
||||
state.Posts[0].Rank = -1
|
||||
}
|
||||
state.CommunityName = resp.PostView.Community.Name
|
||||
cresp := types.GetCommunityResponse{
|
||||
CommunityView: resp.CommunityView,
|
||||
Moderators: resp.Moderators,
|
||||
}
|
||||
state.Community = &cresp
|
||||
}
|
||||
state.Posts = []Post{Post{
|
||||
PostView: resp.PostView,
|
||||
State: state,
|
||||
}}
|
||||
if state.CommentID > 0 && len(state.Posts) > 0 {
|
||||
state.Posts[0].Rank = -1
|
||||
}
|
||||
state.CommunityName = resp.PostView.Community.Name
|
||||
cresp := types.GetCommunityResponse{
|
||||
CommunityView: resp.CommunityView,
|
||||
Moderators: resp.Moderators,
|
||||
}
|
||||
state.Community = &cresp
|
||||
}
|
||||
|
||||
func (state *State) GetCommunity(communityName string) {
|
||||
|
|
@ -592,19 +693,19 @@ func (state *State) UploadImage(file multipart.File, header *multipart.FileHeade
|
|||
|
||||
func getChildren(parent *Comment, pool []types.CommentView, postCreatorID int) {
|
||||
var children []Comment
|
||||
total := -1
|
||||
total := int32(0)
|
||||
for _, c := range pool {
|
||||
levels := strings.Split(c.Comment.Path, ".")
|
||||
for i, l := range levels {
|
||||
id, _ := strconv.Atoi(l)
|
||||
if id == parent.P.Comment.ID {
|
||||
total = total + 1
|
||||
if i == (len(levels) - 2) {
|
||||
children = append(children, Comment{
|
||||
P: c,
|
||||
C: children,
|
||||
State: parent.State,
|
||||
})
|
||||
total += c.Counts.ChildCount
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -612,7 +713,8 @@ func getChildren(parent *Comment, pool []types.CommentView, postCreatorID int) {
|
|||
}
|
||||
for i, _ := range children {
|
||||
getChildren(&children[i], pool, postCreatorID)
|
||||
parent.ChildCount += 1
|
||||
}
|
||||
parent.C = children
|
||||
parent.ChildCount = total
|
||||
parent.P.Counts.ChildCount -= total
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,23 @@
|
|||
<div class="activity">
|
||||
{{ if $activity.Comment }}
|
||||
<div class="title{{ if eq $activity.Comment.Op "unread"}} orangered{{end}}">
|
||||
{{ if not $state.User }}
|
||||
{{ if and (not $state.User) (not $state.Query) }}
|
||||
<b>comment</b> on
|
||||
{{ end }}
|
||||
<a href="/{{$state.Host}}/post/{{ $activity.Comment.P.Post.ID}}">{{ $activity.Comment.P.Post.Name}}</a>
|
||||
<span class="meta">
|
||||
{{ if $state.User}}
|
||||
by
|
||||
<a href="">{{$state.User.PersonView.Person.Name }}</a>
|
||||
{{ end }}
|
||||
in
|
||||
<a href="/{{$state.Host}}/c/{{ fullcname $activity.Comment.P.Community }}">/c/{{ $activity.Comment.P.Community.Name }}</a>
|
||||
<a href="/{{$state.Host}}/c/{{ fullcname $activity.Comment.P.Community }}">
|
||||
c/{{ if $state.HideInstanceNames -}}
|
||||
{{ $activity.Comment.P.Community.Name }}</a>
|
||||
{{ else -}}
|
||||
{{ fullcname $activity.Comment.P.Community }}
|
||||
{{ end }}
|
||||
</span>
|
||||
</div>
|
||||
{{ template "comment.html" $activity.Comment }}
|
||||
{{ else if $activity.Post }}
|
||||
|
|
@ -23,10 +30,22 @@
|
|||
<b>message</b>
|
||||
{{ if eq $activity.Message.Creator.ID $state.Session.UserID }}
|
||||
to
|
||||
<a href="/{{$state.Host}}/u/{{fullname $activity.Message.Recipient}}">{{ $activity.Message.Recipient.Name }}</a>
|
||||
<a href="/{{$state.Host}}/u/{{fullname $activity.Message.Recipient}}">
|
||||
{{- if $state.HideInstanceNames -}}
|
||||
{{ $activity.Message.Recipient.Name }}
|
||||
{{- else -}}
|
||||
{{ fullname $activity.Message.Recipient }}
|
||||
{{- end -}}
|
||||
</a>
|
||||
{{ else }}
|
||||
from
|
||||
<a href="/{{$state.Host}}/u/{{fullname $activity.Message.Creator}}">{{ $activity.Message.Creator.Name }}</a>
|
||||
<a href="/{{$state.Host}}/u/{{fullname $activity.Message.Creator}}">
|
||||
{{- if $state.HideInstanceNames -}}
|
||||
{{ $activity.Message.Creator.Name }}
|
||||
{{- else -}}
|
||||
{{ fullname $activity.Message.Creator }}
|
||||
{{- end -}}
|
||||
</a>
|
||||
{{end}}
|
||||
sent {{ humanize $activity.Message.PrivateMessage.Published.Time }}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="comment{{if or (lt .P.Counts.Score -5) .P.Comment.Deleted }} hidden{{end}}" id="c{{.P.Comment.ID}}">
|
||||
<div class="comment{{if or (lt .P.Counts.Score -5) .P.Comment.Deleted .P.Comment.Removed }} hidden{{end}}" id="c{{.P.Comment.ID}}">
|
||||
{{ if .State.Session }}
|
||||
<div class="score">
|
||||
<form class="link-btn{{ if eq .P.MyVote.String "1"}} like{{ else if eq .P.MyVote.String "-1"}} dislike{{end}}" method="POST">
|
||||
|
|
@ -15,44 +15,44 @@
|
|||
{{ end }}
|
||||
<div class="meta">
|
||||
<a class="minimize" href="" for="c{{.P.Comment.ID}}">
|
||||
{{if or (lt .P.Counts.Score -5) .P.Comment.Deleted }}
|
||||
{{- if or (lt .P.Counts.Score -5) .P.Comment.Deleted -}}
|
||||
[+]
|
||||
{{ else }}
|
||||
{{- else -}}
|
||||
[-]
|
||||
{{ end }}
|
||||
{{- end -}}
|
||||
</a>
|
||||
<a {{ if .P.Comment.Distinguished}}class="{{if .P.Creator.Admin}}admin {{end}}distinguished"{{ else if .Submitter }}class="submitter"{{end}} href="/{{.State.Host}}/u/{{fullname .P.Creator}}">
|
||||
{{- if .State.HideInstanceNames -}}
|
||||
{{ .P.Creator.Name }}
|
||||
{{- else -}}
|
||||
{{ fullname .P.Creator }}
|
||||
{{- end -}}
|
||||
</a>
|
||||
<a {{ if .P.Comment.Distinguished}}class="distinguished"{{ else if .Submitter }}class="submitter"{{end}} href="/{{.State.Host}}/u/{{fullname .P.Creator}}">{{fullname .P.Creator}}</a>
|
||||
<b>{{.P.Counts.Score}} points</b> <span title="{{.P.Comment.Published.Time}}">{{ humanize .P.Comment.Published.Time }}</span>
|
||||
{{- if gt .P.Comment.Updated.Time.Unix .P.Comment.Published.Time.Unix -}}
|
||||
* (last edited <span title="{{.P.Comment.Updated.Time}}">{{ humanize .P.Comment.Updated.Time }}</span>)
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="content">
|
||||
{{ if eq .Op "edit" }}
|
||||
<div class="content">
|
||||
<form class="savecomment" method="POST">
|
||||
<div>
|
||||
<textarea required name="content">{{ .P.Comment.Content }}</textarea>
|
||||
</div>
|
||||
<input type="hidden" name="commentid" value="{{.P.Comment.ID}}">
|
||||
<input type="hidden" name="op" value="edit_comment">
|
||||
<input name="submit" type="submit" value="save">
|
||||
<input name="submit" type="submit" value="cancel">
|
||||
</form>
|
||||
{{ template "create_comment.html" .State }}
|
||||
{{ else }}
|
||||
<div class="content">
|
||||
{{if .P.Comment.Deleted}}
|
||||
[removed]
|
||||
{{else}}
|
||||
{{if .P.Comment.Deleted}}
|
||||
[deleted]
|
||||
{{else if .P.Comment.Removed }}
|
||||
[removed by mod]
|
||||
{{else}}
|
||||
<div {{ if and .Selected (not .State.XHR) (ne .State.Op "reply")}}class="highlight" {{end}}>
|
||||
{{ markdown .State.Host .P.Comment.Content }}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{ if eq .Op "source" }}
|
||||
<div><textarea>{{.P.Comment.Content}}</textarea></div>
|
||||
{{end}}
|
||||
{{ end }}
|
||||
<ul class="buttons">
|
||||
<li><a href="/{{.State.Host}}/comment/{{.P.Comment.ID}}">permalink</a></li>
|
||||
<li><a href="{{.P.Comment.ApID}}">fedilink</a></li>
|
||||
{{ if ne .Op "source"}}
|
||||
<li><a class="source" for="c{{.P.Comment.ID}}" href="/{{.State.Host}}/comment/{{.P.Comment.ID}}?source">source</a></li>
|
||||
{{ else }}
|
||||
|
|
@ -87,29 +87,31 @@
|
|||
</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ if gt .ChildCount 0 }}
|
||||
<li><a class="hidechildren" for="c{{.P.Comment.ID}}" href=""><span class="hide">hide</span><span class="show">show {{ .ChildCount }}</span> child comments</a></li>
|
||||
{{ if and .ParentID .State.CommentID (not .State.XHR) }}
|
||||
<li>
|
||||
<a href="/{{.State.Host}}/comment/{{.ParentID}}">parent</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ if and .ParentID (or .State.Activities .State.Query) }}
|
||||
<li>
|
||||
<a href="/{{.State.Host}}/comment/{{.P.Comment.ID}}?context=3">context</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ if and .State.PostID (gt (add .P.Counts.ChildCount .ChildCount) 0) }}
|
||||
<li><a class="hidechildren" for="c{{.P.Comment.ID}}" href=""><span class="hide">hide</span><span class="show">show {{add .P.Counts.ChildCount .ChildCount }}</span> child comments</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="children">
|
||||
{{ if and (eq .State.Op "reply") (eq .State.CommentID .P.Comment.ID)}}
|
||||
<form class="savecomment" method="POST">
|
||||
<div>
|
||||
<textarea name="content"></textarea>
|
||||
</div>
|
||||
<input type="hidden" name="parentid" value="{{.P.Comment.ID}}">
|
||||
<input type="hidden" name="op" value="create_comment">
|
||||
<input type="submit" name="submit" value="save">
|
||||
<input type="submit" name="submit" value="cancel">
|
||||
</form>
|
||||
{{ template "create_comment.html" .State }}
|
||||
{{ end}}
|
||||
{{ range $ci, $child := .C }}{{ template "comment.html" $child }}{{end}}
|
||||
{{ if ne .P.Counts.ChildCount .ChildCount}}
|
||||
{{ if and (ne .P.Counts.ChildCount .ChildCount) (not .State.Activities) (not .State.Query) }}
|
||||
<div class="morecomments">
|
||||
<a class="loadmore" for="c{{ .P.Comment.ID}}" href="/{{.State.Host}}/comment/{{.P.Comment.ID}}?">load more comments</a>
|
||||
<span class="gray">({{ sub .P.Counts.ChildCount .ChildCount}} replies)</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<div class="community">
|
||||
<form method="POST" class="member {{ membership .Subscribed }}">
|
||||
<input name="op" type="submit" value="{{ membership .Subscribed}}">
|
||||
<input type="hidden" name="communityid" value ="{{.Community.ID}}">
|
||||
<input type="hidden" name="communityid" value ="{{ .Community.ID }}">
|
||||
</form>
|
||||
<span class="title"><a href="{{proxy .Community.ActorID}}">c/{{fullcname .Community}}: {{.Community.Title}}</a></span>
|
||||
<span class="title"><a href="{{ if .Community.Local }}./c/{{.Community.Name}}{{else}}{{ localize .Community.ActorID }}{{end}}">c/{{fullcname .Community}}: {{.Community.Title}}</a></span>
|
||||
<div class="details">
|
||||
{{ if .Community.Description.IsValid }}
|
||||
<div class="description">
|
||||
{{markdown "poop" .Community.Description.String}}
|
||||
{{ markdown "" .Community.Description.String }}
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="gray">
|
||||
{{printer .Counts.Subscribers}} subscribers,
|
||||
{{ if .Counts.Subscribers }}{{ printer .Counts.Subscribers }} subscribers,{{end}}
|
||||
a community founded {{ humanize .Community.Published.Time }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
45
templates/create_comment.html
Normal file
45
templates/create_comment.html
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<form class="savecomment" method="POST" enctype="multipart/form-data"
|
||||
{{- if .CommentID }} action="/{{.Host}}/comment/{{.CommentID}}"
|
||||
{{- else }} action="/{{.Host}}/post/{{.PostID}}"
|
||||
{{- end -}}
|
||||
>
|
||||
<div class="upload">
|
||||
<label title="upload photo"><div>📷</div>
|
||||
<input class="imgupload" type="file" name="file" accept="image/*">
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<textarea name="content">
|
||||
{{- if .Content }}
|
||||
{{- .Content -}}
|
||||
{{ else if and (eq .Op "edit") .Comments }}
|
||||
{{- (index .Comments 0).P.Comment.Content -}}
|
||||
{{ end -}}
|
||||
</textarea>
|
||||
</div>
|
||||
{{ if eq .Op "edit" }}
|
||||
<input type="hidden" name="op" value="edit_comment">
|
||||
<input type="hidden" name="commentid" value="{{.CommentID}}">
|
||||
{{ else }}
|
||||
<input type="hidden" name="op" value="create_comment">
|
||||
{{ end }}
|
||||
<input type="hidden" name="parentid" value="{{.CommentID}}">
|
||||
<input type="submit" name="submit" value="save">
|
||||
{{ if or .Op .Content }}
|
||||
<input type="submit" name="submit" value="cancel">
|
||||
{{ end }}
|
||||
<div class="right">
|
||||
<a href="https://join-lemmy.org/docs/users/02-media.html" target="_blank">formatting help</a>
|
||||
<input name="submit" type="submit" value="preview">
|
||||
</div>
|
||||
{{ if .Content }}
|
||||
<div class="preview">
|
||||
<div class="comment">
|
||||
<h3>Preview</h3>
|
||||
<div class="content">
|
||||
{{ markdown .Host .Content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</form>
|
||||
|
|
@ -2,26 +2,12 @@
|
|||
<head>
|
||||
<title>{{ if and .Community (ne .Community.CommunityView.Community.Title "")}}{{.Community.CommunityView.Community.Title}}{{else if ne .CommunityName ""}}/c/{{.CommunityName}}{{ else if .User}}overview for {{.User.PersonView.Person.Name}}{{else}}{{ host .Host }}{{end}}</title>
|
||||
<link rel="shortcut icon" href="/{{.Host}}/icon.jpg">
|
||||
<link rel="stylesheet" href="/_/static/style.css?v=21">
|
||||
<link rel="stylesheet" href="/_/static/style.css?v={{ .Version }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body {{ if .Dark }}class="dark"{{end}}>
|
||||
<noscript>
|
||||
<style>
|
||||
.expando-button,
|
||||
.minimize,
|
||||
#loadmore,
|
||||
#showimages,
|
||||
.hidechildren {
|
||||
display: none;
|
||||
}
|
||||
div.pager {
|
||||
display: block;
|
||||
}
|
||||
.post .expando .image img {
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/_/static/noscript.css?v={{ .Version }}">
|
||||
</noscript>
|
||||
|
||||
{{ template "nav.html" . -}}
|
||||
|
|
@ -29,12 +15,15 @@
|
|||
{{ template "nsfw.html" }}
|
||||
{{ else }}
|
||||
<main>
|
||||
{{ if or (contains .Sort "Top") (and (not .PostID) (not .User) (not .Community) (not .Activities) (eq .Op ""))}}
|
||||
{{ template "menu.html" . }}
|
||||
{{ end}}
|
||||
{{ template "menu.html" . }}
|
||||
|
||||
{{ if .Error }}
|
||||
<div class="error">{{.Error}}</div>
|
||||
<div class="error">
|
||||
{{.Error}}.
|
||||
{{ if .Unknown }}
|
||||
try remote instance: <a href="{{ .Unknown }}">{{ .Unknown }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ range .Posts }}
|
||||
|
|
@ -56,6 +45,6 @@
|
|||
{{ template "sidebar.html" . }}
|
||||
</main>
|
||||
{{ end }}
|
||||
<script src="/_/static/utils.js?v=17"></script>
|
||||
<script src="/_/static/utils.js?v={{ .Version }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<head>
|
||||
<title>{{ host .Host }}: sign up or log in</title>
|
||||
<link rel="shortcut icon" href="/{{.Host}}/icon.jpg">
|
||||
<link rel="stylesheet" href="/_/static/style.css?v=7">
|
||||
<link rel="stylesheet" href="/_/static/style.css?v={{ .Version }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body {{ if .Dark }}class="dark"{{end}}>
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
{{ if ne .Op "2fa" }}
|
||||
<div>
|
||||
<h2>create a new account</h2>
|
||||
<form method="POST">
|
||||
<form method="POST" action="/{{ .Host}}/login">
|
||||
<label>
|
||||
username
|
||||
<div><input required name="username" type="text"></div>
|
||||
|
|
|
|||
|
|
@ -3,23 +3,11 @@
|
|||
<title>{{if and .Posts .PostID }}{{ (index .Posts 0).Post.Name}} : {{.CommunityName}}{{else if and .Community (ne .Community.CommunityView.Community.Title "")}}{{.Community.CommunityView.Community.Title}}{{else if ne .CommunityName ""}}/c/{{.CommunityName}}{{ else if .User}}overview for {{.User.PersonView.Person.Name}}{{else}}{{ host .Host }}{{end}}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="shortcut icon" href="/{{.Host}}/icon.jpg">
|
||||
<link rel="stylesheet" href="/_/static/style.css?v=21">
|
||||
<link rel="stylesheet" href="/_/static/style.css?v={{ .Version }}">
|
||||
</head>
|
||||
<body{{ if .Dark }} class="dark"{{end}}>
|
||||
<noscript>
|
||||
<style>
|
||||
.scripting,
|
||||
.expando-button,
|
||||
.minimize,
|
||||
#showimages,
|
||||
#lmc,
|
||||
.hidechildren {
|
||||
display: none !important;
|
||||
}
|
||||
.post .expando .image img {
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/_/static/noscript.css?v={{ .Version }}">
|
||||
</noscript>
|
||||
|
||||
{{ template "nav.html" . -}}
|
||||
|
|
@ -27,12 +15,14 @@
|
|||
{{ template "nsfw.html" }}
|
||||
{{ else }}
|
||||
<main>
|
||||
{{ if or (contains .Sort "Top") (and (not .PostID) (not .User) (not .Community) (not .Activities) (eq .Op ""))}}
|
||||
{{ if or (.Query) (.SearchType) (and (not .PostID) (not .User) (not .Activities) (eq .Op ""))}}
|
||||
{{ template "menu.html" . }}
|
||||
{{ end}}
|
||||
|
||||
{{ if or (ne .Query "") .Communities }}
|
||||
<form class="search" method="GET">
|
||||
<input type="hidden" name="sort" value="{{.Sort}}">
|
||||
<input type="hidden" name="listingType" value="{{.Listing}}">
|
||||
<div>search</div>
|
||||
<div class="query">
|
||||
<input type="text" name="q" value="{{.Query}}">
|
||||
|
|
@ -56,7 +46,12 @@
|
|||
{{ end}}
|
||||
|
||||
{{ if .Error }}
|
||||
<div class="error">{{.Error}}</div>
|
||||
<div class="error">
|
||||
{{.Error}}.
|
||||
{{ if .Unknown }}
|
||||
try remote instance: <a href="{{ .Unknown }}">{{ .Unknown }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ range .Communities }}
|
||||
|
|
@ -85,20 +80,16 @@
|
|||
{{if .Comments}}{{if gt .Page 1}}(page {{ .Page }}) {{else if lt (index .Posts 0).Counts.Comments .CommentCount }}all{{else}}top{{end}} {{.CommentCount}} comments{{else}} no comments (yet){{end}}
|
||||
<div>
|
||||
sorted by:
|
||||
<a {{ if eq .Sort "Hot"}}class="selected"{{end}} href="{{ .SortBy "Hot"}}">hot</a>
|
||||
<a {{ if eq .Sort "Top"}}class="selected"{{end}} href="{{ .SortBy "Top"}}">top</a>
|
||||
<a {{ if eq .Sort "New"}}class="selected"{{end}} href="{{ .SortBy "New"}}">new</a>
|
||||
<a {{ if eq .Sort "Old"}}class="selected"{{end}} href="{{ .SortBy "Old"}}">old</a>
|
||||
<a {{ if eq .CommentSort "Hot"}}class="selected"{{end}} href="{{ .SortBy "Hot"}}">hot</a>
|
||||
<a {{ if eq .CommentSort "Top"}}class="selected"{{end}} href="{{ .SortBy "Top"}}">top</a>
|
||||
<a {{ if eq .CommentSort "New"}}class="selected"{{end}} href="{{ .SortBy "New"}}">new</a>
|
||||
<a {{ if eq .CommentSort "Old"}}class="selected"{{end}} href="{{ .SortBy "Old"}}">old</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ if and .Session (ne .Op "edit_post") }}
|
||||
<form class="savecomment" method="POST">
|
||||
<div>
|
||||
<textarea required name="content" {{ if (index .Posts 0).Post.Deleted }} disabled {{end}}></textarea>
|
||||
</div>
|
||||
<input type="hidden" name="op" value="create_comment">
|
||||
<input type="submit" name="submit" value="save"{{ if (index .Posts 0).Post.Deleted }} disabled {{end}}>
|
||||
</form>
|
||||
<div class="create_comment">
|
||||
{{ template "create_comment.html" .}}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end}}
|
||||
|
|
@ -108,7 +99,7 @@
|
|||
{{ template "comment.html" $comment }}
|
||||
{{ end }}
|
||||
|
||||
{{ if (and .Comments (gt (index .Posts 0).Counts.Comments .CommentCount)) }}
|
||||
{{ if and .Comments .Posts (gt (index .Posts 0).Counts.Comments .CommentCount) (not .CommentID)}}
|
||||
<div class="morecomments">
|
||||
<a id="lmc" href="" data-page="2">load more comments</a>
|
||||
</div>
|
||||
|
|
@ -133,7 +124,10 @@
|
|||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<script src="/_/static/utils.js?v=17"></script>
|
||||
<script src="/_/static/utils.js?v={{ .Version }}"></script>
|
||||
{{ if .Watch }}
|
||||
<script src="/_/static/ws.js"></script>
|
||||
{{ end }}
|
||||
{{ template "sidebar.html" . }}
|
||||
</main>
|
||||
{{ end }}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,18 @@
|
|||
{{ end }}
|
||||
{{ if contains .Sort "Top" }}
|
||||
links from past:
|
||||
<a {{ if eq .Sort "TopHour"}}class="selected"{{end}} href="{{ .SortBy "TopHour"}}">1h</a>
|
||||
<span>-</span>
|
||||
<a {{ if eq .Sort "TopSixHour"}}class="selected"{{end}} href="{{ .SortBy "TopSixHour"}}">6h</a>
|
||||
<span>-</span>
|
||||
<a {{ if eq .Sort "TopTwelveHour"}}class="selected"{{end}} href="{{ .SortBy "TopTwelveHour"}}">12h</a>
|
||||
<span>-</span>
|
||||
<a {{ if eq .Sort "TopDay"}}class="selected"{{end}} href="{{ .SortBy "TopDay"}}">day</a>
|
||||
<span>-</span>
|
||||
<a {{ if eq .Sort "TopMonth"}}class="selected"{{end}} href="{{ .SortBy "TopMonth"}}">month</a>
|
||||
<span>-</span>
|
||||
<a {{ if eq .Sort "TopWeek"}}class="selected"{{end}} href="{{ .SortBy "TopWeek"}}">week</a>
|
||||
<span>-</span>
|
||||
<a {{ if eq .Sort "TopMonth"}}class="selected"{{end}} href="{{ .SortBy "TopMonth"}}">month</a>
|
||||
<span>-</span>
|
||||
<a {{ if eq .Sort "TopYear"}}class="selected"{{end}} href="{{ .SortBy "TopYear"}}">year</a>
|
||||
<span>-</span>
|
||||
<a {{ if eq .Sort "TopAll"}}class="selected"{{end}} href="{{ .SortBy "TopAll"}}">all time</a>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
{{ $state := . }}
|
||||
<nav>
|
||||
<div class="communities">
|
||||
{{ if .Session }}
|
||||
<a id="openmycommunities" href="/{{.Host}}/search?searchtype=Communities&listingType=Subscribed&sort=TopMonth&page=0">my communities ▼</a>
|
||||
{{ end }}
|
||||
<a href="/{{.Host}}">home</a>
|
||||
<span> - </span>
|
||||
<a href="/{{.Host}}?listingType=All">all</a>
|
||||
|
|
@ -9,7 +13,15 @@
|
|||
<a href="/{{$host}}/c/{{fullcname $c.Community}}">{{$c.Community.Name}}</a>
|
||||
<span> - </span>
|
||||
{{ end }}
|
||||
<a href="/{{$host}}/search?searchtype=Communities" class="more">more »</a>
|
||||
<a href="/{{$host}}/search?searchtype=Communities&sort=TopMonth" class="more">more »</a>
|
||||
</div>
|
||||
<div id="mycommunities">
|
||||
{{- if and .Session .Session.Communities }}
|
||||
<a href="/{{.Host}}/search?searchtype=Communities&listingType=Subscribed&sort=TopMonth&page=0">view all »</a>
|
||||
{{ range .Session.Communities }}
|
||||
<a href="/{{ $state.Host}}/{{ if .Community.Local }}c/{{.Community.Name}}{{else}}{{ localize .Community.ActorID }}{{end}}">{{fullcname .Community }}</a>
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
</div>
|
||||
<div class="right">
|
||||
{{ if .Session }}
|
||||
|
|
@ -48,14 +60,14 @@
|
|||
<span>: search</span>
|
||||
{{ end }}
|
||||
<ul>
|
||||
{{ if .User }}
|
||||
{{ if and .User (not .Query)}}
|
||||
<li {{if eq .Op "" }}class="selected"{{end}}><a href="?">overview</a></li>
|
||||
{{ if and .Session (eq .User.PersonView.Person.ID .Session.UserID) }}
|
||||
<li {{if eq .Op "Saved"}}class="selected"{{end}}><a href="?view=Saved">saved</a></li>
|
||||
{{ end }}
|
||||
{{ else if .Comments -}}
|
||||
<li class="selected"><a href="">comments</a></li>
|
||||
{{ else if .Activities }}
|
||||
{{ else if and .Activities (not .Query) }}
|
||||
<li class="selected"><a href="">mailbox</a></li>
|
||||
{{ else }}
|
||||
<li{{ if eq .Sort "Hot" }} class="selected"{{end}}><a href="{{ .SortBy "Hot" }}">hot</a></li>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{{ if and (ne .State.Op "vote_post") (ne .State.Op "save_post") }}
|
||||
<div class="post{{if .Post.Deleted}} deleted{{end}}{{ if or .Post.FeaturedCommunity .Post.FeaturedLocal }} distinguished{{end}}">
|
||||
<div class="post{{if .Post.Deleted}} deleted{{end}}{{ if .Post.FeaturedCommunity }} distinguished{{end}}{{if .Post.FeaturedLocal }} announcement{{end}}" id="p{{.Post.ID}}">
|
||||
{{ if gt .Rank 0 }}
|
||||
<div class="rank"> {{ .Rank }} </div>
|
||||
{{ end }}
|
||||
|
|
@ -23,17 +23,19 @@
|
|||
{{ end }}
|
||||
{{ if and (ne .State.Op "vote_post") (ne .State.Op "save_post") }}
|
||||
</div>
|
||||
{{ if not .State.HideThumbnails }}
|
||||
<div class="thumb">
|
||||
<a class="url" href="{{ if .Post.URL.IsValid }}{{ .Post.URL }}{{ else }}/{{ .State.Host }}/post/{{ .Post.ID }}{{ end }}">
|
||||
<div {{ if and .Post.NSFW (not (and .State.Community .State.Community.CommunityView.Community.NSFW))}}class="img-blur"{{end}} style="background-image: url({{thumbnail .Post}})"></div>
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="entry">
|
||||
<div class="title">
|
||||
<a class="url" href="{{ if .Post.URL.IsValid }}{{ .Post.URL }}{{ else }}/{{ .State.Host }}/post/{{ .Post.ID }}{{ end }}">{{ rmmarkdown .Post.Name }}</a>
|
||||
({{ domain . }})
|
||||
</div>
|
||||
<div class="expando-button {{ if and (not (and .Post.Body.IsValid .Post.Body.String )) (not (isImage .Post.URL.String)) }}hidden{{else if eq .Rank 0}}open{{ end }}"></div>
|
||||
<div class="expando-button{{ if and (not (and .Post.Body.IsValid .Post.Body.String )) (not (isImage .Post.URL.String)) }} hidden{{else if eq .Rank 0}} open{{ end }}"></div>
|
||||
<div class="meta">
|
||||
submitted
|
||||
<span title="{{.Post.Published.Time}}">{{ humanize .Post.Published.Time -}}</span>
|
||||
|
|
@ -41,13 +43,26 @@
|
|||
* (last edited <span title="{{.Post.Updated.Time}}">{{ humanize .Post.Updated.Time }}</span>)
|
||||
{{ end }}
|
||||
by
|
||||
<a href="/{{ .State.Host }}/u/{{ fullname .Creator }}">{{ fullname .Creator }}</a>
|
||||
<a class="submitter{{ if .Creator.Admin}} admin{{end}}" href="/{{ .State.Host }}/u/{{ fullname .Creator }}">
|
||||
{{- if .State.HideInstanceNames -}}
|
||||
{{ .Creator.Name }}
|
||||
{{- else -}}
|
||||
{{ fullname .Creator }}
|
||||
{{- end -}}
|
||||
</a>
|
||||
to
|
||||
<a href="/{{ .State.Host }}/c/{{ fullcname .Community }}">{{ fullcname .Community}}</a>
|
||||
<a href="/{{ .State.Host }}/c/{{ fullcname .Community }}">
|
||||
c/{{ if .State.HideInstanceNames -}}
|
||||
{{ .Community.Name }}
|
||||
{{ else -}}
|
||||
{{ fullcname .Community }}
|
||||
{{ end }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
{{ if .Post.NSFW }}<span class="nsfw">NSFW</span>{{end}}
|
||||
<a href="/{{ .State.Host }}/post/{{ .Post.ID }}">{{ .Counts.Comments }} comments</a>
|
||||
<a href="{{ .Post.ApID}}">fedilink</a>
|
||||
{{ if and .State.Session (eq .State.Session.UserID .Post.CreatorID) }}
|
||||
{{ if not .Post.Deleted }}<a href="/{{ .State.Host }}/post/{{ .Post.ID }}?edit">edit</a>{{end}}
|
||||
<form class="link-btn" method="POST">
|
||||
|
|
@ -79,11 +94,22 @@
|
|||
{{ if .State.PostID }}
|
||||
<a id="hidechildren" class="scripting" href="">hide all child comments</a>
|
||||
{{ end }}
|
||||
{{ if and .State.Site .State.Site.MyUser.IsValid (not .State.Site.MyUser.MustValue.LocalUserView.LocalUser.ShowReadPosts) }}
|
||||
<form class="link-btn" method="POST">
|
||||
<input type="hidden" name="postid" value="{{.Post.ID }}">
|
||||
<input type="hidden" name="op" value="read_post">
|
||||
{{ if .Post.Deleted }}
|
||||
<input type="submit" name="submit" value="mark unread">
|
||||
{{ else }}
|
||||
<input type="submit" name="submit" value="mark read">
|
||||
{{ end }}
|
||||
</form>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
<div class="clearleft"></div>
|
||||
<div class="expando {{ if eq .Rank 0 }}open{{ end}}">
|
||||
<div class="expando{{ if eq .Rank 0 }} open{{ end}}">
|
||||
{{ if (and .Post.Body.IsValid (ne .Post.Body.String "")) }}
|
||||
<div class="md">{{ markdown .State.Host .Post.Body.String }}</div>
|
||||
{{ end }}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,12 @@
|
|||
<head>
|
||||
<title>{{ host .Host }}: preferences</title>
|
||||
<link rel="shortcut icon" href="/{{.Host}}/icon.jpg">
|
||||
<link rel="stylesheet" href="/_/static/style.css?v=15">
|
||||
<link rel="stylesheet" href="/_/static/style.css?v={{ .Version }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body {{ if .Dark}}class="dark"{{end}}>
|
||||
<noscript>
|
||||
<style>
|
||||
.scripting {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/_/static/noscript.css?v={{ .Version }}">
|
||||
</noscript>
|
||||
<nav>
|
||||
<div class="communities">
|
||||
|
|
@ -74,7 +70,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<label>
|
||||
default sort
|
||||
default post sort
|
||||
</label>
|
||||
<select name="DefaultSortType">
|
||||
<option value="Hot"{{ if eq .Sort "Hot"}} selected{{end}}>Hot</option>
|
||||
|
|
@ -93,6 +89,17 @@
|
|||
<option value="TopAll"{{ if eq .Sort "TopAll"}} selected{{end}}>Top All Time</option></select>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
default comment sort
|
||||
</label>
|
||||
<select name="DefaultCommentSortType">
|
||||
<option value="Hot"{{ if eq .CommentSort "Hot"}} selected{{end}}>Hot</option>
|
||||
<option value="New"{{ if eq .CommentSort "New"}} selected{{end}}>New</option>
|
||||
<option value="Old"{{ if eq .CommentSort "Old"}} selected{{end}}>Old</option>
|
||||
<option value="Top"{{ if eq .CommentSort "Top"}} selected{{end}}>Top</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
dark mode
|
||||
|
|
@ -112,13 +119,25 @@
|
|||
<input type="checkbox" name="autoLoad">
|
||||
</div>
|
||||
<div>
|
||||
<label></label>
|
||||
<label>
|
||||
hide instance names
|
||||
</label>
|
||||
<input type="checkbox" name="hideInstanceNames" {{ if .HideInstanceNames }}checked{{end}}>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
hide thumbnails
|
||||
</label>
|
||||
<input type="checkbox" name="hideThumbnails" {{ if .HideThumbnails }}checked{{end}}>
|
||||
</div>
|
||||
<div>
|
||||
<label>lemmy: {{ .Site.Version }}<br><a href="https://github.com/rystaf/mlmym">mlmym</a>: {{ .Version }}</label>
|
||||
<input type="submit" value="save">
|
||||
{{ if .XHR }}<input id="closesettings" type="submit" value="close">{{ end }}
|
||||
</div>
|
||||
</form>
|
||||
{{ if not .XHR}}
|
||||
<script src="/_/static/utils.js?v=8"></script>
|
||||
<script src="/_/static/utils.js?v={{ .Version }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
{{ $host := .Host }}
|
||||
<div class="{{ if .User }}user {{end}}side">
|
||||
|
||||
{{ if not .SearchType }}
|
||||
<form method="GET" action="/{{.Host}}/search">
|
||||
<input type="text" placeholder="search" name="q" value="{{.Query}}">
|
||||
<input type="text" placeholder="search" name="q" value="">
|
||||
{{ if .User }}
|
||||
<input type="hidden" name="username" value="{{.UserName}}">
|
||||
{{ else if .Community }}
|
||||
<input type="hidden" name="communityname" value="{{.CommunityName}}">
|
||||
<input type="hidden" name="communityname" value="{{fullcname .Community.CommunityView.Community}}">
|
||||
{{ end }}
|
||||
<input type="hidden" name="sort" value="New">
|
||||
</form>
|
||||
{{ end }}
|
||||
|
||||
{{ if .User }}
|
||||
<h1>{{ .User.PersonView.Person.Name }}</h1>
|
||||
|
|
@ -91,8 +90,14 @@
|
|||
<input name="op" type="submit" value="{{ membership .Community.CommunityView.Subscribed}}">
|
||||
<input name="communityid" type="hidden" value="{{ .Community.CommunityView.Community.ID }}">
|
||||
</form>
|
||||
<form method="POST" class="block {{ if .Community.CommunityView.Blocked }}unblock{{end}}">
|
||||
<input name="op" type="submit" value="{{ if .Community.CommunityView.Blocked}}unblock{{else}}block{{end}}">
|
||||
<input name="communityid" type="hidden" value="{{ .Community.CommunityView.Community.ID }}">
|
||||
</form>
|
||||
{{ end }}
|
||||
{{ .Community.CommunityView.Counts.Subscribers }} readers <br>
|
||||
<div>
|
||||
{{ .Community.CommunityView.Counts.Subscribers }} readers
|
||||
</div>
|
||||
<span class="green" title="Users active in the last day"></span>
|
||||
{{ .Community.CommunityView.Counts.UsersActiveDay }} users here now
|
||||
{{ if and .Session (isMod .Community .Session.UserName) }}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
{{ $state := . }}
|
||||
{{ if or .PostID .CommentID }}
|
||||
{{ range $i, $comment := .Comments }}
|
||||
{{ template "comment.html" $comment }}
|
||||
{{ end }}
|
||||
{{ else if .Activities }}
|
||||
{{ template "activities.html" . }}
|
||||
{{ else }}
|
||||
{{ else if .Posts }}
|
||||
{{ range $post := .Posts }}
|
||||
{{ template "post.html" $post }}
|
||||
{{ end }}
|
||||
{{ else if .Communities }}
|
||||
{{ range .Communities }}
|
||||
{{ if not $state.Page }}
|
||||
<a href="/{{ $state.Host}}/{{ if .Community.Local }}c/{{.Community.Name}}{{else}}{{ localize .Community.ActorID }}{{end}}">{{fullcname .Community }}</a>
|
||||
{{ else }}
|
||||
{{ template "community.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ end }}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue