- commit
- 8e0b2c1
- parent
- af76934
- author
- Eric Bower
- date
- 2022-08-30 03:29:02 +0000 UTC
feat(imgs): web optimized images
12 files changed,
+380,
-66
+1,
-0
1@@ -11,3 +11,4 @@ data/*
2 ssh_data
3 .storage
4 __debug_bin
5+.bin
+3,
-1
1@@ -9,6 +9,7 @@ import (
2
3 "git.sr.ht/~erock/pico/db"
4 "git.sr.ht/~erock/pico/db/postgres"
5+ "git.sr.ht/~erock/pico/imgs"
6 "git.sr.ht/~erock/pico/lists"
7 "git.sr.ht/~erock/pico/shared"
8 "git.sr.ht/~erock/pico/wish/cms/config"
9@@ -97,7 +98,8 @@ func main() {
10 logger.Info("updating dates")
11 for _, post := range posts {
12 if post.Space == "prose" {
13- parsed, err := shared.ParseText(post.Text, "")
14+ linkify := imgs.NewImgsLinkify(post.Username, true, false)
15+ parsed, err := shared.ParseText(post.Text, linkify)
16 if err != nil {
17 logger.Error(err)
18 continue
1@@ -7,6 +7,7 @@ import (
2
3 "git.sr.ht/~erock/pico/db"
4 "git.sr.ht/~erock/pico/db/postgres"
5+ "git.sr.ht/~erock/pico/imgs"
6 "git.sr.ht/~erock/pico/shared"
7 "git.sr.ht/~erock/pico/wish/cms/config"
8 "go.uber.org/zap"
9@@ -75,7 +76,8 @@ func main() {
10
11 logger.Info("replacing tags")
12 for _, post := range posts {
13- parsed, err := shared.ParseText(post.Text, "")
14+ linkify := imgs.NewImgsLinkify(post.Username, true, false)
15+ parsed, err := shared.ParseText(post.Text, linkify)
16 if err != nil {
17 continue
18 }
M
go.mod
+12,
-0
1@@ -21,6 +21,8 @@ require (
2 github.com/microcosm-cc/bluemonday v1.0.19
3 github.com/minio/minio-go/v7 v7.0.34
4 github.com/muesli/reflow v0.3.0
5+ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
6+ github.com/nickalie/go-webpbin v0.0.0-20220110095747-f10016bf2dc1
7 github.com/pkg/sftp v1.13.5
8 github.com/yuin/goldmark v1.4.13
9 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
10@@ -41,8 +43,11 @@ require (
11 github.com/charmbracelet/keygen v0.3.0 // indirect
12 github.com/containerd/console v1.0.3 // indirect
13 github.com/dlclark/regexp2 v1.7.0 // indirect
14+ github.com/dsnet/compress v0.0.1 // indirect
15 github.com/dustin/go-humanize v1.0.0 // indirect
16+ github.com/frankban/quicktest v1.14.3 // indirect
17 github.com/golang/protobuf v1.5.2 // indirect
18+ github.com/golang/snappy v0.0.4 // indirect
19 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
20 github.com/google/uuid v1.3.0 // indirect
21 github.com/gorilla/css v1.0.0 // indirect
22@@ -50,11 +55,13 @@ require (
23 github.com/klauspost/compress v1.15.9 // indirect
24 github.com/klauspost/cpuid/v2 v2.1.0 // indirect
25 github.com/kr/fs v0.1.0 // indirect
26+ github.com/kr/pretty v0.3.0 // indirect
27 github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
28 github.com/mattn/go-isatty v0.0.16 // indirect
29 github.com/mattn/go-localereader v0.0.1 // indirect
30 github.com/mattn/go-runewidth v0.0.13 // indirect
31 github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
32+ github.com/mholt/archiver v3.1.1+incompatible // indirect
33 github.com/minio/md5-simd v1.1.2 // indirect
34 github.com/minio/sha256-simd v1.0.0 // indirect
35 github.com/mitchellh/go-homedir v1.1.0 // indirect
36@@ -64,6 +71,9 @@ require (
37 github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
38 github.com/muesli/cancelreader v0.2.2 // indirect
39 github.com/muesli/termenv v0.12.0 // indirect
40+ github.com/nickalie/go-binwrapper v0.0.0-20190114141239-525121d43c84 // indirect
41+ github.com/nwaples/rardecode v1.1.0 // indirect
42+ github.com/pierrec/lz4 v2.6.1+incompatible // indirect
43 github.com/prometheus/client_golang v1.13.0 // indirect
44 github.com/prometheus/client_model v0.2.0 // indirect
45 github.com/prometheus/common v0.37.0 // indirect
46@@ -71,6 +81,8 @@ require (
47 github.com/rivo/uniseg v0.3.4 // indirect
48 github.com/rs/xid v1.4.0 // indirect
49 github.com/sirupsen/logrus v1.9.0 // indirect
50+ github.com/ulikunitz/xz v0.5.10 // indirect
51+ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
52 go.uber.org/atomic v1.10.0 // indirect
53 go.uber.org/multierr v1.8.0 // indirect
54 golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c // indirect
M
go.sum
+36,
-2
1@@ -84,18 +84,24 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
2 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
3 github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
4 github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
5+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
6 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
8 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
10 github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
11 github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
12+github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
13+github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
14+github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
15 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
16 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
17 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
18 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
19 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
20 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
21+github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
22+github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
23 github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw=
24 github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914=
25 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
26@@ -139,6 +145,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
27 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
28 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
29 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
30+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
31+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
32 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
33 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
34 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
35@@ -150,6 +158,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
36 github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
37 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
38 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
39+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
40 github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
41 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
42 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
43@@ -186,8 +195,10 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
44 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
45 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
46 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
47+github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
48 github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
49 github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
50+github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
51 github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
52 github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
53 github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
54@@ -197,11 +208,13 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
55 github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
56 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
57 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
58-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
59 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
60+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
61+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
62 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
63-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
64 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
65+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
66+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
67 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
68 github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
69 github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
70@@ -220,6 +233,8 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4
71 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
72 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
73 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
74+github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
75+github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
76 github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c=
77 github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE=
78 github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
79@@ -254,6 +269,16 @@ github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc
80 github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A=
81 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
82 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
83+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
84+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
85+github.com/nickalie/go-binwrapper v0.0.0-20190114141239-525121d43c84 h1:/6MoQlTdk1eAi0J9O89ypO8umkp+H7mpnSF2ggSL62Q=
86+github.com/nickalie/go-binwrapper v0.0.0-20190114141239-525121d43c84/go.mod h1:Eeech2fhQ/E4bS8cdc3+SGABQ+weQYGyWBvZ/mNr5uY=
87+github.com/nickalie/go-webpbin v0.0.0-20220110095747-f10016bf2dc1 h1:9awJsNP+gYOGCr3pQu9i217bCNsVwoQCmD3h7CYwxOw=
88+github.com/nickalie/go-webpbin v0.0.0-20220110095747-f10016bf2dc1/go.mod h1:m5oz0fmp+uyRBxxFkvciIpe1wd2JZ3pDVJ3x/D8/EGw=
89+github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
90+github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
91+github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
92+github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
93 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
94 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
95 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
96@@ -292,6 +317,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
97 github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
98 github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
99 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
100+github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
101+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
102 github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
103 github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
104 github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
105@@ -308,6 +335,11 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
106 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
107 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
108 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
109+github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
110+github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
111+github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
112+github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
113+github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
114 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
115 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
116 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
117@@ -355,6 +387,8 @@ golang.org/x/exp v0.0.0-20220823124025-807a23277127 h1:S4NrSKDfihhl3+4jSTgwoIevK
118 golang.org/x/exp v0.0.0-20220823124025-807a23277127/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
119 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
120 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
121+golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
122+golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
123 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
124 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
125 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+249,
-43
1@@ -1,11 +1,17 @@
2 package imgs
3
4 import (
5+ "bufio"
6 "bytes"
7 "fmt"
8 "html/template"
9+ "image"
10+ gif "image/gif"
11+ jpeg "image/jpeg"
12+ png "image/png"
13 "net/http"
14 "net/url"
15+ "strconv"
16 "strings"
17 "time"
18
19@@ -14,6 +20,9 @@ import (
20 "git.sr.ht/~erock/pico/imgs/storage"
21 "git.sr.ht/~erock/pico/shared"
22 "github.com/gorilla/feeds"
23+ "github.com/nfnt/resize"
24+ "github.com/nickalie/go-webpbin"
25+ "go.uber.org/zap"
26 "golang.org/x/exp/slices"
27 )
28
29@@ -96,10 +105,6 @@ func GetBlogName(username string) string {
30 return username
31 }
32
33-func isRequestTrackable(r *http.Request) bool {
34- return true
35-}
36-
37 func blogHandler(w http.ResponseWriter, r *http.Request) {
38 username := shared.GetUsernameFromRequest(r)
39 dbpool := shared.GetDB(r)
40@@ -154,9 +159,13 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
41
42 postCollection := make([]*PostItemData, 0, len(posts))
43 for _, post := range posts {
44+ url := fmt.Sprintf(
45+ "%s/300x",
46+ cfg.ImgURL(post.Username, post.Slug, onSubdomain, withUserName),
47+ )
48 postCollection = append(postCollection, &PostItemData{
49- ImgURL: template.URL(cfg.ImgURL(post.Username, post.Filename, onSubdomain, withUserName)),
50- URL: template.URL(cfg.ImgURL(post.Username, post.Slug, onSubdomain, withUserName)),
51+ ImgURL: template.URL(url),
52+ URL: template.URL(cfg.ImgPostURL(post.Username, post.Slug, onSubdomain, withUserName)),
53 Caption: post.Title,
54 PublishAt: post.PublishAt.Format("02 Jan, 2006"),
55 PublishAtISO: post.PublishAt.Format(time.RFC3339),
56@@ -182,62 +191,255 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
57 }
58 }
59
60-func imgHandler(w http.ResponseWriter, r *http.Request) {
61- username := shared.GetUsernameFromRequest(r)
62- subdomain := shared.GetSubdomain(r)
63- cfg := shared.GetCfg(r)
64+type deviceType int
65
66- var filename string
67- if !cfg.IsSubdomains() || subdomain == "" {
68- filename, _ = url.PathUnescape(shared.GetField(r, 1))
69- } else {
70- filename, _ = url.PathUnescape(shared.GetField(r, 0))
71+const (
72+ desktopDevice deviceType = iota
73+)
74+
75+type ImgOptimizer struct {
76+ // Specify the compression factor for RGB channels between 0 and 100. The default is 75.
77+ // A small factor produces a smaller file with lower quality.
78+ // Best quality is achieved by using a value of 100.
79+ Quality uint
80+ Optimized bool
81+ Width uint
82+ Height uint
83+ DeviceType deviceType
84+ Output []byte
85+ Dimes string
86+}
87+
88+func (h *ImgOptimizer) GetImage(contents []byte, mimeType string) (image.Image, error) {
89+ r := bytes.NewReader(contents)
90+ switch mimeType {
91+ case "image/png":
92+ return png.Decode(r)
93+ case "image/jpeg":
94+ return jpeg.Decode(r)
95+ case "image/jpg":
96+ return jpeg.Decode(r)
97+ case "image/gif":
98+ return gif.Decode(r)
99 }
100
101- dbpool := shared.GetDB(r)
102- st := shared.GetStorage(r)
103- logger := shared.GetLogger(r)
104+ return nil, fmt.Errorf("(%s) not supported optimization", mimeType)
105+}
106
107- user, err := dbpool.FindUserForName(username)
108+func (h *ImgOptimizer) GetRatio() error {
109+ // dimes = x250 -- width is auto scaled and height is 250
110+ if strings.HasPrefix(h.Dimes, "x") {
111+ height, err := strconv.ParseUint(h.Dimes[1:], 10, 64)
112+ if err != nil {
113+ return err
114+ }
115+ h.Height = uint(height)
116+ }
117+
118+ // dimes = 250x -- width is 250 and height is auto scaled
119+ if strings.HasSuffix(h.Dimes, "x") {
120+ width, err := strconv.ParseUint(h.Dimes[:len(h.Dimes)-1], 10, 64)
121+ if err != nil {
122+ return err
123+ }
124+ h.Width = uint(width)
125+ }
126+
127+ res := strings.Split(h.Dimes, "x")
128+ if len(res) != 2 {
129+ return fmt.Errorf("(%s) must be in format (x200, 200x, or 200x200)", h.Dimes)
130+ }
131+
132+ width, err := strconv.ParseUint(res[0], 10, 64)
133 if err != nil {
134- logger.Infof("blog not found: %s", username)
135+ return err
136+ }
137+ h.Width = uint(width)
138+
139+ height, err := strconv.ParseUint(res[1], 10, 64)
140+ if err != nil {
141+ return err
142+ }
143+ h.Height = uint(height)
144+
145+ return nil
146+}
147+
148+type SubImager interface {
149+ SubImage(r image.Rectangle) image.Image
150+}
151+
152+func (h *ImgOptimizer) Process(contents []byte, mimeType string) ([]byte, error) {
153+ if !h.Optimized {
154+ return contents, nil
155+ }
156+
157+ img, err := h.GetImage(contents, mimeType)
158+ if err != nil {
159+ return []byte{}, err
160+ }
161+
162+ nextImg := img
163+ if h.Height > 0 || h.Width > 0 {
164+ nextImg = resize.Resize(h.Width, h.Height, img, resize.NearestNeighbor)
165+ }
166+
167+ encoder := webpbin.Encoder{
168+ Quality: h.Quality,
169+ }
170+
171+ var output bytes.Buffer
172+ w := bufio.NewWriter(&output)
173+
174+ err = encoder.Encode(w, nextImg)
175+ if err != nil {
176+ return []byte{}, err
177+ }
178+
179+ return output.Bytes(), nil
180+}
181+
182+func NewImgOptimizer(logger *zap.SugaredLogger, optimized bool, dimes string) *ImgOptimizer {
183+ opt := &ImgOptimizer{
184+ Optimized: optimized,
185+ DeviceType: desktopDevice,
186+ Quality: 75,
187+ Dimes: dimes,
188+ }
189+
190+ err := opt.GetRatio()
191+ if err != nil {
192+ logger.Error(err)
193+ }
194+ return opt
195+}
196+
197+type ImgHandler struct {
198+ Username string
199+ Subdomain string
200+ Slug string
201+ Cfg *shared.ConfigSite
202+ Dbpool db.DB
203+ Storage storage.ObjectStorage
204+ Logger *zap.SugaredLogger
205+ Img *ImgOptimizer
206+}
207+
208+func imgHandler(w http.ResponseWriter, h *ImgHandler) {
209+ user, err := h.Dbpool.FindUserForName(h.Username)
210+ if err != nil {
211+ h.Logger.Infof("blog not found: %s", h.Username)
212 http.Error(w, "blog not found", http.StatusNotFound)
213 return
214 }
215
216- post, err := dbpool.FindPostWithFilename(filename, user.ID, cfg.Space)
217+ post, err := h.Dbpool.FindPostWithSlug(h.Slug, user.ID, h.Cfg.Space)
218 if err != nil {
219- logger.Infof("image not found %s/%s", username, filename)
220+ h.Logger.Infof("image not found %s/%s", h.Username, h.Slug)
221 http.Error(w, err.Error(), http.StatusInternalServerError)
222 return
223 }
224
225- // validate and fire off analytic event
226- if isRequestTrackable(r) {
227- _, err := dbpool.AddViewCount(post.ID)
228- if err != nil {
229- logger.Error(err)
230- }
231+ _, err = h.Dbpool.AddViewCount(post.ID)
232+ if err != nil {
233+ h.Logger.Error(err)
234 }
235
236- bucket, err := st.GetBucket(user.ID)
237+ bucket, err := h.Storage.GetBucket(user.ID)
238 if err != nil {
239- logger.Infof("bucket not found %s/%s", username, filename)
240+ h.Logger.Infof("bucket not found %s/%s", h.Username, post.Filename)
241 http.Error(w, err.Error(), http.StatusInternalServerError)
242 return
243 }
244- contents, err := st.GetFile(bucket, post.Filename)
245+ contents, err := h.Storage.GetFile(bucket, post.Filename)
246 if err != nil {
247- logger.Infof("file not found %s/%s", username, post.Filename)
248+ h.Logger.Infof("file not found %s/%s", h.Username, post.Filename)
249 http.Error(w, err.Error(), http.StatusInternalServerError)
250 return
251 }
252
253- w.Header().Add("Content-Type", "image/png")
254- _, err = w.Write(contents)
255+ if h.Img.Optimized {
256+ w.Header().Add("Content-Type", "image/webp")
257+ } else {
258+ w.Header().Add("Content-Type", post.MimeType)
259+ }
260+
261+ contentsProc, err := h.Img.Process(contents, strings.TrimSpace(post.MimeType))
262 if err != nil {
263- logger.Error(err)
264+ h.Logger.Error(err)
265+ }
266+
267+ _, err = w.Write(contentsProc)
268+ if err != nil {
269+ h.Logger.Error(err)
270+ }
271+}
272+
273+func imgRequestOriginal(w http.ResponseWriter, r *http.Request) {
274+ username := shared.GetUsernameFromRequest(r)
275+ subdomain := shared.GetSubdomain(r)
276+ cfg := shared.GetCfg(r)
277+
278+ var slug string
279+ if !cfg.IsSubdomains() || subdomain == "" {
280+ slug, _ = url.PathUnescape(shared.GetField(r, 1))
281+ } else {
282+ slug, _ = url.PathUnescape(shared.GetField(r, 0))
283 }
284+
285+ // users might add the file extension when requesting an image
286+ // but we want to remove that
287+ slug = shared.SanitizeFileExt(slug)
288+
289+ dbpool := shared.GetDB(r)
290+ st := shared.GetStorage(r)
291+ logger := shared.GetLogger(r)
292+
293+ imgHandler(w, &ImgHandler{
294+ Username: username,
295+ Subdomain: subdomain,
296+ Slug: slug,
297+ Cfg: cfg,
298+ Dbpool: dbpool,
299+ Storage: st,
300+ Logger: logger,
301+ Img: NewImgOptimizer(logger, false, ""),
302+ })
303+}
304+
305+func imgRequest(w http.ResponseWriter, r *http.Request) {
306+ username := shared.GetUsernameFromRequest(r)
307+ subdomain := shared.GetSubdomain(r)
308+ cfg := shared.GetCfg(r)
309+
310+ var dimes string
311+ var slug string
312+ if !cfg.IsSubdomains() || subdomain == "" {
313+ slug, _ = url.PathUnescape(shared.GetField(r, 1))
314+ dimes, _ = url.PathUnescape(shared.GetField(r, 2))
315+ } else {
316+ slug, _ = url.PathUnescape(shared.GetField(r, 0))
317+ dimes, _ = url.PathUnescape(shared.GetField(r, 1))
318+ }
319+
320+ // users might add the file extension when requesting an image
321+ // but we want to remove that
322+ slug = shared.SanitizeFileExt(slug)
323+
324+ dbpool := shared.GetDB(r)
325+ st := shared.GetStorage(r)
326+ logger := shared.GetLogger(r)
327+
328+ imgHandler(w, &ImgHandler{
329+ Username: username,
330+ Subdomain: subdomain,
331+ Slug: slug,
332+ Cfg: cfg,
333+ Dbpool: dbpool,
334+ Storage: st,
335+ Logger: logger,
336+ Img: NewImgOptimizer(logger, true, dimes),
337+ })
338 }
339
340 func postHandler(w http.ResponseWriter, r *http.Request) {
341@@ -272,7 +474,8 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
342 var data PostPageData
343 post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space)
344 if err == nil {
345- parsed, err := shared.ParseText(post.Text, cfg.ImgURL(username, "", true, false))
346+ linkify := NewImgsLinkify(username, onSubdomain, withUserName)
347+ parsed, err := shared.ParseText(post.Text, linkify)
348 if err != nil {
349 logger.Error(err)
350 }
351@@ -302,7 +505,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
352 Username: username,
353 BlogName: blogName,
354 Contents: template.HTML(text),
355- ImgURL: template.URL(cfg.ImgURL(username, post.Filename, onSubdomain, withUserName)),
356+ ImgURL: template.URL(cfg.ImgURL(username, post.Slug, onSubdomain, withUserName)),
357 Tags: tagLinks,
358 }
359 } else {
360@@ -431,7 +634,7 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
361 }
362 var tpl bytes.Buffer
363 data := &PostPageData{
364- ImgURL: template.URL(cfg.ImgURL(username, post.Filename, onSubdomain, withUserName)),
365+ ImgURL: template.URL(cfg.ImgURL(username, post.Slug, onSubdomain, withUserName)),
366 }
367 if err := ts.Execute(&tpl, data); err != nil {
368 continue
369@@ -510,7 +713,7 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
370 for _, post := range pager.Data {
371 var tpl bytes.Buffer
372 data := &PostPageData{
373- ImgURL: template.URL(cfg.ImgURL(post.Username, post.Filename, onSubdomain, withUserName)),
374+ ImgURL: template.URL(cfg.ImgURL(post.Username, post.Slug, onSubdomain, withUserName)),
375 }
376 if err := ts.Execute(&tpl, data); err != nil {
377 continue
378@@ -593,8 +796,9 @@ func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
379 shared.NewRoute("GET", "/([^/]+)/rss.xml", rssBlogHandler),
380 shared.NewRoute("GET", "/([^/]+)/atom.xml", rssBlogHandler),
381 shared.NewRoute("GET", "/([^/]+)/feed.xml", rssBlogHandler),
382- shared.NewRoute("GET", "/([^/]+)/([^/]+\\..+)", imgHandler),
383- shared.NewRoute("GET", "/([^/]+)/([^/]+)", postHandler),
384+ shared.NewRoute("GET", "/([^/]+)/([^/]+)", imgRequest),
385+ shared.NewRoute("GET", "/([^/]+)/o/([^/]+)", imgRequestOriginal),
386+ shared.NewRoute("GET", "/([^/]+)/p/([^/]+)", postHandler),
387 )
388
389 return routes
390@@ -613,8 +817,10 @@ func createSubdomainRoutes(staticRoutes []shared.Route) []shared.Route {
391
392 routes = append(
393 routes,
394- shared.NewRoute("GET", "/([^/]+\\..+)", imgHandler),
395- shared.NewRoute("GET", "/([^/]+)", postHandler),
396+ shared.NewRoute("GET", "/([^/]+)", imgRequest),
397+ shared.NewRoute("GET", "/([^/]+)/([a-z0-9]+)", imgRequest),
398+ shared.NewRoute("GET", "/o/([^/]+)", imgRequestOriginal),
399+ shared.NewRoute("GET", "/p/([^/]+)", postHandler),
400 )
401
402 return routes
+16,
-4
1@@ -7,13 +7,25 @@ import (
2 "git.sr.ht/~erock/pico/wish/cms/config"
3 )
4
5-func ImgBaseURL(username string) string {
6+type ImgsLinkify struct {
7+ Cfg *shared.ConfigSite
8+ Username string
9+ OnSubdomain bool
10+ WithUsername bool
11+}
12+
13+func NewImgsLinkify(username string, onSubdomain, withUsername bool) *ImgsLinkify {
14 cfg := NewConfigSite()
15- if cfg.IsSubdomains() {
16- return fmt.Sprintf("%s://%s.%s", cfg.Protocol, username, cfg.Domain)
17+ return &ImgsLinkify{
18+ Cfg: cfg,
19+ Username: username,
20+ OnSubdomain: onSubdomain,
21+ WithUsername: withUsername,
22 }
23+}
24
25- return "/"
26+func (i *ImgsLinkify) Create(fname string) string {
27+ return i.Cfg.ImgURL(i.Username, fname, i.OnSubdomain, i.WithUsername)
28 }
29
30 func NewConfigSite() *shared.ConfigSite {
+11,
-6
1@@ -203,7 +203,8 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
2
3 readme, err := dbpool.FindPostWithFilename("_readme.md", user.ID, cfg.Space)
4 if err == nil {
5- parsedText, err := shared.ParseText(readme.Text, imgs.ImgBaseURL(readme.Username))
6+ linkify := imgs.NewImgsLinkify(readme.Username, onSubdomain, withUserName)
7+ parsedText, err := shared.ParseText(readme.Text, linkify)
8 if err != nil {
9 logger.Error(err)
10 }
11@@ -352,8 +353,9 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
12 hasCSS := false
13 var data PostPageData
14 post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space)
15+ linkify := imgs.NewImgsLinkify(username, onSubdomain, withUserName)
16 if err == nil {
17- parsedText, err := shared.ParseText(post.Text, imgs.ImgBaseURL(username))
18+ parsedText, err := shared.ParseText(post.Text, linkify)
19 if err != nil {
20 logger.Error(err)
21 }
22@@ -361,7 +363,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
23 // we need the blog name from the readme unfortunately
24 readme, err := dbpool.FindPostWithFilename("_readme.md", user.ID, cfg.Space)
25 if err == nil {
26- readmeParsed, err := shared.ParseText(readme.Text, imgs.ImgBaseURL(username))
27+ readmeParsed, err := shared.ParseText(readme.Text, linkify)
28 if err != nil {
29 logger.Error(err)
30 }
31@@ -604,7 +606,8 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
32
33 readme, err := dbpool.FindPostWithFilename("_readme.md", user.ID, cfg.Space)
34 if err == nil {
35- parsedText, err := shared.ParseText(readme.Text, imgs.ImgBaseURL(readme.Username))
36+ linkify := imgs.NewImgsLinkify(readme.Username, true, false)
37+ parsedText, err := shared.ParseText(readme.Text, linkify)
38 if err != nil {
39 logger.Error(err)
40 }
41@@ -636,7 +639,8 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
42 if slices.Contains(cfg.HiddenPosts, post.Filename) {
43 continue
44 }
45- parsed, err := shared.ParseText(post.Text, imgs.ImgBaseURL(post.Username))
46+ linkify := imgs.NewImgsLinkify(post.Username, true, false)
47+ parsed, err := shared.ParseText(post.Text, linkify)
48 if err != nil {
49 logger.Error(err)
50 }
51@@ -719,7 +723,8 @@ func rssHandler(w http.ResponseWriter, r *http.Request) {
52
53 var feedItems []*feeds.Item
54 for _, post := range pager.Data {
55- parsed, err := shared.ParseText(post.Text, imgs.ImgBaseURL(post.Username))
56+ linkify := imgs.NewImgsLinkify(post.Username, onSubdomain, withUserName)
57+ parsed, err := shared.ParseText(post.Text, linkify)
58 if err != nil {
59 logger.Error(err)
60 }
+3,
-1
1@@ -6,6 +6,7 @@ import (
2
3 "git.sr.ht/~erock/pico/db"
4 "git.sr.ht/~erock/pico/filehandlers"
5+ "git.sr.ht/~erock/pico/imgs"
6 "git.sr.ht/~erock/pico/shared"
7 "golang.org/x/exp/slices"
8 )
9@@ -45,7 +46,8 @@ func (p *MarkdownHooks) FileValidate(data *filehandlers.PostMetaData) (bool, err
10 }
11
12 func (p *MarkdownHooks) FileMeta(data *filehandlers.PostMetaData) error {
13- parsedText, err := shared.ParseText(data.Text, "")
14+ linkify := imgs.NewImgsLinkify("", true, false)
15+ parsedText, err := shared.ParseText(data.Text, linkify)
16 // we return nil here because we don't want the file upload to fail
17 if err != nil {
18 return nil
1@@ -155,6 +155,32 @@ func (c *ConfigSite) ImgURL(username string, slug string, onSubdomain bool, with
2 return fmt.Sprintf("/%s", fname)
3 }
4
5+func (c *ConfigSite) ImgPostURL(username string, slug string, onSubdomain bool, withUserName bool) string {
6+ fname := url.PathEscape(strings.TrimLeft(slug, "/"))
7+ if c.IsSubdomains() && onSubdomain {
8+ return fmt.Sprintf("%s://%s.%s/p/%s", c.Protocol, username, c.Domain, fname)
9+ }
10+
11+ if withUserName {
12+ return fmt.Sprintf("/%s/p/%s", username, fname)
13+ }
14+
15+ return fmt.Sprintf("/p/%s", fname)
16+}
17+
18+func (c *ConfigSite) ImgOrigURL(username string, slug string, onSubdomain bool, withUserName bool) string {
19+ fname := url.PathEscape(strings.TrimLeft(slug, "/"))
20+ if c.IsSubdomains() && onSubdomain {
21+ return fmt.Sprintf("%s://%s.%s/o/%s", c.Protocol, username, c.Domain, fname)
22+ }
23+
24+ if withUserName {
25+ return fmt.Sprintf("/%s/o/%s", username, fname)
26+ }
27+
28+ return fmt.Sprintf("/o/%s", fname)
29+}
30+
31 func (c *ConfigSite) TagURL(username, tag string, onSubdomain, withUserName bool) string {
32 tg := url.PathEscape(tag)
33 return fmt.Sprintf("%s?tag=%s", c.FullBlogURL(username, onSubdomain, withUserName), tg)
1@@ -158,21 +158,28 @@ func (r *ImgRender) renderImage(w util.BufWriter, source []byte, node ast.Node,
2 return ast.WalkSkipChildren, nil
3 }
4
5-func createImgURL(absURL string) func([]byte) []byte {
6+type Linkify interface {
7+ Create(fname string) string
8+}
9+
10+func createImgURL(linkify Linkify) func([]byte) []byte {
11 return func(url []byte) []byte {
12 if url[0] == '/' {
13- nextURL := fmt.Sprintf("%s%s", absURL, string(url))
14+ name := SanitizeFileExt(string(url))
15+ // fmt.Sprintf("%s%s", absURL, name)
16+ nextURL := linkify.Create(name)
17 return []byte(nextURL)
18 } else if bytes.HasPrefix(url, []byte{'.', '/'}) {
19- fname := url[1:]
20- nextURL := fmt.Sprintf("%s%s", absURL, string(fname))
21+ name := SanitizeFileExt(string(url[1:]))
22+ // nextURL := fmt.Sprintf("%s%s", absURL, name)
23+ nextURL := linkify.Create(name)
24 return []byte(nextURL)
25 }
26 return url
27 }
28 }
29
30-func ParseText(text string, absURL string) (*ParsedText, error) {
31+func ParseText(text string, linkify Linkify) (*ParsedText, error) {
32 parsed := ParsedText{
33 MetaData: &MetaData{
34 Tags: []string{},
35@@ -198,7 +205,7 @@ func ParseText(text string, absURL string) (*ParsedText, error) {
36 goldmark.WithRendererOptions(
37 ghtml.WithUnsafe(),
38 renderer.WithNodeRenderers(
39- util.Prioritized(NewImgsRenderer(createImgURL(absURL)), 0),
40+ util.Prioritized(NewImgsRenderer(createImgURL(linkify)), 0),
41 ),
42 ),
43 )
44@@ -214,9 +221,11 @@ func ParseText(text string, absURL string) (*ParsedText, error) {
45 parsed.MetaData.Layout = toString(metaData["layout"])
46 parsed.MetaData.Image = toString(metaData["image"])
47 if strings.HasPrefix(parsed.Image, "/") {
48- parsed.MetaData.Image = fmt.Sprintf("%s%s", absURL, parsed.Image)
49+ parsed.Image = linkify.Create(parsed.Image)
50+ // parsed.MetaData.Image = fmt.Sprintf("%s%s", absURL, parsed.Image)
51 } else if strings.HasPrefix(parsed.Image, "./") {
52- parsed.MetaData.Image = fmt.Sprintf("%s%s", absURL, parsed.Image[1:])
53+ parsed.Image = linkify.Create(parsed.Image[1:])
54+ // parsed.MetaData.Image = fmt.Sprintf("%s%s", absURL, parsed.Image[1:])
55 }
56 parsed.MetaData.ImageCard = toString(metaData["card"])
57
1@@ -105,6 +105,9 @@ func GetStorage(r *http.Request) storage.ObjectStorage {
2
3 func GetField(r *http.Request, index int) string {
4 fields := r.Context().Value(ctxKey{}).([]string)
5+ if index >= len(fields) {
6+ return ""
7+ }
8 return fields[index]
9 }
10