mirror of
https://github.com/httpjamesm/AnonymousOverflow.git
synced 2024-12-06 19:16:43 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d8632e26c | |||
| eee8ec42c5 | |||
| d7bca93af8 | |||
| bd48b309ac | |||
| 15b843d46b |
@@ -6,11 +6,11 @@ This project is super lightweight by design. The UI is simple and the frontend i
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Clearnet Instances
|
## Clearnet Instances
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
var Version = "1.5"
|
var Version = "1.5.1"
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ func main() {
|
|||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
r.LoadHTMLGlob("templates/*")
|
r.LoadHTMLGlob("templates/*")
|
||||||
r.Static("/static", "./public")
|
|
||||||
|
|
||||||
r.Use(gin.Recovery())
|
r.Use(gin.Recovery())
|
||||||
r.Use(middleware.XssPreventionHeaders())
|
r.Use(middleware.XssPreventionHeaders())
|
||||||
@@ -40,6 +39,8 @@ func main() {
|
|||||||
r.Use(middleware.OptionsMiddleware())
|
r.Use(middleware.OptionsMiddleware())
|
||||||
r.Use(middleware.Ratelimit())
|
r.Use(middleware.Ratelimit())
|
||||||
|
|
||||||
|
r.GET("/static/*filepath", routes.StaticContent)
|
||||||
|
|
||||||
r.GET("/robots.txt", func(c *gin.Context) {
|
r.GET("/robots.txt", func(c *gin.Context) {
|
||||||
c.String(200, "User-agent: *\nDisallow: /")
|
c.String(200, "User-agent: *\nDisallow: /")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 491 B |
@@ -111,6 +111,42 @@ img {
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.answers-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answers-header .sorting form {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answers-header select {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: var(--meta-bg);
|
||||||
|
color: var(--text-color);
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answers-header button {
|
||||||
|
width: 2rem;
|
||||||
|
border: 0;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--meta-bg);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@media only screen and (max-width: 800px) {
|
||||||
body {
|
body {
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
|
|||||||
+1
-12
@@ -19,12 +19,6 @@ func GetImage(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
url := c.Query("url")
|
|
||||||
if url == "" {
|
|
||||||
c.String(400, "Missing url")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate the auth token
|
// validate the auth token
|
||||||
token, err := jwt.ParseWithClaims(authorization, &types.ImageProxyClaims{}, func(token *jwt.Token) (interface{}, error) {
|
token, err := jwt.ParseWithClaims(authorization, &types.ImageProxyClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
|
||||||
@@ -51,11 +45,6 @@ func GetImage(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if claims.ImageURL != url {
|
|
||||||
c.String(400, "Request & token mismatch")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if claims.Exp < time.Now().Unix() {
|
if claims.Exp < time.Now().Unix() {
|
||||||
c.String(400, "Token expired")
|
c.String(400, "Token expired")
|
||||||
return
|
return
|
||||||
@@ -63,7 +52,7 @@ func GetImage(c *gin.Context) {
|
|||||||
|
|
||||||
// download the image
|
// download the image
|
||||||
client := resty.New()
|
client := resty.New()
|
||||||
resp, err := client.R().Get(url)
|
resp, err := client.R().Get(claims.ImageURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatus(500)
|
c.AbortWithStatus(500)
|
||||||
return
|
return
|
||||||
|
|||||||
+19
-1
@@ -21,6 +21,13 @@ import (
|
|||||||
var codeBlockRegex = regexp.MustCompile(`(?s)<pre><code>(.+?)<\/code><\/pre>`)
|
var codeBlockRegex = regexp.MustCompile(`(?s)<pre><code>(.+?)<\/code><\/pre>`)
|
||||||
var questionCodeBlockRegex = regexp.MustCompile(`(?s)<pre class=".+"><code( class=".+")?>(.+?)</code></pre>`)
|
var questionCodeBlockRegex = regexp.MustCompile(`(?s)<pre class=".+"><code( class=".+")?>(.+?)</code></pre>`)
|
||||||
|
|
||||||
|
var soSortValues = map[string]string{
|
||||||
|
"votes": "scoredesc",
|
||||||
|
"trending": "trending",
|
||||||
|
"newest": "modifieddesc",
|
||||||
|
"oldest": "createdasc",
|
||||||
|
}
|
||||||
|
|
||||||
func ViewQuestion(c *gin.Context) {
|
func ViewQuestion(c *gin.Context) {
|
||||||
client := resty.New()
|
client := resty.New()
|
||||||
|
|
||||||
@@ -36,7 +43,17 @@ func ViewQuestion(c *gin.Context) {
|
|||||||
|
|
||||||
questionTitle := c.Param("title")
|
questionTitle := c.Param("title")
|
||||||
|
|
||||||
soLink := fmt.Sprintf("https://stackoverflow.com/questions/%s/%s", questionId, questionTitle)
|
sortValue := c.Query("sort_by")
|
||||||
|
if sortValue == "" {
|
||||||
|
sortValue = "votes"
|
||||||
|
}
|
||||||
|
|
||||||
|
soSortValue, ok := soSortValues[sortValue]
|
||||||
|
if !ok {
|
||||||
|
soSortValue = soSortValues["votes"]
|
||||||
|
}
|
||||||
|
|
||||||
|
soLink := fmt.Sprintf("https://stackoverflow.com/questions/%s/%s?answertab=%s", questionId, questionTitle, soSortValue)
|
||||||
|
|
||||||
resp, err := client.R().Get(soLink)
|
resp, err := client.R().Get(soLink)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -251,6 +268,7 @@ func ViewQuestion(c *gin.Context) {
|
|||||||
"imagePolicy": imagePolicy,
|
"imagePolicy": imagePolicy,
|
||||||
"theme": c.MustGet("theme").(string),
|
"theme": c.MustGet("theme").(string),
|
||||||
"currentUrl": fmt.Sprintf("%s/questions/%s/%s", os.Getenv("APP_URL"), questionId, questionTitle),
|
"currentUrl": fmt.Sprintf("%s/questions/%s/%s", os.Getenv("APP_URL"), questionId, questionTitle),
|
||||||
|
"sortValue": sortValue,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StaticContent(c *gin.Context) {
|
||||||
|
cleanFilePath := strings.ReplaceAll(c.Param("filepath"), "..", "")
|
||||||
|
|
||||||
|
c.File(fmt.Sprintf("./public/%s", cleanFilePath))
|
||||||
|
}
|
||||||
+1
-1
@@ -25,7 +25,7 @@ func ReplaceImgTags(inHtml string) string {
|
|||||||
authToken, _ := generateImageProxyAuth(src)
|
authToken, _ := generateImageProxyAuth(src)
|
||||||
|
|
||||||
// replace the img tag with a proxied url
|
// replace the img tag with a proxied url
|
||||||
inHtml = strings.Replace(inHtml, imgTag, fmt.Sprintf(`<img src="%s/proxy?url=%s&auth=%s">`, os.Getenv("APP_URL"), src, authToken), 1)
|
inHtml = strings.Replace(inHtml, imgTag, fmt.Sprintf(`<img src="%s/proxy?auth=%s">`, os.Getenv("APP_URL"), authToken), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return inHtml
|
return inHtml
|
||||||
|
|||||||
+23
-7
@@ -38,13 +38,30 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">{{ .question.Body }}</div>
|
<div class="card-body">{{ .question.Body }}</div>
|
||||||
{{ if .question.Comments }}
|
{{ if .question.Comments }} {{ template "comments.html"
|
||||||
{{ template "comments.html" .question }}
|
.question }} {{end}}
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<hr class="post-divider" />
|
<hr class="post-divider" />
|
||||||
<h2>Answers</h2>
|
<div class="answers-header">
|
||||||
|
<h2>Answers</h2>
|
||||||
|
<div class="sorting">
|
||||||
|
<form>
|
||||||
|
<select name="sort_by">
|
||||||
|
<option disabled value="">Sort answers by...</option>
|
||||||
|
<option value="votes"{{ if eq .sortValue "votes" }} selected{{ end }}>Votes</option>
|
||||||
|
<option value="trending"{{ if eq .sortValue "trending" }} selected{{ end }}>Trending</option>
|
||||||
|
<option value="newest"{{ if eq .sortValue "newest" }} selected{{ end }}>Newest</option>
|
||||||
|
<option value="oldest"{{ if eq .sortValue "oldest" }} selected{{ end }}>Oldest</option>
|
||||||
|
</select>
|
||||||
|
<button type="submit">
|
||||||
|
<img
|
||||||
|
src="/static/icons/sort.svg"
|
||||||
|
alt="Sieve icon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{ range $answer := .answers }}
|
{{ range $answer := .answers }}
|
||||||
<div class="answer">
|
<div class="answer">
|
||||||
<div
|
<div
|
||||||
@@ -65,8 +82,7 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ if $answer.Comments }}
|
{{ if $answer.Comments }} {{ template "comments.html" $answer }}
|
||||||
{{ template "comments.html" $answer }}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<hr class="answer-divider" />
|
<hr class="answer-divider" />
|
||||||
|
|||||||
Reference in New Issue
Block a user