reply fixes #25, endless scrolling settings fixes #18, settings popup.

This commit is contained in:
Ryan Stafford 2023-07-08 16:51:42 -04:00
parent 08a7ec66d4
commit 34f349313f
8 changed files with 201 additions and 60 deletions

View file

@ -253,9 +253,12 @@ summary {
border-top: 1px dotted gray; border-top: 1px dotted gray;
font-size: 12px; font-size: 12px;
} }
.comment form.savecomment { form.savecomment {
margin: 0px 0px 10px 10px; margin: 0px 0px 10px 10px;
} }
.comment > .children > form.savecomment {
margin: 0px 0px 10px 20px;
}
.savecomment textarea { .savecomment textarea {
width: 500px; width: 500px;
height: 100px; height: 100px;
@ -370,7 +373,6 @@ form.nsfw div {
} }
.pager { .pager {
margin: 10px; margin: 10px;
display: none;
} }
.pager a { .pager a {
padding: 1px 4px; padding: 1px 4px;
@ -381,10 +383,18 @@ form.nsfw div {
text-decoration: none; text-decoration: none;
color: #369; color: #369;
} }
.pager.hidden {
display: none;
}
#loadmore {
display: none;
}
#loadmore, #end { #loadmore, #end {
visibility: hidden;
margin: 10px 0px; margin: 10px 0px;
} }
#loadmore.show {
display: block;
}
#loadmore[disabled] { #loadmore[disabled] {
visibility: visible; visibility: visible;
} }
@ -475,6 +485,23 @@ form.nsfw div {
position: relative; position: relative;
color: #000; color: #000;
} }
#settings {
background-color: white;
border: 1px solid #888;
display: none;
position: absolute;
right: 10px;
top: 45px;
}
#settings form {
margin: 0px;
}
.dark #settings {
background-color: #262626;
}
#settings.open {
display: inline-block;
}
.expando.open{ .expando.open{
display: block; display: block;
} }
@ -980,7 +1007,7 @@ form.create input[type=file], form.create select {
} }
.preferences label{ .preferences label{
display: inline-block; display: inline-block;
width: 120px; width: 100px;
text-align: right; text-align: right;
} }

View file

@ -1,8 +1,10 @@
function request(url, params, callback, errorcallback) { function request(url, params, callback, errorcallback = function(){}) {
var xmlHttp = new XMLHttpRequest(); var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() { xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) if (xmlHttp.readyState != 4 ) { return }
if (xmlHttp.status == 200) {
return callback(xmlHttp.responseText); return callback(xmlHttp.responseText);
}
errorcallback(xmlHttp.responseText); errorcallback(xmlHttp.responseText);
} }
var method = "GET" var method = "GET"
@ -41,7 +43,11 @@ function commentClick(e) {
var form = e.target.parentNode var form = e.target.parentNode
if (form) { if (form) {
data = new FormData(form) data = new FormData(form)
if (("c"+data.get("commentid")) != targ.id) { return } if (("c"+data.get("commentid")) == targ.id) {
} else if (("c"+data.get("parentid")) == targ.id) {
targ = form
} else { return }
params = new URLSearchParams(data).toString() params = new URLSearchParams(data).toString()
params += "&" + e.target.name + "=" + e.target.value params += "&" + e.target.name + "=" + e.target.value
params += "&xhr=1" params += "&xhr=1"
@ -114,6 +120,8 @@ function loadMore(e) {
toggle_images(true) toggle_images(true)
} }
} }
var loadmore = document.getElementById("loadmore")
if (loadmore) loadmore.className = "show"
} }
else { else {
e.target.outerHTML = '<input id="end" type="submit" value="" disabled>' e.target.outerHTML = '<input id="end" type="submit" value="" disabled>'
@ -167,6 +175,47 @@ function formSubmit(e) {
return false return false
} }
function open_settings(e) {
e.preventDefault()
var settings = document.getElementById("settings")
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"
}
}
})
return false
}
function close_settings(e) {
e.preventDefault()
var settings = document.getElementById("settings")
settings.className = ""
return false
}
function save_settings(e) {
e = e || window.event;
var targ = e.currentTarget || e.srcElement || e;
var data = new FormData(targ)
console.log(data)
e.preventDefault()
var params = new URLSearchParams(data).toString()
request(targ.target, params, function(res) {
["endlessScrolling", "autoLoad"].map(function(x) {
localStorage.setItem(x, data.get(x)=="on")
})
window.location.reload()
})
return false;
}
function parse_youtube(url){ function parse_youtube(url){
if (url.indexOf("youtu") == -1) return false if (url.indexOf("youtu") == -1) return false
var regExp = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/|shorts\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/; var regExp = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/|shorts\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/;
@ -227,12 +276,22 @@ for (var i = 0; i < posts.length; i++) {
} }
} }
window.onscroll = function(ev) { if (localStorage.getItem("endlessScrolling") == "true") {
if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) { var pager = document.getElementsByClassName("pager")
var loadmore = document.getElementById("loadmore") if (pager.length) pager[0].className = "pager hidden"
if (loadmore) { var loadmore = document.getElementById("loadmore")
loadmore.click() if (loadmore) loadmore.className = "show"
}
} if (localStorage.getItem("autoLoad") == "true") {
}; window.onscroll = function(e) {
if ((window.innerHeight + Math.round(window.scrollY)) >= document.body.offsetHeight) {
var loadmore = document.getElementById("loadmore")
if (loadmore) {
loadmore.click()
}
}
};
}
}

