Compare commits

..

5 Commits

Author SHA1 Message Date
httpjamesm ba5798bf9a feat: remove no-cache middleware 2024-06-20 00:17:41 -04:00
httpjamesm 0b71f4d3bb fix: rename alt for toggle images 2024-06-20 00:16:28 -04:00
httpjamesm 5a77206d26 feat: use css media query to derive theme 2024-06-20 00:15:32 -04:00
jan Tawi Akemi b0ae8a50b5 add instance: https://anonflow.aketawi.space/ (#138)
Co-authored-by: jan Tawi Akemi <aketawi@proton.me>
2024-06-19 12:05:10 -04:00
httpjamesm e278368ab7 fix: set answer ID to data-answerid value (#135) 2024-06-16 14:05:02 -04:00
15 changed files with 29 additions and 159 deletions
+5
View File
@@ -144,6 +144,11 @@
"url": "https://overflow.darkness.services/",
"regions": ["United States"],
"operators": ["https://zzz.darkness.services"]
},
{
"url": "https://anonflow.aketawi.space/",
"regions": ["Russia"],
"operators": ["https://www.aketawi.space/"]
}
],
-1
View File
@@ -35,7 +35,6 @@ func main() {
r.Use(gin.Recovery())
r.Use(middleware.XssPreventionHeaders())
r.Use(middleware.NoCacheMiddleware())
r.Use(middleware.OptionsMiddleware())
r.Use(middleware.Ratelimit())
+14 -12
View File
@@ -11,17 +11,19 @@
--link-color: #92adff;
}
[data-theme='light'] {
--main-bg: #dbdbdb;
--text-color: #000;
--muted-text-color: #636363;
--code-bg: #36383d;
--code-fg: #ffffff;
--input-bg: #bcbcbc;
--input-bg-hover: #a8a8a8;
--meta-bg: #aaa8a8;
--divider-color: #b5b5b5;
--link-color: #335ad0;
@media (prefers-color-scheme: light) {
:root {
--main-bg: #dbdbdb;
--text-color: #000;
--muted-text-color: #636363;
--code-bg: #36383d;
--code-fg: #ffffff;
--input-bg: #bcbcbc;
--input-bg-hover: #a8a8a8;
--meta-bg: #aaa8a8;
--divider-color: #b5b5b5;
--link-color: #335ad0;
}
}
a {
@@ -76,4 +78,4 @@ details {
.fw-nowrap {
flex-wrap: nowrap;
}
}
-12
View File
@@ -1,12 +0,0 @@
package middleware
import "github.com/gin-gonic/gin"
func NoCacheMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Pragma", "no-cache")
c.Header("Expires", "0")
c.Next()
}
}
-8
View File
@@ -7,7 +7,6 @@ import (
func OptionsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("disable_images", false)
c.Set("theme", "dark")
imagesCookie, err := c.Cookie("disable_images")
if err == nil {
@@ -16,13 +15,6 @@ func OptionsMiddleware() gin.HandlerFunc {
}
}
themeCookie, err := c.Cookie("theme")
if err == nil {
if themeCookie == "light" {
c.Set("theme", "light")
}
}
c.Next()
}
}
-1
View File
@@ -47,7 +47,6 @@ func Ratelimit() gin.HandlerFunc {
if val.(int) > 30 {
c.HTML(429, "home.html", gin.H{
"errorMessage": "You have exceeded the request limit. Please try again in a minute.",
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
c.Abort()
-3
View File
@@ -12,7 +12,6 @@ import (
func GetHome(c *gin.Context) {
c.HTML(200, "home.html", gin.H{
"version": config.Version,
"theme": c.MustGet("theme").(string),
})
}
@@ -62,7 +61,6 @@ func PostHome(c *gin.Context) {
if err := c.ShouldBind(&body); err != nil {
c.HTML(400, "home.html", gin.H{
"errorMessage": "Invalid request body",
"theme": c.MustGet("theme").(string),
})
return
}
@@ -72,7 +70,6 @@ func PostHome(c *gin.Context) {
if translated == "" {
c.HTML(400, "home.html", gin.H{
"errorMessage": "Invalid stack overflow/exchange URL",
"theme": c.MustGet("theme").(string),
})
return
}
-20
View File
@@ -3,8 +3,6 @@ package routes
import (
"anonymousoverflow/config"
"fmt"
"os"
"strings"
"github.com/gin-gonic/gin"
)
@@ -21,26 +19,8 @@ func ChangeOptions(c *gin.Context) {
c.SetCookie("disable_images", fmt.Sprintf("%t", !c.MustGet("disable_images").(bool)), 60*60*24*365*10, "/", "", false, true)
c.HTML(200, "home.html", gin.H{
"successMessage": "Images are now " + text,
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
case "theme":
text := "dark"
if c.MustGet("theme").(string) == "dark" {
text = "light"
}
c.SetCookie("theme", text, 60*60*24*365*10, "/", "", false, true)
// get redirect url from query
redirectUrl := c.Query("redirect_url")
if !strings.HasPrefix(redirectUrl, os.Getenv("APP_URL")) {
redirectUrl = os.Getenv("APP_URL")
}
c.Redirect(302, redirectUrl)
default:
c.String(400, "400 Bad Request")
}
+5 -18
View File
@@ -34,7 +34,6 @@ func ViewQuestion(c *gin.Context) {
if _, err := strconv.Atoi(questionId); err != nil {
c.HTML(400, "home.html", gin.H{
"errorMessage": "Invalid question ID",
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
return
@@ -60,7 +59,6 @@ func ViewQuestion(c *gin.Context) {
if resp.StatusCode() != 200 {
c.HTML(500, "home.html", gin.H{
"errorMessage": fmt.Sprintf("Received a non-OK status code %d", resp.StatusCode()),
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
return
@@ -74,17 +72,15 @@ func ViewQuestion(c *gin.Context) {
if err != nil {
c.HTML(500, "home.html", gin.H{
"errorMessage": "Unable to parse question data",
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
return
}
newFilteredQuestion, err := extractQuestionData(doc, domain, params.Sub)
newFilteredQuestion, err := extractQuestionData(doc, domain)
if err != nil {
c.HTML(500, "home.html", gin.H{
"errorMessage": "Failed to extract question data",
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
return
@@ -94,7 +90,6 @@ func ViewQuestion(c *gin.Context) {
if err != nil {
c.HTML(500, "home.html", gin.H{
"errorMessage": "Failed to extract answer data",
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
return
@@ -110,7 +105,6 @@ func ViewQuestion(c *gin.Context) {
"question": newFilteredQuestion,
"answers": answers,
"imagePolicy": imagePolicy,
"theme": c.MustGet("theme").(string),
"currentUrl": fmt.Sprintf("%s%s", os.Getenv("APP_URL"), c.Request.URL.Path),
"sortValue": params.SoSortValue,
"domain": domain,
@@ -132,7 +126,6 @@ func parseAndValidateParameters(c *gin.Context) (inputs viewQuestionInputs, err
if _, err = strconv.Atoi(questionId); err != nil {
c.HTML(400, "home.html", gin.H{
"errorMessage": "Invalid question ID",
"theme": c.MustGet("theme").(string),
"version": config.Version,
})
return
@@ -167,7 +160,7 @@ func fetchQuestionData(soLink string) (resp *resty.Response, err error) {
}
// extractQuestionData parses the HTML document and extracts question data.
func extractQuestionData(doc *goquery.Document, domain, exchangeSub string) (question types.FilteredQuestion, err error) {
func extractQuestionData(doc *goquery.Document, domain string) (question types.FilteredQuestion, err error) {
// Extract the question title.
questionTextParent := doc.Find("h1.fs-headline1").First()
question.Title = strings.TrimSpace(questionTextParent.Children().First().Text())
@@ -182,15 +175,7 @@ func extractQuestionData(doc *goquery.Document, domain, exchangeSub string) (que
if err != nil {
return question, err
}
questionBodyParentHTML = utils.ProcessHTMLBody(questionBodyParentHTML)
// If there's an exchange sub, prepend the /exchange/:sub prefix to prevent unintended platform switching.
if exchangeSub != "" {
questionBodyParentHTML = utils.ConvertRelativeAnchorURLsToAbsolute(questionBodyParentHTML, fmt.Sprintf("/exchange/%s", exchangeSub))
}
question.Body = template.HTML(questionBodyParentHTML)
question.Body = template.HTML(utils.ProcessHTMLBody(questionBodyParentHTML))
// Extract the shortened body description.
shortenedBody := strings.TrimSpace(questionBodyParent.Text())
@@ -239,6 +224,8 @@ func extractAnswersData(doc *goquery.Document, domain string) ([]types.FilteredA
doc.Find("div.answer").Each(func(i int, s *goquery.Selection) {
var answer types.FilteredAnswer
answer.ID = s.AttrOr("data-answerid", "")
postLayout := s.Find("div.post-layout").First()
// Extract upvotes.
-2
View File
@@ -33,7 +33,6 @@ func RedirectShortenedOverflowURL(c *gin.Context) {
if err != nil {
c.HTML(400, "home.html", gin.H{
"errorMessage": "Unable to fetch stack overflow URL",
"theme": c.MustGet("theme").(string),
})
return
}
@@ -41,7 +40,6 @@ func RedirectShortenedOverflowURL(c *gin.Context) {
if resp.StatusCode() != 302 {
c.HTML(400, "home.html", gin.H{
"errorMessage": fmt.Sprintf("Unexpected HTTP status from origin: %d", resp.StatusCode()),
"theme": c.MustGet("theme").(string),
})
return
}
+1 -27
View File
@@ -42,30 +42,4 @@ func ReplaceStackOverflowLinks(html string) string {
// Replace the href attribute value in the anchor tag
return strings.Replace(match, hrefMatch[1], newUrl, 1)
})
}
var relativeAnchorURLRegex = regexp.MustCompile(`href="(/[^"]+)"`)
func ConvertRelativeAnchorURLsToAbsolute(html, prefix string) string {
if prefix == "" {
return html
}
if !strings.HasSuffix(prefix, "/") {
prefix += "/"
}
return relativeAnchorURLRegex.ReplaceAllStringFunc(html, func(match string) string {
// Extract the URL from the match
url := strings.TrimPrefix(match, `href="`)
url = strings.TrimSuffix(url, `"`)
// If the URL already has the desired prefix, return the match as is
if strings.HasPrefix(url, prefix) {
return match
}
// Otherwise, prepend the prefix
return strings.Replace(match, `href="/`, `href="`+prefix, 1)
})
}
}
+1 -44
View File
@@ -2,11 +2,9 @@ package utils
import (
"fmt"
"log"
"github.com/stretchr/testify/assert"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
var sampleInput = `<div class="d-flex fd-column fw-nowrap">
@@ -49,44 +47,3 @@ func TestReplaceStackOverflowLinks(t *testing.T) {
assert.False(t, strings.Contains(replacedLinks, "stackoverflow.com"))
assert.False(t, strings.Contains(replacedLinks, "stackexchange.com"))
}
var sampleRelativeAnchorURLsInput = `<aside class="s-notice s-notice__info post-notice js-post-notice mb16" role="status">
<div class="d-flex fd-column fw-nowrap">
<div class="d-flex fw-nowrap">
<div class="flex--item wmn0 fl1 lh-lg">
<div class="flex--item fl1 lh-lg">
<div>
<b>This question already has an answer here</b>:
</div>
</div>
</div>
</div>
<div class="flex--item mb0 mt4">
<a href="/questions/5771/is-it-possible-to-restrict-gnu-gplv3-to-non-commercial-use-only" dir="ltr">Is it possible to restrict GNU GPLv3 to non-commercial use only?</a>
<span class="question-originals-answer-count">
(1 answer)
</span>
</div>
<div class="flex--item mb0 mt8">Closed <span title="2018-09-30 22:47:30Z" class="relativetime">5 years ago</span>.</div>
</div>
</aside>
<div class="answer-author-parent">
<div class="answer-author">
Answered Sep 27, 2018 at 21:21 by
<a href="https://notopensource.stackexchange.com/users/1212/amon" target="_blank" rel="noopener noreferrer">amon</a>
</div>
</div>
<a href="/exchange/opensource/9999/1111">This shouldn't be re-prefixed</a>
`
func TestConvertRelativeAnchorURLsToAbsolute(t *testing.T) {
prefix := "/exchange/opensource"
fixedHTML := ConvertRelativeAnchorURLsToAbsolute(sampleRelativeAnchorURLsInput, prefix)
log.Println(fixedHTML)
assert.True(t, strings.Contains(fixedHTML, prefix))
assert.True(t, strings.Contains(fixedHTML, "https://notopensource.stackexchange.com"))
}
+2 -3
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html data-theme="{{ .theme }}">
<html>
<head>
<title>AnonymousOverflow - Private frontend for StackOverflow</title>
@@ -43,10 +43,9 @@
<div class="options">
<div class="icon">
<a href="/options/images">
<img src="/static/icons/image.svg" alt="Toggle theme" />
<img src="/static/icons/image.svg" alt="Toggle images" />
</a>
</div>
{{ template "themeSwitcher.html" .}}
</div>
<p class="footer">
Brought to you by
+1 -2
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html data-theme="{{ .theme }}">
<html>
<head>
<title>{{ .question.Title }} | AnonymousOverflow</title>
@@ -25,7 +25,6 @@
<a href="/" class="logo-link">
<img class="logo" src="/static/codecircles.webp" alt="AnonymousOverflow home" />
</a>
{{ template "themeSwitcher.html" . }}
</div>
<div class="card">
<div class="card-header">
-6
View File
@@ -1,6 +0,0 @@
<div class="icon">
<a href="/options/theme?redirect_url={{ .currentUrl }}">
<img src="/static/icons/{{ if eq .theme "dark" }}sun{{
else }}moon{{ end }}.svg" alt="Toggle theme" />
</a>
</div>