Compare commits
75 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 | ||
|
|
7b423eebb7 | ||
|
|
ca47acec84 | ||
|
|
dcc3cd4d49 | ||
|
|
b9cf0c4835 | ||
|
|
36df7969e6 | ||
|
|
919a114f99 | ||
|
|
7ca1e23acf | ||
|
|
bf0bc421cd | ||
|
|
6685badaca | ||
|
|
34f349313f | ||
|
|
08a7ec66d4 | ||
|
|
f2ca245b13 | ||
|
|
1b5c6f619e | ||
|
|
d907420e58 | ||
|
|
0a4888906a | ||
|
|
e46820ed3f | ||
|
|
48d12530fe | ||
|
|
4fcfda174d | ||
|
|
2a9a54f595 | ||
|
|
7760d7c226 | ||
|
|
8e270007f4 | ||
|
|
4991a2e5ed | ||
|
|
f0754e3b20 | ||
|
|
7c5ab065d0 | ||
|
|
3d23c96166 | ||
|
|
ae0c8427d2 | ||
|
|
834d8170d1 | ||
|
|
88457d0818 | ||
|
|
2b5349466a | ||
|
|
c105dfc900 | ||
|
|
95195d00d7 | ||
|
|
6e121dd63c | ||
|
|
3643600cab | ||
|
|
f1f2305e82 | ||
|
|
5a24937781 | ||
|
|
6184bddd02 | ||
|
|
1e0f1fc184 | ||
|
|
8668878548 |
30 changed files with 1936 additions and 464 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +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"
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -1,16 +1,25 @@
|
|||
# mlmym
|
||||
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 |
|
||||
|
||||
|
|
|
|||
23
go.mod
23
go.mod
|
|
@ -3,15 +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/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
|
||||
)
|
||||
|
|
|
|||
42
go.sum
42
go.sum
|
|
@ -2,39 +2,33 @@ 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/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/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/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/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/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-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=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
|||
50
main.go
50
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
|
||||
|
|
@ -48,10 +50,13 @@ func NewAddHeaderTransport(remoteAddr string) *AddHeaderTransport {
|
|||
}
|
||||
|
||||
func init() {
|
||||
md = goldmark.New(goldmark.WithExtensions(extension.Linkify))
|
||||
md = goldmark.New(goldmark.WithExtensions(
|
||||
extension.Linkify,
|
||||
extension.Table,
|
||||
))
|
||||
templates = make(map[string]*template.Template)
|
||||
if !*watch {
|
||||
for _, name := range []string{"index.html", "login.html", "frontpage.html", "root.html", "settings.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 {
|
||||
|
|
@ -61,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;
|
||||
}
|
||||
390
public/style.css
390
public/style.css
|
|
@ -10,6 +10,9 @@ body.dark {
|
|||
code {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
.dark a {
|
||||
color: #8cb3d9;
|
||||
}
|
||||
|
|
@ -24,24 +27,38 @@ code {
|
|||
.clearleft {
|
||||
clear: left;
|
||||
}
|
||||
.img-blur {
|
||||
filter: blur(10px);
|
||||
-webkit-filter: blur(10px);
|
||||
-moz-filter: blur(10px);
|
||||
-o-filter: blur(10px);
|
||||
-ms-filter: blur(10px);
|
||||
transform: scale(1.03);
|
||||
}
|
||||
.post {
|
||||
margin: 6px;
|
||||
margin: 6px 0px;
|
||||
}
|
||||
.post .thumb {
|
||||
height: 52px;
|
||||
width: 70px;
|
||||
margin: 0px 4px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
--background-color: #e7e7e7;
|
||||
}
|
||||
.post .thumb div {
|
||||
height: 52px;
|
||||
width: 70px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
--background-color: #e7e7e7;
|
||||
}
|
||||
.rank {
|
||||
color: #c6c6c6;
|
||||
font-size: medium;
|
||||
font-size: 14px;
|
||||
margin-top: 17px;
|
||||
text-align: right;
|
||||
min-width: 19px;
|
||||
min-width: 21px;
|
||||
}
|
||||
.dark .post .rank {
|
||||
color: #646464;
|
||||
|
|
@ -57,52 +74,67 @@ code {
|
|||
color: #646464;
|
||||
}
|
||||
.comment .score {
|
||||
margin-right: 2px;
|
||||
clear:left;
|
||||
margin-right: 4px;
|
||||
overflow:hidden;
|
||||
}
|
||||
.comment .content {
|
||||
line-height: 20px;
|
||||
overflow:hidden;
|
||||
max-width: 840px;
|
||||
}
|
||||
.comment.hidden {
|
||||
padding-bottom:5px;
|
||||
}
|
||||
.comment.hidden .score {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
.score form.link-btn input {
|
||||
display: block;
|
||||
display: inline-block;
|
||||
color: #b7b7b7;
|
||||
font-size: 20px;
|
||||
line-height: 16px;
|
||||
line-height: 15px;
|
||||
}
|
||||
.dark .score form.link-btn input {
|
||||
color: #646464;
|
||||
}
|
||||
.comment form.link-btn input {
|
||||
.comment .score form.link-btn input {
|
||||
line-height: 17px;
|
||||
}
|
||||
.score form.like.link-btn input:first-child, .score .like div {
|
||||
color: orangered;
|
||||
color: #3880ff;
|
||||
}
|
||||
.score form.dislike.link-btn input:last-child, .score .dislike div {
|
||||
color: #8080FF;
|
||||
color: #eb445a;
|
||||
}
|
||||
|
||||
.score form div {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.title a {
|
||||
color: #0000ff;
|
||||
font-size: medium;
|
||||
text-decoration: none;
|
||||
}
|
||||
.title a p {
|
||||
display: inline;
|
||||
}
|
||||
.post.distinguished .title a, .post.announcement .title a,
|
||||
.dark .post.distinguished .title a:visited, .post.announcement .title a:visited {
|
||||
color: #228822;
|
||||
font-weight: bold;
|
||||
}
|
||||
.dark .title a {
|
||||
color: #dedede;
|
||||
}
|
||||
.dark .title a:visited {
|
||||
color: #a6a6a6
|
||||
}
|
||||
.post.deleted .title a {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
[disabled] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
#loadmore [disabled], .link-btn [disabled] {
|
||||
cursor: wait;
|
||||
}
|
||||
.post .title {
|
||||
color: #888;
|
||||
font-size: 10px;
|
||||
|
|
@ -131,8 +163,8 @@ code {
|
|||
.message b {
|
||||
color: #000;
|
||||
}
|
||||
.meta {
|
||||
color: #888;
|
||||
.dark .message b {
|
||||
color: #ddd;
|
||||
}
|
||||
.dark .meta {
|
||||
color: #b4b4b4;
|
||||
|
|
@ -140,10 +172,10 @@ code {
|
|||
|
||||
.comment {
|
||||
font-size: 14px;
|
||||
margin: 0px 0px 5px 15px;
|
||||
margin: 0px 0px 5px 0px;
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 3px;
|
||||
padding: 5px 10px 5px 5px;
|
||||
padding: 10px 10px 0px 7px;
|
||||
}
|
||||
.dark .comment {
|
||||
border-color: #333;
|
||||
|
|
@ -155,7 +187,6 @@ code {
|
|||
max-height: 300px;
|
||||
}
|
||||
.comment .comment {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.comment .comment,
|
||||
.comment .comment .comment .comment,
|
||||
|
|
@ -186,9 +217,30 @@ code {
|
|||
}
|
||||
.comment .meta {
|
||||
font-size: 10px;
|
||||
margin-bottom: 3px;
|
||||
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;
|
||||
|
|
@ -197,6 +249,13 @@ code {
|
|||
.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;
|
||||
}
|
||||
|
|
@ -207,29 +266,65 @@ code {
|
|||
border-radius: 3px;
|
||||
padding: 0px 2px;
|
||||
}
|
||||
.comment .meta a.distinguished {
|
||||
background-color: #228822;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border-radius: 3px;
|
||||
padding: 0px 2px;
|
||||
}
|
||||
.commentmenu {
|
||||
font-size: 16px;
|
||||
margin: 0px 0px 10px 10px;
|
||||
margin: 0px 0px 10px 0px;
|
||||
}
|
||||
.commentmenu div {
|
||||
border-top: 1px dotted gray;
|
||||
font-size: 12px;
|
||||
}
|
||||
.savecomment {
|
||||
margin: 10px;
|
||||
form.savecomment {
|
||||
margin: 0px 0px 10px 0px;
|
||||
width: 500px;
|
||||
}
|
||||
.comment > .children > form.savecomment {
|
||||
margin: 0px 0px 10px 20px;
|
||||
}
|
||||
.comment .children {
|
||||
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;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.comment.hidden {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.comment.hidden .meta a {
|
||||
color: gray;
|
||||
font-weight: 400;
|
||||
|
|
@ -238,11 +333,15 @@ code {
|
|||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
.comment.hidden .content, .comment.hidden .children {
|
||||
.comment.hidden .content, .comment.hidden .children, .comment.hidden .morecomments {
|
||||
display: none;
|
||||
}
|
||||
.children .morecomments {
|
||||
}
|
||||
.morecomments {
|
||||
margin: 10px 0px;
|
||||
height: 20px;
|
||||
clear: left;
|
||||
margin: 0px 0px 10px 0px;
|
||||
font-size: 10px;
|
||||
}
|
||||
.morecomments a {
|
||||
|
|
@ -253,11 +352,11 @@ code {
|
|||
.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;
|
||||
|
|
@ -269,10 +368,10 @@ code {
|
|||
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 {
|
||||
|
|
@ -281,9 +380,37 @@ code {
|
|||
.left {
|
||||
float: left;
|
||||
}
|
||||
span.nsfw {
|
||||
color: #d10023;
|
||||
font-size: 10px;
|
||||
line-height: 14px;
|
||||
border-radius:3px;
|
||||
border: 1px solid #d10023;
|
||||
padding: 0 4px;
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
}
|
||||
form.nsfw {
|
||||
text-align: center;
|
||||
margin: 50px auto;
|
||||
width: 650px;
|
||||
font-size: 18px;
|
||||
}
|
||||
form.nsfw div {
|
||||
font-size: 40px;
|
||||
background-color: #ff575b;
|
||||
display: inline-block;
|
||||
padding: 20px 10px;
|
||||
border-radius: 50%;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
.gray {
|
||||
color: #808080;
|
||||
}
|
||||
.loading {
|
||||
color: red !important;
|
||||
}
|
||||
.error {
|
||||
color: red;
|
||||
font-size: 13px;
|
||||
|
|
@ -306,7 +433,7 @@ code {
|
|||
top: 1px;
|
||||
}
|
||||
.pager {
|
||||
margin: 10px;
|
||||
margin: 20px 0px;
|
||||
}
|
||||
.pager a {
|
||||
padding: 1px 4px;
|
||||
|
|
@ -317,6 +444,21 @@ code {
|
|||
text-decoration: none;
|
||||
color: #369;
|
||||
}
|
||||
.pager.hidden {
|
||||
display: none;
|
||||
}
|
||||
#loadmore {
|
||||
display: none;
|
||||
}
|
||||
#loadmore, #end {
|
||||
margin: 10px 0px;
|
||||
}
|
||||
#loadmore.show {
|
||||
display: block;
|
||||
}
|
||||
#end {
|
||||
visibility: hidden;
|
||||
}
|
||||
.buttons li {
|
||||
display: inline;
|
||||
}
|
||||
|
|
@ -325,8 +467,11 @@ code {
|
|||
font-size: 10px;
|
||||
padding: 0;
|
||||
}
|
||||
.comment .textarea {
|
||||
width: 100%;
|
||||
}
|
||||
.comment .buttons {
|
||||
margin: 8px 0px 3px 0px;
|
||||
margin: 3px 0px 0px 0px;
|
||||
}
|
||||
.comment.hidden .buttons {
|
||||
display: none;
|
||||
|
|
@ -336,12 +481,13 @@ code {
|
|||
border-left: 2px solid #c5c1ad;
|
||||
padding: 0 8px;
|
||||
}
|
||||
.buttons a, .buttons form input {
|
||||
.buttons a, .buttons form input, .comment .buttons form input {
|
||||
text-decoration: none;
|
||||
color: #888;
|
||||
padding-right: 4px;
|
||||
display: inline-block;
|
||||
margin-right: 5px !important;
|
||||
}
|
||||
.buttons a:hover, .title a:hover, .buttons input:hover {
|
||||
.buttons a:hover, .title a:hover, .buttons form input:hover, .comment .buttons form input:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.entry {
|
||||
|
|
@ -381,18 +527,94 @@ code {
|
|||
.expando-button:hover{
|
||||
background-color: #466599;
|
||||
}
|
||||
.expando-button.hidden {
|
||||
.expando-button.hidden, .children.hidden .comment, .children.hidden .morecomments {
|
||||
display: none;
|
||||
}
|
||||
.hidechildren .show {
|
||||
display: none;
|
||||
}
|
||||
.hidechildren.hidden .show {
|
||||
display: inline;
|
||||
}
|
||||
.hidechildren.hidden .hide {
|
||||
display: none;
|
||||
}
|
||||
.hidechildren span {
|
||||
pointer-events: none;
|
||||
}
|
||||
.expando {
|
||||
display: none;
|
||||
max-width: 587px;
|
||||
max-width: 870px;
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
color: #000;
|
||||
}
|
||||
#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 #mycommunities {
|
||||
background-color: #262626;
|
||||
}
|
||||
#settingspopup.open, #mycommunities.open {
|
||||
display: inline-block;
|
||||
}
|
||||
.expando.open{
|
||||
display: block;
|
||||
}
|
||||
.expando img {
|
||||
.expando .embed {
|
||||
text-align: center;
|
||||
}
|
||||
.expando .image {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
resize: both;
|
||||
max-width: 578px;
|
||||
margin: 0 auto;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-position: top left;
|
||||
}
|
||||
.expando .image img {
|
||||
visibility: hidden;
|
||||
max-width: 100%;
|
||||
}
|
||||
.expando .md {
|
||||
|
|
@ -400,13 +622,21 @@ code {
|
|||
border: 1px solid #369;
|
||||
border-radius: 7px;
|
||||
padding: 5px 10px;
|
||||
margin: 5px 0px;
|
||||
margin: 5px auto;
|
||||
max-width: 578px;
|
||||
font-size: 14px;
|
||||
overflow: auto;
|
||||
}
|
||||
.expando .md img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.expando.open.showimage .md {
|
||||
display: none;
|
||||
}
|
||||
.dark .expando .md {
|
||||
background-color: #262626;
|
||||
color: #ddd;
|
||||
border-color: #666;
|
||||
}
|
||||
.expando p, .comment p, .message p {
|
||||
margin-top: 0;
|
||||
|
|
@ -442,24 +672,30 @@ code {
|
|||
text-align: right;
|
||||
}
|
||||
.side {
|
||||
display: none;
|
||||
margin: 0 auto;
|
||||
font-size: 12px;
|
||||
width: 300px;
|
||||
padding-right: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.side img, .md img{
|
||||
max-width: 100%;
|
||||
}
|
||||
main {
|
||||
position: relative;
|
||||
margin: 0px 10px;
|
||||
}
|
||||
@media (min-width: 900px) {
|
||||
.side {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
main {
|
||||
padding-right: 310px;
|
||||
margin-left: 10px;
|
||||
padding-right: 316px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
.side form {
|
||||
|
|
@ -544,7 +780,7 @@ main {
|
|||
color: white;
|
||||
}
|
||||
.dark .create a:hover{
|
||||
color: #1496dc;
|
||||
color: #0cbe30;
|
||||
}
|
||||
.dark .create input[type=submit],
|
||||
.dark .search .query input,
|
||||
|
|
@ -604,8 +840,8 @@ h1, h2 {
|
|||
margin-left: 36px;
|
||||
}
|
||||
nav {
|
||||
border-bottom: 1px solid #5f99cf;
|
||||
background-color: #cee3f8;
|
||||
border-bottom: 1px solid #00a846;
|
||||
background-color: #9ad59b;
|
||||
z-index: 99;
|
||||
margin-bottom: 5px;
|
||||
position: relative;
|
||||
|
|
@ -640,16 +876,20 @@ nav .communities a.more {
|
|||
font-weight: bold;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.dark nav .communities a.more {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
.orangered, .orangered b {
|
||||
color: orangered !important;
|
||||
}
|
||||
|
||||
nav a {
|
||||
nav .communities a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
nav > a:hover {
|
||||
nav .title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
|
@ -662,7 +902,7 @@ nav .title, nav > span {
|
|||
font-variant: small-caps;
|
||||
}
|
||||
.dark nav .title, .dark nav > span {
|
||||
color: #8cb3d9;;
|
||||
color: #ececec;
|
||||
}
|
||||
nav a.title {
|
||||
margin-left: 70px;
|
||||
|
|
@ -693,32 +933,37 @@ nav .icon img {
|
|||
height:100%;
|
||||
}
|
||||
|
||||
nav .tabs {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
white-space: nowrap;
|
||||
list-style: none;
|
||||
margin: 5px 2.5px 0px 2.5px;
|
||||
padding: 0;
|
||||
display: inline;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
nav li {
|
||||
white-space: nowrap;
|
||||
margin: 0px 1px;
|
||||
padding: 0px;
|
||||
display: inline;
|
||||
display: inline-block;
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
nav ul a {
|
||||
color: #369;
|
||||
background-color: #eff7ff;
|
||||
text-decoration: none;
|
||||
color: #369;
|
||||
padding: 2px 6px 0 6px;
|
||||
}
|
||||
.dark nav ul a {
|
||||
background-color: #262626;
|
||||
color: #6a98af;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.selected {
|
||||
|
|
@ -728,20 +973,23 @@ nav ul a {
|
|||
nav .selected a {
|
||||
color: orangered;
|
||||
background-color: white;
|
||||
border: 1px solid #5f99cf;
|
||||
border: 1px solid #00a846;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
.dark nav .selected a {
|
||||
color: #d25a32;
|
||||
border-bottom: 1px solid #262626;
|
||||
}
|
||||
|
||||
nav .right {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
background-color: #EFF7FF;
|
||||
background-color: #EFFFEF;
|
||||
padding: 4px 6px;
|
||||
line-height: 12px;
|
||||
border-bottom-left-radius: 7px;
|
||||
border-bottom-right-radius: 7px;
|
||||
margin-right: 10px;
|
||||
color: gray;
|
||||
font-size: 10px;
|
||||
z-index: 101;
|
||||
|
|
@ -755,12 +1003,12 @@ 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]{
|
||||
color: #8cb3d9;
|
||||
.dark nav .right a, .dark nav .right input[type=submit]{
|
||||
color: #dadada;
|
||||
}
|
||||
nav .right form, .comment form, form.link-btn {
|
||||
display: inline-block;
|
||||
|
|
@ -805,10 +1053,16 @@ nav .right form input, .comment .buttons input, form.link-btn input {
|
|||
padding: 5px 10px;
|
||||
margin: 5px 10px;
|
||||
}
|
||||
.dark .warning {
|
||||
background-color: #544400;
|
||||
}
|
||||
.highlight {
|
||||
background-color: #ffc;
|
||||
padding-left: 5px;
|
||||
}
|
||||
.dark .highlight{
|
||||
background-color: #4c4c4c;
|
||||
}
|
||||
|
||||
form.create {
|
||||
width: 520px;
|
||||
|
|
@ -843,13 +1097,21 @@ form.create input[type=file], form.create select {
|
|||
content: "*";
|
||||
color: red;
|
||||
}
|
||||
.preferences {
|
||||
margin: 20px;
|
||||
}
|
||||
.preferences div {
|
||||
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;
|
||||
|
||||
}
|
||||
|
|
|
|||
391
public/utils.js
391
public/utils.js
|
|
@ -1,26 +1,24 @@
|
|||
function request(url, params, callback) {
|
||||
function request(url, params, callback, errorcallback = function(){}) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = function() {
|
||||
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
|
||||
callback(xmlHttp.responseText);
|
||||
if (xmlHttp.readyState != 4 ) { return }
|
||||
if (xmlHttp.status == 200) {
|
||||
return callback(xmlHttp.responseText);
|
||||
}
|
||||
errorcallback(xmlHttp.responseText);
|
||||
}
|
||||
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) {
|
||||
console.log(e)
|
||||
e = e || window.event;
|
||||
if (e.target.className.indexOf("expando-button") == -1) { return }
|
||||
var targ = e.currentTarget || e.srcElement || e;
|
||||
if (targ.nodeType == 3) targ = targ.parentNode;
|
||||
var bdy = targ.getElementsByClassName("expando")[0]
|
||||
var btn = targ.getElementsByClassName("expando-button")[0]
|
||||
console.log(bdy.style.display)
|
||||
console.log(bdy.style.display.indexOf("block"))
|
||||
if (bdy.className.indexOf("open")>-1) {
|
||||
bdy.className = 'expando';
|
||||
btn.className = "expando-button"
|
||||
|
|
@ -29,26 +27,42 @@ function postClick(e) {
|
|||
bdy.className = 'expando open';
|
||||
btn.className = "expando-button open"
|
||||
var url = targ.getElementsByClassName("url")[0].href
|
||||
if (id = parse_youtube(url)) {
|
||||
targ.getElementsByClassName("embed")[0].innerHTML = youtube_iframe(id)
|
||||
if (id = parseYoutube(url)) {
|
||||
targ.getElementsByClassName("embed")[0].innerHTML = youtubeIframe(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
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=="vote") {
|
||||
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)
|
||||
if (("c"+data.get("commentid")) != targ.id) { return }
|
||||
params = new URLSearchParams(data).toString()
|
||||
params += "&" + e.target.name + "=" + e.target.value
|
||||
params += "&xhr=1"
|
||||
request(targ.target, params, function(res){
|
||||
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 }
|
||||
e.target.disabled = "disabled"
|
||||
request(targ.action || "", data,
|
||||
function(res){
|
||||
targ.outerHTML = res
|
||||
setup()
|
||||
},
|
||||
function(res){
|
||||
e.target.disabled = ""
|
||||
})
|
||||
}
|
||||
return false
|
||||
|
|
@ -67,8 +81,21 @@ function commentClick(e) {
|
|||
}
|
||||
return false
|
||||
}
|
||||
if ((e.target.className.indexOf("loadmore") != -1) ||
|
||||
(e.target.className.indexOf("edit") != -1) ||
|
||||
if (e.target.className.indexOf("hidechildren") != -1) {
|
||||
if (e.target.getAttribute("for") != targ.id) { return }
|
||||
e.preventDefault()
|
||||
var btn = targ.getElementsByClassName("hidechildren")[0]
|
||||
var children = targ.getElementsByClassName("children")[0]
|
||||
if (children.className.indexOf("hidden") == -1) {
|
||||
children.className = "children hidden"
|
||||
btn.className = "hidechildren hidden"
|
||||
} else {
|
||||
children.className = "children"
|
||||
btn.className = "hidechildren"
|
||||
}
|
||||
return false
|
||||
}
|
||||
if ((e.target.className.indexOf("edit") != -1) ||
|
||||
(e.target.className.indexOf("source") != -1) ||
|
||||
(e.target.className.indexOf("reply") != -1)) {
|
||||
var id = targ.id
|
||||
|
|
@ -76,47 +103,341 @@ function commentClick(e) {
|
|||
e.preventDefault()
|
||||
request(e.target.href+"&xhr",false, function(res){
|
||||
targ.outerHTML = res
|
||||
setup()
|
||||
})
|
||||
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) {
|
||||
e.preventDefault()
|
||||
page = e.target.getAttribute("data-page")
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
urlParams.set("xhr", "1")
|
||||
urlParams.set("page", page)
|
||||
e.target.innerHTML = "loading"
|
||||
e.target.className = "loading"
|
||||
request(window.location.origin+window.location.pathname+"?"+urlParams.toString(), "",
|
||||
function(res){
|
||||
if (res.trim()) {
|
||||
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.innerHTML = ""
|
||||
}
|
||||
}, function() {
|
||||
e.target.innerHTML = "loading failed"
|
||||
})
|
||||
return false;
|
||||
}
|
||||
function loadMore(e) {
|
||||
e.preventDefault()
|
||||
page = e.target.getAttribute("data-page")
|
||||
e.target.disabled="disabled"
|
||||
e.target.value="loading"
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
urlParams.set("xhr", "1")
|
||||
urlParams.set("page", page)
|
||||
request(window.location.origin+window.location.pathname+"?"+urlParams.toString(), "",
|
||||
function(res){
|
||||
if (res.trim()) {
|
||||
e.target.outerHTML = res + '<input id="loadmore" type="submit" data-page="'+(parseInt(page)+1)+'" value="load more">'
|
||||
if (showimages = document.getElementById("showimages")) {
|
||||
if (showimages.className == "selected") {
|
||||
toggleImages(true)
|
||||
}
|
||||
}
|
||||
var loadmore = document.getElementById("loadmore")
|
||||
loadmore.className = "show"
|
||||
loadmore.addEventListener("click", loadMore)
|
||||
setup()
|
||||
}
|
||||
else {
|
||||
e.target.outerHTML = '<input id="end" type="submit" value="" disabled>'
|
||||
}
|
||||
},
|
||||
function(res) {
|
||||
e.target.outerHTML = '<input id="loadmore" type="submit" data-page="'+parseInt(page)+'" value="loading failed">'
|
||||
var loadmore = document.getElementById("loadmore")
|
||||
loadmore.className = "show"
|
||||
loadmore.addEventListener("click", loadMore)
|
||||
}
|
||||
)
|
||||
return false;
|
||||
}
|
||||
function hideAllChildComments(e) {
|
||||
e.preventDefault()
|
||||
var comments = document.getElementsByClassName("comment")
|
||||
if (e.target.innerHTML == "hide all child comments") {
|
||||
e.target.innerHTML = "show all child comments"
|
||||
} else {
|
||||
e.target.innerHTML = "hide all child comments"
|
||||
}
|
||||
for (var i = 0; i < comments.length; i++) {
|
||||
var comment = comments[i]
|
||||
var btn = comment.getElementsByClassName("hidechildren")
|
||||
if (!btn.length) { continue }
|
||||
btn = btn[0]
|
||||
if (btn.getAttribute("for") != comment.id) { continue }
|
||||
var children = comment.getElementsByClassName("children")[0]
|
||||
if (e.target.innerHTML == "show all child comments") {
|
||||
children.className = "children hidden"
|
||||
btn.className = "hidechildren hidden"
|
||||
} else {
|
||||
children.className = "children"
|
||||
btn.className = "hidechildren"
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
function formSubmit(e) {
|
||||
e = e || window.event;
|
||||
var targ = e.currentTarget || e.srcElement || e;
|
||||
console.log(e)
|
||||
e.preventDefault()
|
||||
var data = new FormData(targ)
|
||||
params = new URLSearchParams(data).toString()
|
||||
params += "&" + e.submitter.name + "=" + e.submitter.value
|
||||
params += "&xhr=1"
|
||||
request(targ.target, params, function(res){
|
||||
data.set(e.submitter.name, e.submitter.value)
|
||||
data.set("xhr", "1")
|
||||
e.submitter.disabled = "disabled"
|
||||
request(targ.target, data,
|
||||
function(res){
|
||||
if (data.get("op") == "read_post") {
|
||||
document.getElementById("p"+data.get("postid")).remove()
|
||||
return
|
||||
}
|
||||
targ.outerHTML = res
|
||||
setup()
|
||||
},
|
||||
function(res){
|
||||
e.submitter.disabled = ""
|
||||
}
|
||||
)
|
||||
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
|
||||
var options = document.getElementsByClassName("scripting")
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var input = options[i].getElementsByTagName('input')
|
||||
if (!input.length) { continue }
|
||||
if (localStorage.getItem(input[0].name) == "true") {
|
||||
input[0].checked = "checked"
|
||||
}
|
||||
}
|
||||
document.getElementById("settings").addEventListener("submit", saveSettings)
|
||||
document.getElementById("closesettings").addEventListener("click", closeSettings)
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
function parse_youtube(url){
|
||||
function closeSettings(e) {
|
||||
e.preventDefault()
|
||||
var settings = document.getElementById("settingspopup")
|
||||
settings.className = ""
|
||||
return false
|
||||
}
|
||||
|
||||
function saveSettings(e) {
|
||||
e = e || window.event;
|
||||
var targ = e.currentTarget || e.srcElement || e;
|
||||
var data = new FormData(targ)
|
||||
e.preventDefault()
|
||||
request(targ.target, data, function(res) {
|
||||
["endlessScrolling", "autoLoad"].map(function(x) {
|
||||
localStorage.setItem(x, data.get(x)=="on")
|
||||
})
|
||||
window.location.reload()
|
||||
})
|
||||
return false;
|
||||
}
|
||||
|
||||
function parseYoutube(url){
|
||||
if (url.indexOf("youtu") == -1) return false
|
||||
var regExp = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/|shorts\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/;
|
||||
var match = url.match(regExp);
|
||||
if (match.length > 1) {
|
||||
if (match && match.length > 1) {
|
||||
return match[1]
|
||||
}
|
||||
return false
|
||||
}
|
||||
function youtube_iframe(id) {
|
||||
function youtubeIframe(id) {
|
||||
return '<iframe width="560" height="315" src="https://www.youtube.com/embed/'+id+'" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>'
|
||||
}
|
||||
|
||||
var posts = document.getElementsByClassName("post")
|
||||
for (var i = 0; i < posts.length; i++) {
|
||||
var url = posts[i].getElementsByClassName("url")[0].href
|
||||
if (id = parse_youtube(url)) {
|
||||
var btn = posts[i].getElementsByClassName("expando-button")[0]
|
||||
if (btn.className.indexOf("open") > -1) {
|
||||
console.log(id)
|
||||
posts[i].getElementsByClassName("embed")[0].innerHTML = youtube_iframe(id)
|
||||
function showImages(e) {
|
||||
e = e || window.event;
|
||||
e.preventDefault()
|
||||
var targ = e.currentTarget || e.srcElement || e;
|
||||
var parent = targ.parentNode
|
||||
if (parent.className == "") {
|
||||
parent.className = "selected"
|
||||
toggleImages(true)
|
||||
} else {
|
||||
parent.className = ""
|
||||
toggleImages(false)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function toggleImages(open) {
|
||||
var posts = document.getElementsByClassName("post")
|
||||
for (var i = 0; i < posts.length; i++) {
|
||||
var btn = posts[i].getElementsByClassName("expando-button")[0]
|
||||
if (btn.className.indexOf("hidden") != -1) { continue }
|
||||
var img = posts[i].getElementsByClassName("image")
|
||||
if (!img.length) { continue }
|
||||
var bdy = posts[i].getElementsByClassName("expando")[0]
|
||||
if (open) {
|
||||
bdy.className = 'expando open showimage';
|
||||
btn.className = "expando-button open"
|
||||
} else {
|
||||
bdy.className = 'expando';
|
||||
btn.className = "expando-button"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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")){
|
||||
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)
|
||||
var forms = posts[i].getElementsByClassName("link-btn")
|
||||
for (var f = 0; f < forms.length; f++) {
|
||||
forms[f].addEventListener("submit", formSubmit)
|
||||
}
|
||||
var url = posts[i].getElementsByClassName("url")[0].href
|
||||
if (id = parseYoutube(url)) {
|
||||
var btn = posts[i].getElementsByClassName("expando-button")[0]
|
||||
if (btn.className.indexOf("open") > -1) {
|
||||
posts[i].getElementsByClassName("embed")[0].innerHTML = youtubeIframe(id)
|
||||
} else {
|
||||
btn.className = "expando-button"
|
||||
}
|
||||
}
|
||||
}
|
||||
var comments = document.getElementsByClassName("comment")
|
||||
for (var i = 0; i < comments.length; i++) {
|
||||
comments[i].addEventListener("click", commentClick)
|
||||
}
|
||||
}
|
||||
setup()
|
||||
|
||||
if (localStorage.getItem("endlessScrolling") == "true") {
|
||||
var pager = document.getElementsByClassName("pager")
|
||||
if (pager.length) pager[0].className = "pager hidden"
|
||||
var loadmore = document.getElementById("loadmore")
|
||||
if (loadmore) {
|
||||
loadmore.className = "show"
|
||||
loadmore.addEventListener("click", loadMore)
|
||||
}
|
||||
}
|
||||
if (localStorage.getItem("autoLoad") == "true") {
|
||||
window.onscroll = function(e) {
|
||||
if ((window.innerHeight + Math.round(window.scrollY)) >= document.body.offsetHeight) {
|
||||
if (localStorage.getItem("endlessScrolling") == "true") {
|
||||
if (loadmore = document.getElementById("loadmore")) {
|
||||
loadmore.click()
|
||||
}
|
||||
}
|
||||
if (lmc = document.getElementById("lmc")) {
|
||||
lmc.click()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// delete cookies without HTTPOnly
|
||||
var cookies = document.cookie.split(";");
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = cookies[i];
|
||||
var eqPos = cookie.indexOf("=");
|
||||
var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
|
||||
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;SameSite=None;Secure";
|
||||
}
|
||||
|
|
|
|||
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()
|
||||
}
|
||||
465
routes.go
465
routes.go
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/k3a/html2text"
|
||||
"github.com/rystaf/go-lemmy"
|
||||
"github.com/rystaf/go-lemmy/types"
|
||||
"golang.org/x/text/language"
|
||||
|
|
@ -31,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)
|
||||
|
|
@ -110,24 +111,109 @@ var funcMap = template.FuncMap{
|
|||
}
|
||||
return false
|
||||
},
|
||||
"thumbnail": func(p types.Post) string {
|
||||
if p.ThumbnailURL.IsValid() {
|
||||
return p.ThumbnailURL.String() + "?format=jpg&thumbnail=96"
|
||||
}
|
||||
re := regexp.MustCompile(`\/pictrs\/image\/([a-z0-9\-]+)\.([a-z]+)$`)
|
||||
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]{5,})(\.[a-zA-Z0-9]+)?`)
|
||||
if re.MatchString(p.URL.String()) {
|
||||
return re.ReplaceAllString(p.URL.String(), "https://i.imgur.com/${2}s.jpg")
|
||||
}
|
||||
if p.URL.IsValid() {
|
||||
return "/_/static/link.png"
|
||||
}
|
||||
return "/_/static/text.png"
|
||||
},
|
||||
"humanize": humanize.Time,
|
||||
"markdown": func(host string, body string) template.HTML {
|
||||
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 {
|
||||
panic(err)
|
||||
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`)
|
||||
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
|
||||
if err := md.Convert([]byte(body), &buf); err != nil {
|
||||
fmt.Println(err)
|
||||
return body
|
||||
}
|
||||
return template.HTML(converted)
|
||||
text := html2text.HTML2TextWithOptions(buf.String(), html2text.WithLinksInnerText())
|
||||
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) {
|
||||
|
|
@ -135,6 +221,10 @@ func Initialize(Host string, r *http.Request) (State, error) {
|
|||
Host: Host,
|
||||
Page: 1,
|
||||
Status: http.StatusOK,
|
||||
Version: version,
|
||||
}
|
||||
if watch != nil {
|
||||
state.Watch = *watch
|
||||
}
|
||||
lemmyDomain := os.Getenv("LEMMY_DOMAIN")
|
||||
if lemmyDomain != "" {
|
||||
|
|
@ -157,7 +247,7 @@ func Initialize(Host string, r *http.Request) (State, error) {
|
|||
token := getCookie(r, "jwt")
|
||||
user := getCookie(r, "user")
|
||||
parts := strings.Split(user, ":")
|
||||
if len(parts) == 2 {
|
||||
if len(parts) == 2 && token != "" {
|
||||
if id, err := strconv.Atoi(parts[1]); err == nil {
|
||||
state.Client.Token = token
|
||||
sess := Session{
|
||||
|
|
@ -169,13 +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
|
||||
}
|
||||
|
|
@ -210,6 +315,8 @@ func Render(w http.ResponseWriter, templateName string, state State) {
|
|||
if state.Status != http.StatusOK {
|
||||
w.WriteHeader(state.Status)
|
||||
}
|
||||
header := w.Header()
|
||||
header.Set("Content-Security-Policy", "script-src 'self'")
|
||||
err = tmpl.Execute(w, state)
|
||||
if err != nil {
|
||||
fmt.Println("execute fail", err)
|
||||
|
|
@ -219,6 +326,7 @@ func Render(w http.ResponseWriter, templateName string, state State) {
|
|||
}
|
||||
func GetRoot(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
data := make(map[string]any)
|
||||
data["Title"] = r.Host
|
||||
tmpl, err := GetTemplate("root.html")
|
||||
if err != nil {
|
||||
fmt.Println("execute fail", err)
|
||||
|
|
@ -334,7 +442,49 @@ func GetFrontpage(w http.ResponseWriter, r *http.Request, ps httprouter.Params)
|
|||
if state.Op == "" {
|
||||
state.GetPosts()
|
||||
}
|
||||
if state.XHR {
|
||||
Render(w, "xhr.html", state)
|
||||
} else {
|
||||
Render(w, "frontpage.html", state)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -343,11 +493,40 @@ func GetPost(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
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()
|
||||
|
|
@ -359,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"
|
||||
|
|
@ -366,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)
|
||||
}
|
||||
|
|
@ -464,6 +680,9 @@ func setCookie(w http.ResponseWriter, host string, name string, value string) {
|
|||
Name: name,
|
||||
Value: value,
|
||||
MaxAge: 86400 * 30,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteNoneMode,
|
||||
Secure: true,
|
||||
Path: "/" + host,
|
||||
}
|
||||
http.SetCookie(w, &cookie)
|
||||
|
|
@ -485,23 +704,49 @@ 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"} {
|
||||
setCookie(w, state.Host, name, r.FormValue(name))
|
||||
for _, name := range []string{"DefaultSortType", "DefaultListingType", "DefaultCommentSortType"} {
|
||||
deleteCookie(w, state.Host, name)
|
||||
setCookie(w, "", name, r.FormValue(name))
|
||||
}
|
||||
if r.FormValue("darkmode") != "" {
|
||||
setCookie(w, state.Host, "Dark", "1")
|
||||
setCookie(w, "", "Dark", "1")
|
||||
state.Dark = true
|
||||
} else {
|
||||
deleteCookie(w, state.Host, "Dark")
|
||||
setCookie(w, "", "Dark", "0")
|
||||
state.Dark = false
|
||||
}
|
||||
if r.FormValue("shownsfw") != "" {
|
||||
setCookie(w, "", "ShowNSFW", "1")
|
||||
state.ShowNSFW = true
|
||||
} else {
|
||||
deleteCookie(w, state.Host, "ShowNSFW")
|
||||
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 {
|
||||
// TODO fetch server settings
|
||||
// TODO fetch user settings
|
||||
}
|
||||
}
|
||||
Render(w, "settings.html", state)
|
||||
|
|
@ -540,6 +785,7 @@ func SignUpOrLogin(w http.ResponseWriter, r *http.Request, ps httprouter.Params)
|
|||
if resp.JWT.IsValid() {
|
||||
token = resp.JWT.String()
|
||||
username = r.FormValue("username")
|
||||
deleteCookie(w, state.Host, "ShowNSFW")
|
||||
}
|
||||
case "sign up":
|
||||
register := types.Register{
|
||||
|
|
@ -583,10 +829,14 @@ func SignUpOrLogin(w http.ResponseWriter, r *http.Request, ps httprouter.Params)
|
|||
q.Add("alert", alert)
|
||||
r.URL.RawQuery = q.Encode()
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
return
|
||||
}
|
||||
}
|
||||
if token != "" {
|
||||
state.GetUser(username)
|
||||
if state.User == nil {
|
||||
return
|
||||
}
|
||||
setCookie(w, state.Host, "jwt", token)
|
||||
userid := strconv.Itoa(state.User.PersonView.Person.ID)
|
||||
setCookie(w, state.Host, "user", state.User.PersonView.Person.Name+":"+userid)
|
||||
|
|
@ -603,7 +853,7 @@ func GetLogin(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
return
|
||||
}
|
||||
state.GetSite()
|
||||
if state.Site.SiteView.LocalSite.CaptchaEnabled {
|
||||
if state.Site != nil && state.Site.SiteView.LocalSite.CaptchaEnabled {
|
||||
state.GetCaptcha()
|
||||
}
|
||||
m, _ := url.ParseQuery(r.URL.RawQuery)
|
||||
|
|
@ -669,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")
|
||||
|
|
@ -684,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 {
|
||||
|
|
@ -820,6 +1087,7 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
post := types.EditPost{
|
||||
PostID: postid,
|
||||
Body: types.NewOptional(r.FormValue("body")),
|
||||
Name: types.NewOptional(r.FormValue("name")),
|
||||
URL: types.NewOptional(r.FormValue("url")),
|
||||
}
|
||||
if r.FormValue("url") == "" {
|
||||
|
|
@ -850,6 +1118,38 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
state.Error = err
|
||||
fmt.Println(err)
|
||||
}
|
||||
case "save_post":
|
||||
postid, _ := strconv.Atoi(r.FormValue("postid"))
|
||||
_, err := state.Client.SavePost(context.Background(), types.SavePost{
|
||||
PostID: postid,
|
||||
Save: r.FormValue("submit") == "save",
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
if r.FormValue("xhr") != "" {
|
||||
state.GetPost(postid)
|
||||
state.PostID = 0
|
||||
state.Op = "save_post"
|
||||
state.XHR = true
|
||||
Render(w, "index.html", state)
|
||||
return
|
||||
}
|
||||
case "save_comment":
|
||||
commentid, _ := strconv.Atoi(r.FormValue("commentid"))
|
||||
_, err := state.Client.SaveComment(context.Background(), types.SaveComment{
|
||||
CommentID: commentid,
|
||||
Save: r.FormValue("submit") == "save",
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
if r.FormValue("xhr") != "" {
|
||||
state.XHR = true
|
||||
state.GetComment(commentid)
|
||||
Render(w, "index.html", state)
|
||||
return
|
||||
}
|
||||
case "delete_post":
|
||||
postid, _ := strconv.Atoi(r.FormValue("postid"))
|
||||
post := types.DeletePost{
|
||||
|
|
@ -866,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
|
||||
|
|
@ -883,6 +1199,8 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
state.Client.CreatePostLike(context.Background(), post)
|
||||
if r.FormValue("xhr") != "" {
|
||||
state.GetPost(postid)
|
||||
state.PostID = 0
|
||||
state.Op = "vote_post"
|
||||
state.XHR = true
|
||||
Render(w, "index.html", state)
|
||||
return
|
||||
|
|
@ -890,7 +1208,7 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
case "vote_comment":
|
||||
var score int16
|
||||
score = 1
|
||||
if r.FormValue("vote") != "▲" {
|
||||
if r.FormValue("submit") != "▲" {
|
||||
score = -1
|
||||
}
|
||||
if r.FormValue("undo") == strconv.Itoa(int(score)) {
|
||||
|
|
@ -917,8 +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 {
|
||||
|
|
@ -926,6 +1256,13 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
}
|
||||
resp, err := state.Client.CreateComment(context.Background(), createComment)
|
||||
if err == nil {
|
||||
if r.FormValue("xhr") != "" {
|
||||
state.XHR = true
|
||||
state.Comments = nil
|
||||
state.GetComment(resp.CommentView.Comment.ID)
|
||||
Render(w, "index.html", state)
|
||||
return
|
||||
}
|
||||
postid := strconv.Itoa(state.PostID)
|
||||
commentid := strconv.Itoa(resp.CommentView.Comment.ID)
|
||||
r.URL.Path = "/" + state.Host + "/post/" + postid
|
||||
|
|
@ -933,11 +1270,48 @@ 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
|
||||
}
|
||||
if r.FormValue("submit") == "cancel" {
|
||||
r.URL.RawQuery = ""
|
||||
}
|
||||
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)
|
||||
|
|
@ -946,6 +1320,39 @@ 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
|
||||
state.GetComment(commentid)
|
||||
Render(w, "index.html", state)
|
||||
return
|
||||
}
|
||||
if r.FormValue("submit") == "cancel" {
|
||||
r.URL.RawQuery = ""
|
||||
}
|
||||
case "delete_comment":
|
||||
commentid, _ := strconv.Atoi(r.FormValue("commentid"))
|
||||
resp, err := state.Client.DeleteComment(context.Background(), types.DeleteComment{
|
||||
|
|
@ -959,6 +1366,12 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|||
r.URL.Fragment = "c" + commentid
|
||||
r.URL.RawQuery = ""
|
||||
}
|
||||
case "shownsfw":
|
||||
if r.FormValue("submit") == "continue" {
|
||||
setCookie(w, "", "ShowNSFW", "1")
|
||||
} else {
|
||||
r.URL.Path = "/" + state.Host
|
||||
}
|
||||
}
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
}
|
||||
|
|
@ -973,6 +1386,7 @@ func GetRouter() *httprouter.Router {
|
|||
router.GET("/:host/search", middleware(Search))
|
||||
router.POST("/:host/search", middleware(UserOp))
|
||||
router.GET("/:host/inbox", middleware(Inbox))
|
||||
router.POST("/:host/inbox", middleware(UserOp))
|
||||
router.GET("/:host/login", middleware(GetLogin))
|
||||
router.POST("/:host/login", middleware(SignUpOrLogin))
|
||||
router.GET("/:host/settings", middleware(Settings))
|
||||
|
|
@ -997,12 +1411,14 @@ 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))
|
||||
router.GET("/search", middleware(Search))
|
||||
router.POST("/search", middleware(UserOp))
|
||||
router.GET("/inbox", middleware(Inbox))
|
||||
router.POST("/inbox", middleware(UserOp))
|
||||
router.GET("/login", middleware(GetLogin))
|
||||
router.POST("/login", middleware(SignUpOrLogin))
|
||||
router.GET("/settings", middleware(Settings))
|
||||
|
|
@ -1027,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
|
||||
}
|
||||
|
|
|
|||
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
|
Before Width: | Height: | Size: 110 KiB |
BIN
screenshot1.png
Normal file
BIN
screenshot1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
150
state.go
150
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
|
||||
}
|
||||
|
|
@ -53,9 +61,12 @@ type Post struct {
|
|||
type Session struct {
|
||||
UserName string
|
||||
UserID int
|
||||
Communities []types.CommunityView
|
||||
}
|
||||
|
||||
type State struct {
|
||||
Watch bool
|
||||
Version string
|
||||
Client *lemmy.Client
|
||||
HTTPClient *http.Client
|
||||
Session *Session
|
||||
|
|
@ -69,6 +80,7 @@ type State struct {
|
|||
Communities []types.CommunityView
|
||||
UnreadCount int64
|
||||
Sort string
|
||||
CommentSort string
|
||||
Listing string
|
||||
Page int
|
||||
Parts []string
|
||||
|
|
@ -78,6 +90,7 @@ type State struct {
|
|||
CommentCount int
|
||||
PostID int
|
||||
CommentID int
|
||||
Context int
|
||||
UserName string
|
||||
User *types.GetPersonDetailsResponse
|
||||
Now int64
|
||||
|
|
@ -85,11 +98,46 @@ type State struct {
|
|||
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" {
|
||||
|
|
@ -155,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]
|
||||
|
|
@ -168,6 +217,11 @@ func (state *State) ParseQuery(RawQuery string) {
|
|||
if len(m["xhr"]) > 0 {
|
||||
state.XHR = true
|
||||
}
|
||||
if len(m["view"]) > 0 {
|
||||
if m["view"][0] == "Saved" {
|
||||
state.Op = "Saved"
|
||||
}
|
||||
}
|
||||
//if len(m["op"]) > 0 {
|
||||
// state.Op = m["op"][0]
|
||||
//}
|
||||
|
|
@ -208,16 +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.Host = "."
|
||||
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) {
|
||||
|
|
@ -227,9 +292,9 @@ 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(200)),
|
||||
Limit: types.NewOptional(int64(50)),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
|
@ -251,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" {
|
||||
|
|
@ -258,9 +350,9 @@ 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(200)),
|
||||
Limit: types.NewOptional(int64(50)),
|
||||
Page: types.NewOptional(int64(state.Page)),
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -343,6 +435,7 @@ func (state *State) GetMessages() {
|
|||
Post: m.Post,
|
||||
Creator: m.Creator,
|
||||
Community: m.Community,
|
||||
Counts: m.Counts,
|
||||
},
|
||||
Op: unread,
|
||||
State: state,
|
||||
|
|
@ -365,9 +458,11 @@ func (state *State) GetUser(username string) {
|
|||
Username: types.NewOptional(state.UserName),
|
||||
Page: types.NewOptional(int64(state.Page)),
|
||||
Limit: types.NewOptional(int64(limit)),
|
||||
SavedOnly: types.NewOptional(state.Op == "Saved"),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
state.Error = err
|
||||
state.Status = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
|
|
@ -425,13 +520,16 @@ func (state *State) MarkAllAsRead() {
|
|||
}
|
||||
|
||||
func (state *State) GetPosts() {
|
||||
resp, err := state.Client.Posts(context.Background(), types.GetPosts{
|
||||
posts := types.GetPosts{
|
||||
Sort: types.NewOptional(types.SortType(state.Sort)),
|
||||
Type: types.NewOptional(types.ListingType(state.Listing)),
|
||||
CommunityName: types.NewOptional(state.CommunityName),
|
||||
Limit: types.NewOptional(int64(25)),
|
||||
Page: types.NewOptional(int64(state.Page)),
|
||||
})
|
||||
}
|
||||
if state.CommunityName != "" {
|
||||
posts.CommunityName = types.NewOptional(state.CommunityName)
|
||||
}
|
||||
resp, err := state.Client.Posts(context.Background(), posts)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
state.Status = http.StatusInternalServerError
|
||||
|
|
@ -449,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)),
|
||||
|
|
@ -464,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)),
|
||||
|
|
@ -492,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
|
||||
|
|
@ -512,8 +625,9 @@ 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,
|
||||
|
|
@ -527,7 +641,6 @@ func (state *State) GetPost(postid int) {
|
|||
Moderators: resp.Moderators,
|
||||
}
|
||||
state.Community = &cresp
|
||||
}
|
||||
}
|
||||
|
||||
func (state *State) GetCommunity(communityName string) {
|
||||
|
|
@ -580,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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -600,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="../post/{{ $activity.Comment.P.Post.ID}}">{{ $activity.Comment.P.Post.Name}}</a>
|
||||
<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,57 +1,65 @@
|
|||
<div class="comment{{if or (lt .P.Counts.Score -5) .P.Comment.Deleted }} hidden{{end}}" id="c{{.P.Comment.ID}}" onclick="commentClick(event)">
|
||||
<div class="meta">
|
||||
<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">
|
||||
<input type="submit" name="vote" value="▲">
|
||||
<input type="submit" name="submit" value="▲">
|
||||
<div></div>
|
||||
{{ if .P.MyVote.IsValid}}
|
||||
<input type="hidden" name="undo" value="{{.P.MyVote.String}}">
|
||||
{{ end}}
|
||||
<input type="hidden" name="op" value="vote_comment">
|
||||
<input type="hidden" name="commentid" value="{{.P.Comment.ID }}">
|
||||
<input type="submit" name="vote" value="▼">
|
||||
<input type="submit" name="submit" value="▼">
|
||||
</form>
|
||||
</div>
|
||||
{{ 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 .Submitter }}class="submitter"{{end}} href="/{{.State.Host}}/u/{{fullname .P.Creator}}">{{fullname .P.Creator}}</a>
|
||||
{{.P.Counts.Score}} points <span title="{{.P.Comment.Published.Time}}">{{ humanize .P.Comment.Published.Time }}</span>
|
||||
<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>
|
||||
<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" }}
|
||||
<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 type="submit" value="save">
|
||||
</form>
|
||||
{{ template "create_comment.html" .State }}
|
||||
{{ else }}
|
||||
<div class="content{{ if and .Selected}} highlight{{end}}">{{if .P.Comment.Deleted}}[removed]{{else}}{{ markdown .State.Host .P.Comment.Content }}{{end}}</div>
|
||||
{{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}}
|
||||
{{ 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 }}
|
||||
<li><a class="source" for="c{{.P.Comment.ID}}" href="/{{.State.Host}}/comment/{{.P.Comment.ID}}?">hide source</a></li>
|
||||
{{ end }}
|
||||
|
||||
{{ if .State.Session }}
|
||||
{{ if and (eq .P.Comment.CreatorID .State.Session.UserID) (ne .Op "edit")}}
|
||||
{{ if and (eq .P.Comment.CreatorID .State.Session.UserID) (ne .Op "edit") }}
|
||||
<li><a class="edit" for="c{{.P.Comment.ID}}" href="/{{.State.Host}}/comment/{{.P.Comment.ID}}?edit">edit</a></li>
|
||||
<li>
|
||||
<form class="delete" method="POST">
|
||||
|
|
@ -61,28 +69,49 @@
|
|||
</form>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ if ne .Op "reply"}}
|
||||
<li><a class="reply" for="c{{.P.Comment.ID}}" href="/{{.State.Host}}/comment/{{.P.Comment.ID}}?reply">reply</a></li>
|
||||
<li>
|
||||
<form class="link-btn" method="POST">
|
||||
<input type="hidden" name="commentid" value="{{.P.Comment.ID}}">
|
||||
<input type="hidden" name="op" value="save_comment">
|
||||
{{ if .P.Saved }}
|
||||
<input type="submit" name="submit" value="unsave">
|
||||
{{ else }}
|
||||
<input type="submit" name="submit" value="save">
|
||||
|
||||
{{ end }}
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<a class="reply" for="c{{.P.Comment.ID}}" href="/{{.State.Host}}/comment/{{.P.Comment.ID}}?reply">
|
||||
reply
|
||||
</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ 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 required name="content"></textarea>
|
||||
</div>
|
||||
<input type="hidden" name="parentid" value="{{.P.Comment.ID}}">
|
||||
<input type="hidden" name="op" value="create_comment">
|
||||
<input type="submit" value="save">
|
||||
</form>
|
||||
{{ template "create_comment.html" .State }}
|
||||
{{ end}}
|
||||
{{ range $ci, $child := .C }}{{ template "comment.html" $child }}{{end}}
|
||||
</div>
|
||||
{{ 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>
|
||||
|
|
|
|||
|
|
@ -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,48 +2,49 @@
|
|||
<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?3">
|
||||
<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 {
|
||||
display: none;
|
||||
}
|
||||
.comment .meta a.minimize {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/_/static/noscript.css?v={{ .Version }}">
|
||||
</noscript>
|
||||
|
||||
{{ template "nav.html" . -}}
|
||||
{{ if and (not .ShowNSFW) .Community .Community.CommunityView.Community.NSFW }}
|
||||
{{ 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}}
|
||||
|
||||
{{ if .Error }}
|
||||
<div class="error">{{.Error}}</div>
|
||||
{{ end }}
|
||||
{{ if .Error }}
|
||||
<div class="error">
|
||||
{{.Error}}.
|
||||
{{ if .Unknown }}
|
||||
try remote instance: <a href="{{ .Unknown }}">{{ .Unknown }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ range .Posts }}
|
||||
{{ range .Posts }}
|
||||
{{ template "post.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if or (and (not .Op) (not .Activities) (not .Comments) (not .Posts) (not .Communities)) (and (not .Comments) .PostID) (and (not .Activities) (not .Query) .User) }}
|
||||
<div class="error">there doesn't seem to be anything here</div>
|
||||
{{ end }}
|
||||
{{ if or (and (not .Op) (not .Activities) (not .Comments) (not .Posts) (not .Communities)) (and (not .Comments) .PostID) (and (not .Activities) (not .Query) .User) }}
|
||||
<div class="error">there doesn't seem to be anything here</div>
|
||||
{{ end }}
|
||||
|
||||
|
||||
{{ if or .Query (eq .SearchType "Communities") (eq (len .Posts) 25) (and .Comments (and (eq .CommentCount 200) (gt (index .Posts 0).Counts.Comments .CommentCount))) (and .User (or (gt .User.PersonView.Counts.CommentCount 10) (gt .User.PersonView.Counts.PostCount 10))) }}
|
||||
{{ if or .Query (eq .SearchType "Communities") (eq (len .Posts) 25) (and .Comments (and (eq .CommentCount 200) (gt (index .Posts 0).Counts.Comments .CommentCount))) (and .User (or (gt .User.PersonView.Counts.CommentCount 10) (gt .User.PersonView.Counts.PostCount 10))) }}
|
||||
<div class="pager">
|
||||
view more: {{if gt .Page 1 }}<a href="{{ .PrevPage }}">‹ prev</a>{{ end }} <a href="{{ .NextPage }}">next ›</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
<input id="loadmore" type="submit" value="load more" data-page="2">
|
||||
{{ end }}
|
||||
|
||||
{{ template "sidebar.html" . }}
|
||||
</main>
|
||||
<script src="/_/static/utils.js"></script>
|
||||
{{ end }}
|
||||
<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?1">
|
||||
<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>
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
{{ end }}
|
||||
<div>
|
||||
<h2>login</h2>
|
||||
<form method="POST" action="/{{host .Host}}/login">
|
||||
<form method="POST" action="/{{ .Host}}/login">
|
||||
<label>
|
||||
username
|
||||
<div><input required name="username" type="text"></div>
|
||||
|
|
|
|||
|
|
@ -3,28 +3,26 @@
|
|||
<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?3">
|
||||
<link rel="stylesheet" href="/_/static/style.css?v={{ .Version }}">
|
||||
</head>
|
||||
<body{{ if .Dark }} class="dark"{{end}}>
|
||||
<noscript>
|
||||
<style>
|
||||
.expando-button {
|
||||
display: none;
|
||||
}
|
||||
.comment .meta a.minimize {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/_/static/noscript.css?v={{ .Version }}">
|
||||
</noscript>
|
||||
|
||||
{{ template "nav.html" . -}}
|
||||
{{ if and (not .ShowNSFW) .Community .Community.CommunityView.Community.NSFW }}
|
||||
{{ 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}}
|
||||
{{ end}}
|
||||
|
||||
{{ if or (ne .Query "") .Communities }}
|
||||
{{ 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}}">
|
||||
|
|
@ -45,80 +43,93 @@
|
|||
<input type="hidden" name="searchtype" value="Communities">
|
||||
{{ end }}
|
||||
</form>
|
||||
{{ end}}
|
||||
{{ end}}
|
||||
|
||||
{{ if .Error }}
|
||||
<div class="error">{{.Error}}</div>
|
||||
{{ end }}
|
||||
{{ if .Error }}
|
||||
<div class="error">
|
||||
{{.Error}}.
|
||||
{{ if .Unknown }}
|
||||
try remote instance: <a href="{{ .Unknown }}">{{ .Unknown }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ range .Communities }}
|
||||
{{ range .Communities }}
|
||||
{{ template "community.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if eq .Op "create_community" "edit_community" }}
|
||||
{{ if eq .Op "create_community" "edit_community" }}
|
||||
{{ template "create_community.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ range .Posts }}
|
||||
{{ range .Posts }}
|
||||
{{ template "post.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if eq .Op "create_post" "edit_post" }}
|
||||
{{ if eq .Op "create_post" "edit_post" }}
|
||||
{{ template "create_post.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if and .PostID .Posts}}
|
||||
{{ if and .PostID .Posts}}
|
||||
{{ if .CommentID}}
|
||||
<div class="warning">you are viewing a single comment's thread<br>
|
||||
<a href="../post/{{.PostID}}/#c{{.CommentID}}">view the rest of the comments</a>
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="commentmenu">
|
||||
{{if .Comments}}{{if gt .Page 1}}next{{else if or (lt .CommentCount 200) (lt (index .Posts 0).Counts.Comments .CommentCount) }}all{{else}}top{{end}} {{.CommentCount}} comments{{else}} no comments (yet){{end}}
|
||||
{{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 class="create_comment">
|
||||
{{ template "create_comment.html" .}}
|
||||
</div>
|
||||
<input type="hidden" name="op" value="create_comment">
|
||||
<input type="submit" value="save"{{ if (index .Posts 0).Post.Deleted }} disabled {{end}}>
|
||||
</form>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end}}
|
||||
{{ end}}
|
||||
|
||||
|
||||
{{ range $i, $comment := .Comments }}
|
||||
{{ range $i, $comment := .Comments }}
|
||||
{{ template "comment.html" $comment }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if eq .Op "send_message" }}
|
||||
{{ template "send_message.html" . }}
|
||||
{{ else }}
|
||||
{{ template "activities.html" . }}
|
||||
{{ end }}
|
||||
{{ 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>
|
||||
{{ end }}
|
||||
|
||||
{{ if or (and (not .Op) (not .Activities) (not .Comments) (not .Posts) (not .Communities)) (and (not .Comments) .PostID) (and (not .Activities) (not .Query) .User) }}
|
||||
<div class="error">there doesn't seem to be anything here</div>
|
||||
{{ end }}
|
||||
{{ if eq .Op "send_message" }}
|
||||
{{ template "send_message.html" . }}
|
||||
{{ else }}
|
||||
{{ template "activities.html" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ if or (and (not .Op) (not .Activities) (not .Comments) (not .Posts) (not .Communities)) (and (not .Comments) .PostID) (and (not .Activities) (not .Query) .User) }}
|
||||
<div class="error">there doesn't seem to be anything here</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if or .Query (eq .SearchType "Communities") (eq (len .Posts) 25) (and .Comments (and (eq .CommentCount 200) (gt (index .Posts 0).Counts.Comments .CommentCount))) (and .User (or (gt .User.PersonView.Counts.CommentCount 10) (gt .User.PersonView.Counts.PostCount 10))) }}
|
||||
{{ if or .Query (eq .SearchType "Communities") (eq (len .Posts) 25) (and .Comments (gt (index .Posts 0).Counts.Comments .CommentCount) (not .CommentID)) (and .User (or (gt .User.PersonView.Counts.CommentCount 10) (gt .User.PersonView.Counts.PostCount 10))) }}
|
||||
<div class="pager">
|
||||
view more: {{if gt .Page 1 }}<a href="{{ .PrevPage }}">‹ prev</a>{{ end }} <a href="{{ .NextPage }}">next ›</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if not .PostID }}
|
||||
<input id="loadmore" type="submit" value="load more" data-page="2">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<script src="/_/static/utils.js"></script>
|
||||
<script src="/_/static/utils.js?v={{ .Version }}"></script>
|
||||
{{ if .Watch }}
|
||||
<script src="/_/static/ws.js"></script>
|
||||
{{ end }}
|
||||
{{ template "sidebar.html" . }}
|
||||
</main>
|
||||
{{ end }}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -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,15 +1,27 @@
|
|||
{{ $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>
|
||||
|
|
||||
{{ $host := .Host }}
|
||||
{{ range $i, $c := .TopCommunities}}
|
||||
<a href="/{{$host}}/c/{{$c.Community.Name}}">{{$c.Community.Name}}</a>
|
||||
<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 }}
|
||||
|
|
@ -17,15 +29,16 @@
|
|||
|
|
||||
<a href="/{{.Host}}/inbox" class="mailbox{{ if .UnreadCount }} orangered{{end}}">✉</a>
|
||||
|
|
||||
<a href="/{{.Host}}/settings">settings</a>
|
||||
<a id="opensettings" href="/{{.Host}}/settings">settings</a>
|
||||
|
|
||||
<form method="POST"><input type="submit" name="op" value="logout"></form>
|
||||
{{else}}
|
||||
Want to join? <a href="/{{.Host}}/login">Log in</a> or <a href="/{{.Host}}/login">sign up</a> in seconds
|
||||
<a href="/{{.Host}}/login">log in</a> or <a href="/{{.Host}}/login">sign up</a>
|
||||
|
|
||||
<a href="/{{.Host}}/settings">settings</a>
|
||||
<a id="opensettings" href="/{{.Host}}/settings">settings</a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div id="settingspopup"></div>
|
||||
<div class="spacer">
|
||||
<a href="/{{ .Host}}/">
|
||||
<img class="icon" src="{{ if .Site }}{{ .Site.SiteView.Site.Icon.String }}{{else}}/{{ .Host}}/icon.jpg{{end}}">
|
||||
|
|
@ -47,11 +60,14 @@
|
|||
<span>: search</span>
|
||||
{{ end }}
|
||||
<ul>
|
||||
{{ if .User }}
|
||||
<li class="selected"><a href="">overview</a></li>
|
||||
{{ 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>
|
||||
|
|
@ -61,6 +77,9 @@
|
|||
<li{{ if eq .Sort "MostComments" }} class="selected"{{end}}><a href="{{ .SortBy "MostComments" }}">most comments</a></li>
|
||||
<li{{ if eq .Sort "NewComments" }} class="selected"{{end}}><a href="{{ .SortBy "NewComments" }}">new comments</a></li>
|
||||
<li{{ if contains .Sort "Top" }} class="selected"{{end}}><a href="{{ .SortBy "TopDay" }}">top</a></li>
|
||||
{{ if .Posts }}
|
||||
<li id="showimages"><a id="se" href="">show images</a></li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
|
|
|
|||
9
templates/nsfw.html
Normal file
9
templates/nsfw.html
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<form method="POST" class="nsfw">
|
||||
<div>18+</div>
|
||||
<h3>You must be 18+ to view this community</h3>
|
||||
|
||||
<p>You must be at least eighteen years old to view this content. Are you over eighteen and willing to see adult content?</p>
|
||||
|
||||
<input type="submit" name="submit" value="no thank you"> <input type="submit" name="submit" value="continue">
|
||||
<input type="hidden" name="op" value="shownsfw">
|
||||
</form>
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
{{ if not .State.XHR }}
|
||||
<div class="post {{if .Post.Deleted}}deleted{{end}}" onclick="postClick(event)">
|
||||
{{ if and (ne .State.Op "vote_post") (ne .State.Op "save_post") }}
|
||||
<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 }}
|
||||
<div class="score">
|
||||
{{ end }}
|
||||
{{ if or (ne .State.Op "save_post") (eq .State.Op "vote_post") }}
|
||||
{{ if .State.Session }}
|
||||
<form class="link-btn {{ if lt .Rank 1 }}squish{{end}}{{ if eq .MyVote.String "1" }} like{{else if eq .MyVote.String "-1"}} dislike{{end}}" method="POST" onsubmit="formSubmit(event)">
|
||||
<form class="link-btn {{ if lt .Rank 1 }}squish{{end}}{{ if eq .MyVote.String "1" }} like{{else if eq .MyVote.String "-1"}} dislike{{end}}" method="POST">
|
||||
<input type="submit" name="vote" value="▲">
|
||||
{{ if .MyVote.IsValid}}
|
||||
<input type="hidden" name="undo" value="{{.MyVote.String}}">
|
||||
|
|
@ -17,18 +18,24 @@
|
|||
<input type="submit" name="vote" value="▼">
|
||||
</form>
|
||||
{{ else }}
|
||||
<div style="margin-top: 19px;">{{ .Counts.Score }}</div>
|
||||
<div style="margin-top: 19px;">{{ .Counts.Score }}</div>
|
||||
{{ end }}
|
||||
{{ if not .State.XHR}}
|
||||
{{ end }}
|
||||
{{ if and (ne .State.Op "vote_post") (ne .State.Op "save_post") }}
|
||||
</div>
|
||||
<div class="thumb" style="background-image: url({{if .Post.ThumbnailURL.IsValid}}{{.Post.ThumbnailURL.String}}?format=jpg&thumbnail=96{{else if .Post.URL.IsValid}}/_/static/link.png{{else}}/_/static/text.png{{end}})"></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 }}">{{ .Post.Name }}</a>
|
||||
<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>
|
||||
|
|
@ -36,12 +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 }}">c/{{ 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">
|
||||
|
|
@ -55,18 +76,49 @@
|
|||
{{ end }}
|
||||
</form>
|
||||
{{ end}}
|
||||
</div>
|
||||
<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 }}
|
||||
{{ if isImage .Post.URL.String}}
|
||||
<img loading="lazy" src="{{ .Post.URL }}">
|
||||
{{ if or (ne .State.Op "vote_post") (eq .State.Op "save_post") }}
|
||||
{{ if .State.Session }}
|
||||
<form class="link-btn" method="POST">
|
||||
<input type="hidden" name="postid" value="{{.Post.ID }}">
|
||||
<input type="hidden" name="op" value="save_post">
|
||||
{{ if .PostView.Saved }}
|
||||
<input type="submit" name="submit" value="unsave">
|
||||
{{ else }}
|
||||
<input type="submit" name="submit" value="save">
|
||||
{{ end }}
|
||||
</form>
|
||||
{{end}}
|
||||
{{ end }}
|
||||
<div class="embed"></div>
|
||||
{{ if and (ne .State.Op "vote_post") (ne .State.Op "save_post") }}
|
||||
{{ 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}}">
|
||||
{{ if (and .Post.Body.IsValid (ne .Post.Body.String "")) }}
|
||||
<div class="md">{{ markdown .State.Host .Post.Body.String }}</div>
|
||||
{{ end }}
|
||||
{{ if isImage .Post.URL.String}}
|
||||
<div class="image" style="background-image: url({{.Post.URL}})">
|
||||
<img loading="lazy" src="{{ .Post.URL }}">
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="embed"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
<head>
|
||||
<title>mlmym</title>
|
||||
<link rel="shortcut icon" href="/{{.Host}}/icon.jpg">
|
||||
<link rel="stylesheet" href="/_/static/style.css">
|
||||
<link rel="stylesheet" href="/_/static/style.css?v=7">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<div class="spacer"></div>
|
||||
<span class="title">mlmym</span>
|
||||
<span class="title">{{ .Title }}</span>
|
||||
</nav>
|
||||
<form class="root" method="POST">
|
||||
<label>Enter a <a href="https://join-lemmy.org/instances" target="blank_">lemmy</a> domain or url
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
{{ if not .XHR }}
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>{{ host .Host }}: preferences</title>
|
||||
<link rel="shortcut icon" href="/{{.Host}}/icon.jpg">
|
||||
<link rel="stylesheet" href="/_/static/style.css?1">
|
||||
<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>
|
||||
<link rel="stylesheet" href="/_/static/noscript.css?v={{ .Version }}">
|
||||
</noscript>
|
||||
<nav>
|
||||
<div class="communities">
|
||||
<a href="/{{.Host}}">home</a>
|
||||
|
|
@ -29,7 +33,7 @@
|
|||
|
|
||||
<form method="POST"><input type="submit" name="op" value="logout"></form>
|
||||
{{else}}
|
||||
Want to join? <a href="/{{.Host}}/login">Log in</a> or <a href="/{{.Host}}/login">sign up</a> in seconds
|
||||
<a href="/{{.Host}}/login">log in</a> or <a href="/{{.Host}}/login">sign up</a>
|
||||
|
|
||||
<a href="/{{.Host}}/settings">settings</a>
|
||||
{{end}}
|
||||
|
|
@ -52,7 +56,8 @@
|
|||
{{ if .Error }}
|
||||
<div class="error">{{.Error}}</div>
|
||||
{{ end }}
|
||||
<form class="preferences" method="POST">
|
||||
{{ end }}
|
||||
<form id="settings" class="preferences" method="POST" target="/{{.Host}}/settings">
|
||||
<div>
|
||||
<label>
|
||||
default listing
|
||||
|
|
@ -65,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>
|
||||
|
|
@ -84,16 +89,55 @@
|
|||
<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
|
||||
</label>
|
||||
<input type="checkbox" name="darkmode" {{ if .Dark }}checked{{end}}>
|
||||
</div>
|
||||
<div class="scripting">
|
||||
<label>
|
||||
endless scrolling
|
||||
</label>
|
||||
<input type="checkbox" name="endlessScrolling">
|
||||
</div>
|
||||
<div class="scripting">
|
||||
<label>
|
||||
auto load more
|
||||
</label>
|
||||
<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={{ .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>
|
||||
|
|
@ -66,7 +65,7 @@
|
|||
{{ printer .Site.SiteView.Counts.Users }} readers <br>
|
||||
<span class="green" title="Users active in the last day"></span>
|
||||
{{ printer .Site.SiteView.Counts.UsersActiveDay }} users here now
|
||||
<p>{{ markdown .Host .Site.SiteView.Site.Sidebar.String }}</p>
|
||||
{{ markdown .Host .Site.SiteView.Site.Sidebar.String }}
|
||||
<div class="age" title="{{ .Site.SiteView.Site.Published.Time}}">founded {{ humanize .Site.SiteView.Site.Published.Time }}</div>
|
||||
{{ if .Site.Admins }}
|
||||
ADMINS
|
||||
|
|
@ -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,10 +1,21 @@
|
|||
{{ if .CommentID }}
|
||||
{{ $state := . }}
|
||||
{{ if or .PostID .CommentID }}
|
||||
{{ range $i, $comment := .Comments }}
|
||||
{{ template "comment.html" $comment }}
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ else if .Activities }}
|
||||
{{ template "activities.html" . }}
|
||||
{{ 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