View file

@ -147,7 +147,7 @@ var funcMap = template.FuncMap{
converted = re.ReplaceAllString(converted, `href="/`+host+`/$1`) converted = re.ReplaceAllString(converted, `href="/`+host+`/$1`)
re = regexp.MustCompile(` !([a-zA-Z0-9]+)@([a-zA-Z0-9\.\-]+) `) re = regexp.MustCompile(` !([a-zA-Z0-9]+)@([a-zA-Z0-9\.\-]+) `)
converted = re.ReplaceAllString(converted, ` <a href="/$2/c/$1">!$1@$2</a> `) converted = re.ReplaceAllString(converted, ` <a href="/$2/c/$1">!$1@$2</a> `)
re = regexp.MustCompile(`::: spoiler (.*?)\n(.*?)\s:::`) re = regexp.MustCompile(`::: spoiler (.*?)\n([\S\s]*?):::`)
converted = re.ReplaceAllString(converted, "<details><summary>$1</summary>$2</details>") converted = re.ReplaceAllString(converted, "<details><summary>$1</summary>$2</details>")
return template.HTML(converted) return template.HTML(converted)
}, },
@ -999,33 +999,59 @@ func UserOp(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
parentid, _ := strconv.Atoi(r.FormValue("parentid")) parentid, _ := strconv.Atoi(r.FormValue("parentid"))
state.GetComment(parentid) state.GetComment(parentid)
} }
createComment := types.CreateComment{ if r.FormValue("submit") == "save" {
Content: r.FormValue("content"), createComment := types.CreateComment{
PostID: state.PostID, Content: r.FormValue("content"),
PostID: state.PostID,
}
if state.CommentID > 0 {
createComment.ParentID = types.NewOptional(state.CommentID)
}
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
r.URL.Fragment = "c" + commentid
} else {
fmt.Println(err)
}
} else if r.FormValue("xhr") != "" {
w.Write([]byte{})
return
} }
if state.CommentID > 0 { if r.FormValue("submit") == "cancel" {
createComment.ParentID = types.NewOptional(state.CommentID) r.URL.RawQuery = ""
}
resp, err := state.Client.CreateComment(context.Background(), createComment)
if err == nil {
postid := strconv.Itoa(state.PostID)
commentid := strconv.Itoa(resp.CommentView.Comment.ID)
r.URL.Path = "/" + state.Host + "/post/" + postid
r.URL.Fragment = "c" + commentid
} else {
fmt.Println(err)
} }
case "edit_comment": case "edit_comment":
commentid, _ := strconv.Atoi(r.FormValue("commentid")) commentid, _ := strconv.Atoi(r.FormValue("commentid"))
resp, err := state.Client.EditComment(context.Background(), types.EditComment{ if r.FormValue("submit") == "save" {
CommentID: commentid, resp, err := state.Client.EditComment(context.Background(), types.EditComment{
Content: types.NewOptional(r.FormValue("content")), CommentID: commentid,
}) Content: types.NewOptional(r.FormValue("content")),
if err != nil { })
fmt.Println(err) if err != nil {
} else { fmt.Println(err)
commentid := strconv.Itoa(resp.CommentView.Comment.ID) } else {
r.URL.Fragment = "c" + commentid commentid := strconv.Itoa(resp.CommentView.Comment.ID)
r.URL.Fragment = "c" + commentid
r.URL.RawQuery = ""
}
}
if r.FormValue("xhr") != "" {
state.XHR = true
state.GetComment(commentid)
Render(w, "index.html", state)
return
}
if r.FormValue("submit") == "cancel" {
r.URL.RawQuery = "" r.URL.RawQuery = ""
} }
case "delete_comment": case "delete_comment":

View file

@ -28,17 +28,25 @@
{{ end }} {{ end }}
</div> </div>
{{ if eq .Op "edit" }} {{ if eq .Op "edit" }}
<div class="content">
<form class="savecomment" method="POST"> <form class="savecomment" method="POST">
<div> <div>
<textarea required name="content">{{ .P.Comment.Content }}</textarea> <textarea required name="content">{{ .P.Comment.Content }}</textarea>
</div> </div>
<input type="hidden" name="commentid" value="{{.P.Comment.ID}}"> <input type="hidden" name="commentid" value="{{.P.Comment.ID}}">
<input type="hidden" name="op" value="edit_comment"> <input type="hidden" name="op" value="edit_comment">
<input type="submit" value="save"> <input name="submit" type="submit" value="save">
<input name="submit" type="submit" value="cancel">
</form> </form>
{{ else }} {{ else }}
<div class="content{{ if and .Selected}} highlight{{end}}"> <div class="content">
{{if .P.Comment.Deleted}}[removed]{{else}}{{ markdown .State.Host .P.Comment.Content }}{{end}} {{if .P.Comment.Deleted}}
[removed]
{{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" }} {{ if eq .Op "source" }}
<div><textarea>{{.P.Comment.Content}}</textarea></div> <div><textarea>{{.P.Comment.Content}}</textarea></div>
{{end}} {{end}}
@ -73,13 +81,11 @@
{{ end }} {{ end }}
</form> </form>
</li> </li>
{{ if ne .Op "reply" }}
<li> <li>
<a class="reply" for="c{{.P.Comment.ID}}" href="/{{.State.Host}}/comment/{{.P.Comment.ID}}?reply"> <a class="reply" for="c{{.P.Comment.ID}}" href="/{{.State.Host}}/comment/{{.P.Comment.ID}}?reply">
reply reply
</a> </a>
</li> </li>
{{ end }}
{{ end }} {{ end }}
{{ if gt .ChildCount 0 }} {{ if gt .ChildCount 0 }}
<li><a class="hidechildren" for="c{{.P.Comment.ID}}" href=""><span class="hide">hide</span><span class="show">show {{ .ChildCount }}</span> child comments</a></li> <li><a class="hidechildren" for="c{{.P.Comment.ID}}" href=""><span class="hide">hide</span><span class="show">show {{ .ChildCount }}</span> child comments</a></li>
@ -90,11 +96,12 @@
{{ if and (eq .State.Op "reply") (eq .State.CommentID .P.Comment.ID)}} {{ if and (eq .State.Op "reply") (eq .State.CommentID .P.Comment.ID)}}
<form class="savecomment" method="POST"> <form class="savecomment" method="POST">
<div> <div>
<textarea required name="content"></textarea> <textarea name="content"></textarea>
</div> </div>
<input type="hidden" name="parentid" value="{{.P.Comment.ID}}"> <input type="hidden" name="parentid" value="{{.P.Comment.ID}}">
<input type="hidden" name="op" value="create_comment"> <input type="hidden" name="op" value="create_comment">
<input type="submit" value="save"> <input type="submit" name="submit" value="save">
<input type="submit" name="submit" value="cancel">
</form> </form>
{{ end}} {{ end}}
{{ range $ci, $child := .C }}{{ template "comment.html" $child }}{{end}} {{ range $ci, $child := .C }}{{ template "comment.html" $child }}{{end}}

View file

@ -2,7 +2,7 @@
<head> <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> <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="shortcut icon" href="/{{.Host}}/icon.jpg">
<link rel="stylesheet" href="/_/static/style.css?v=14"> <link rel="stylesheet" href="/_/static/style.css?v=15">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body {{ if .Dark }}class="dark"{{end}}> <body {{ if .Dark }}class="dark"{{end}}>
@ -56,6 +56,6 @@
{{ template "sidebar.html" . }} {{ template "sidebar.html" . }}
</main> </main>
{{ end }} {{ end }}
<script src="/_/static/utils.js?v=7"></script> <script src="/_/static/utils.js?v=9"></script>
</body> </body>
</html> </html>

View file

@ -3,21 +3,18 @@
<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> <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" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="shortcut icon" href="/{{.Host}}/icon.jpg"> <link rel="shortcut icon" href="/{{.Host}}/icon.jpg">
<link rel="stylesheet" href="/_/static/style.css?v=14"> <link rel="stylesheet" href="/_/static/style.css?v=15">
</head> </head>
<body{{ if .Dark }} class="dark"{{end}}> <body{{ if .Dark }} class="dark"{{end}}>
<noscript> <noscript>
<style> <style>
.scripting,
.expando-button, .expando-button,
.minimize, .minimize,
#loadmore,
#showimages, #showimages,
.hidechildren { .hidechildren {
display: none !important; display: none !important;
} }
div.pager {
display: block;
}
.post .expando .image img { .post .expando .image img {
visibility: visible; visibility: visible;
} }
@ -99,7 +96,7 @@
<textarea required name="content" {{ if (index .Posts 0).Post.Deleted }} disabled {{end}}></textarea> <textarea required name="content" {{ if (index .Posts 0).Post.Deleted }} disabled {{end}}></textarea>
</div> </div>
<input type="hidden" name="op" value="create_comment"> <input type="hidden" name="op" value="create_comment">
<input type="submit" value="save"{{ if (index .Posts 0).Post.Deleted }} disabled {{end}}> <input type="submit" name="submit" value="save"{{ if (index .Posts 0).Post.Deleted }} disabled {{end}}>
</form> </form>
{{ end }} {{ end }}
{{ end }} {{ end }}
@ -128,7 +125,7 @@
<input id="loadmore" type="submit" value="load more" onclick="loadMore(event)" data-page="2"> <input id="loadmore" type="submit" value="load more" onclick="loadMore(event)" data-page="2">
{{ end }} {{ end }}
<script src="/_/static/utils.js?v=7"></script> <script src="/_/static/utils.js?v=9"></script>
{{ template "sidebar.html" . }} {{ template "sidebar.html" . }}
</main> </main>
{{ end }} {{ end }}

View file

@ -17,15 +17,16 @@
| |
<a href="/{{.Host}}/inbox" class="mailbox{{ if .UnreadCount }} orangered{{end}}"></a> <a href="/{{.Host}}/inbox" class="mailbox{{ if .UnreadCount }} orangered{{end}}"></a>
| |
<a href="/{{.Host}}/settings">settings</a> <a href="/{{.Host}}/settings" onclick="open_settings(event)">settings</a>
| |
<form method="POST"><input type="submit" name="op" value="logout"></form> <form method="POST"><input type="submit" name="op" value="logout"></form>
{{else}} {{else}}
<a href="/{{.Host}}/login">log in</a> or <a href="/{{.Host}}/login">sign up</a> <a href="/{{.Host}}/login">log in</a> or <a href="/{{.Host}}/login">sign up</a>
| |
<a href="/{{.Host}}/settings">settings</a> <a href="/{{.Host}}/settings" onclick="open_settings(event)">settings</a>
{{end}} {{end}}
</div> </div>
<div id="settings"></div>
<div class="spacer"> <div class="spacer">
<a href="/{{ .Host}}/"> <a href="/{{ .Host}}/">
<img class="icon" src="{{ if .Site }}{{ .Site.SiteView.Site.Icon.String }}{{else}}/{{ .Host}}/icon.jpg{{end}}"> <img class="icon" src="{{ if .Site }}{{ .Site.SiteView.Site.Icon.String }}{{else}}/{{ .Host}}/icon.jpg{{end}}">

View file

@ -1,11 +1,19 @@
{{ if not .XHR }}
<!DOCTYPE html> <!DOCTYPE html>
<head> <head>
<title>{{ host .Host }}: preferences</title> <title>{{ host .Host }}: preferences</title>
<link rel="shortcut icon" href="/{{.Host}}/icon.jpg"> <link rel="shortcut icon" href="/{{.Host}}/icon.jpg">
<link rel="stylesheet" href="/_/static/style.css?v=7"> <link rel="stylesheet" href="/_/static/style.css?v=15">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body {{ if .Dark}}class="dark"{{end}}> <body {{ if .Dark}}class="dark"{{end}}>
<noscript>
<style>
.scripting {
display: none;
}
</style>
</noscript>
<nav> <nav>
<div class="communities"> <div class="communities">
<a href="/{{.Host}}">home</a> <a href="/{{.Host}}">home</a>
@ -52,7 +60,8 @@
{{ if .Error }} {{ if .Error }}
<div class="error">{{.Error}}</div> <div class="error">{{.Error}}</div>
{{ end }} {{ end }}
<form class="preferences" method="POST"> {{ end }}
<form class="preferences" method="POST" target="/{{.Host}}/settings" onsubmit="save_settings(event)" >
<div> <div>
<label> <label>
default listing default listing
@ -90,11 +99,26 @@
</label> </label>
<input type="checkbox" name="darkmode" {{ if .Dark }}checked{{end}}> <input type="checkbox" name="darkmode" {{ if .Dark }}checked{{end}}>
</div> </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> <div>
<label></label> <label></label>
<input type="submit" value="save"> <input type="submit" value="save">
{{ if .XHR }}<input type="submit" value="close" onclick="close_settings(event)">{{ end }}
</div> </div>
</form> </form>
{{ if not .XHR}}
<script src="/_/static/utils.js?v=8"></script>
</body> </body>
</html> </html>
{{ end }}