Improved the poll UIs slightly.

Polls without any votes should look a little nicer now.
.hide_on_edit and other similar classes should now override other classes on Cosora.
Added <red> to avoid having to use inline styles for parser errors.
Reorganised a few elements in topic.html and topic_alt.html
Split topic_poll.html out of topic.html

Fixed a bug where attachments were getting assigned to the wrong reply / topic.

Added the router_after_filters hook.
Added the topic.poll_no_results phrase.

Added two parser tests.
This commit is contained in:
Azareal 2019-04-10 17:40:47 +10:00
parent d5acb92aef
commit 5808f1d0ba
20 changed files with 160 additions and 104 deletions

View File

@ -277,6 +277,7 @@ func createTables(adapter qgen.Adapter) error {
tblColumn{"originTable", "varchar", 200, false, false, "replies"}, tblColumn{"originTable", "varchar", 200, false, false, "replies"},
tblColumn{"uploadedBy", "int", 0, false, false, ""}, // TODO; Make this a foreign key tblColumn{"uploadedBy", "int", 0, false, false, ""}, // TODO; Make this a foreign key
tblColumn{"path", "varchar", 200, false, false, ""}, tblColumn{"path", "varchar", 200, false, false, ""},
//tblColumn{"extra", "varchar", 200, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"attachID", "primary"}, tblKey{"attachID", "primary"},

View File

@ -81,7 +81,7 @@ func (store *DefaultAttachmentStore) BulkMiniGetList(originTable string, ids []i
} }
if len(ids) == 1 { if len(ids) == 1 {
res, err := store.MiniGetList(originTable, ids[0]) res, err := store.MiniGetList(originTable, ids[0])
return map[int][]*MiniAttachment{0: res}, err return map[int][]*MiniAttachment{ids[0]: res}, err
} }
amap = make(map[int][]*MiniAttachment) amap = make(map[int][]*MiniAttachment)

View File

@ -88,6 +88,7 @@ var hookTable = &HookTable{
"action_end_edit_reply": nil, "action_end_edit_reply": nil,
"action_end_delete_reply": nil, "action_end_delete_reply": nil,
"router_after_filters": nil,
"router_pre_route": nil, "router_pre_route": nil,
}, },
map[string][]func(string) string{ map[string][]func(string) string{

View File

@ -9,13 +9,14 @@ import (
"unicode/utf8" "unicode/utf8"
) )
// TODO: Somehow localise these?
var SpaceGap = []byte(" ") var SpaceGap = []byte(" ")
var httpProtBytes = []byte("http://") var httpProtBytes = []byte("http://")
var InvalidURL = []byte("<span style='color: red;'>[Invalid URL]</span>") var InvalidURL = []byte("<red>[Invalid URL]</red>")
var InvalidTopic = []byte("<span style='color: red;'>[Invalid Topic]</span>") var InvalidTopic = []byte("<red>[Invalid Topic]</red>")
var InvalidProfile = []byte("<span style='color: red;'>[Invalid Profile]</span>") var InvalidProfile = []byte("<red>[Invalid Profile]</red>")
var InvalidForum = []byte("<span style='color: red;'>[Invalid Forum]</span>") var InvalidForum = []byte("<red>[Invalid Forum]</red>")
var unknownMedia = []byte("<span style='color: red;'>[Unknown Media]</span>") var unknownMedia = []byte("<red>[Unknown Media]</red>")
var URLOpen = []byte("<a href='") var URLOpen = []byte("<a href='")
var URLOpen2 = []byte("'>") var URLOpen2 = []byte("'>")
var bytesSinglequote = []byte("'") var bytesSinglequote = []byte("'")

View File

@ -792,11 +792,17 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1] req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
} }
// TODO: Use the same hook table as downstream
hTbl := common.GetHookTable()
skip, ferr := hTbl.VhookSkippable("router_after_filters", w, req, prefix, extraData)
if skip || ferr != nil {
return
}
if prefix != "/ws" { if prefix != "/ws" {
h := w.Header() h := w.Header()
h.Set("X-Frame-Options", "deny") h.Set("X-Frame-Options", "deny")
h.Set("X-XSS-Protection", "1; mode=block") // TODO: Remove when we add a CSP? CSP's are horrendously glitchy things, tread with caution before removing h.Set("X-XSS-Protection", "1; mode=block") // TODO: Remove when we add a CSP? CSP's are horrendously glitchy things, tread with caution before removing
// TODO: Set the content policy header
h.Set("X-Content-Type-Options", "nosniff") h.Set("X-Content-Type-Options", "nosniff")
} }
@ -994,9 +1000,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w} w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w}
} }
// TODO: Use the same hook table as downstream skip, ferr = hTbl.VhookSkippable("router_pre_route", w, req, user, prefix, extraData)
hTbl := common.GetHookTable()
skip, ferr := hTbl.VhookSkippable("router_pre_route", w, req, user, prefix, extraData)
if skip || ferr != nil { if skip || ferr != nil {
r.handleError(ferr,w,req,user) r.handleError(ferr,w,req,user)
} }

View File

@ -597,6 +597,7 @@
"topic.poll_vote":"Vote", "topic.poll_vote":"Vote",
"topic.poll_results":"Results", "topic.poll_results":"Results",
"topic.poll_cancel":"Cancel", "topic.poll_cancel":"Cancel",
"topic.poll_no_results":"No one has voted yet.",
"topic.post_controls_aria":"Controls and Author Information", "topic.post_controls_aria":"Controls and Author Information",
"topic.unlike_tooltip":"Unlike", "topic.unlike_tooltip":"Unlike",
"topic.unlike_aria":"Unlike this topic", "topic.unlike_aria":"Unlike this topic",

View File

@ -143,11 +143,13 @@ func TestParser(t *testing.T) {
msgList.Add("//"+common.Site.URL+"\n//"+common.Site.URL, "<a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a><br><a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a>") msgList.Add("//"+common.Site.URL+"\n//"+common.Site.URL, "<a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a><br><a href='//"+common.Site.URL+"'>//"+common.Site.URL+"</a>")
msgList.Add("#tid-1", "<a href='/topic/1'>#tid-1</a>") msgList.Add("#tid-1", "<a href='/topic/1'>#tid-1</a>")
msgList.Add("#tid-0", "<red>[Invalid Topic]</red>")
msgList.Add("https://"+url+"/#tid-1", "<a href='https://"+url+"/#tid-1'>https://"+url+"/#tid-1</a>") msgList.Add("https://"+url+"/#tid-1", "<a href='https://"+url+"/#tid-1'>https://"+url+"/#tid-1</a>")
msgList.Add("#fid-1", "<a href='/forum/1'>#fid-1</a>") msgList.Add("#fid-1", "<a href='/forum/1'>#fid-1</a>")
msgList.Add("#fid-0", "<red>[Invalid Forum]</red>")
msgList.Add("@1", "<a href='/user/admin.1' class='mention'>@Admin</a>") msgList.Add("@1", "<a href='/user/admin.1' class='mention'>@Admin</a>")
msgList.Add("@0", "<span style='color: red;'>[Invalid Profile]</span>") msgList.Add("@0", "<red>[Invalid Profile]</red>")
msgList.Add("@-1", "<span style='color: red;'>[Invalid Profile]</span>1") msgList.Add("@-1", "<red>[Invalid Profile]</red>1")
for _, item := range msgList.Items { for _, item := range msgList.Items {
res := common.ParseMessage(item.Msg, 1, "forums") res := common.ParseMessage(item.Msg, 1, "forums")

View File

@ -32,9 +32,9 @@ func init() {
func initBbcode(plugin *common.Plugin) error { func initBbcode(plugin *common.Plugin) error {
plugin.AddHook("parse_assign", bbcodeFullParse) plugin.AddHook("parse_assign", bbcodeFullParse)
bbcodeInvalidNumber = []byte("<span style='color: red;'>[Invalid Number]</span>") bbcodeInvalidNumber = []byte("<red>[Invalid Number]</red>")
bbcodeNoNegative = []byte("<span style='color: red;'>[No Negative Numbers]</span>") bbcodeNoNegative = []byte("<red>[No Negative Numbers]</red>")
bbcodeMissingTag = []byte("<span style='color: red;'>[Missing Tag]</span>") bbcodeMissingTag = []byte("<red>[Missing Tag]</red>")
bbcodeBold = regexp.MustCompile(`(?s)\[b\](.*)\[/b\]`) bbcodeBold = regexp.MustCompile(`(?s)\[b\](.*)\[/b\]`)
bbcodeItalic = regexp.MustCompile(`(?s)\[i\](.*)\[/i\]`) bbcodeItalic = regexp.MustCompile(`(?s)\[i\](.*)\[/i\]`)

View File

@ -29,7 +29,7 @@ func init() {
func initMarkdown(plugin *common.Plugin) error { func initMarkdown(plugin *common.Plugin) error {
plugin.AddHook("parse_assign", markdownParse) plugin.AddHook("parse_assign", markdownParse)
markdownUnclosedElement = []byte("<span style='color: red;'>[Unclosed Element]</span>") markdownUnclosedElement = []byte("<red>[Unclosed Element]</red>")
markdownBoldTagOpen = []byte("<b>") markdownBoldTagOpen = []byte("<b>")
markdownBoldTagClose = []byte("</b>") markdownBoldTagClose = []byte("</b>")
@ -63,7 +63,7 @@ func markdownParse(msg string) string {
// Under Construction! // Under Construction!
func _markdownParse(msg string, n int) string { func _markdownParse(msg string, n int) string {
if n > markdownMaxDepth { if n > markdownMaxDepth {
return "<span style='color: red;'>[Markdown Error: Overflowed the max depth of 20]</span>" return "<red>[Markdown Error: Overflowed the max depth of 20]</red>"
} }
var outbytes []byte var outbytes []byte

View File

@ -87,7 +87,7 @@ func TestBBCodeRender(t *testing.T) {
var expects string var expects string
msg = "[rand][/rand]" msg = "[rand][/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand][/rand]" expects = "<red>[Invalid Number]</red>[rand][/rand]"
t.Log("Testing string '" + msg + "'") t.Log("Testing string '" + msg + "'")
res = bbcodeFullParse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
@ -96,7 +96,7 @@ func TestBBCodeRender(t *testing.T) {
} }
msg = "[rand]-1[/rand]" msg = "[rand]-1[/rand]"
expects = "<span style='color: red;'>[No Negative Numbers]</span>[rand]-1[/rand]" expects = "<red>[No Negative Numbers]</red>[rand]-1[/rand]"
t.Log("Testing string '" + msg + "'") t.Log("Testing string '" + msg + "'")
res = bbcodeFullParse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
@ -105,7 +105,7 @@ func TestBBCodeRender(t *testing.T) {
} }
msg = "[rand]-01[/rand]" msg = "[rand]-01[/rand]"
expects = "<span style='color: red;'>[No Negative Numbers]</span>[rand]-01[/rand]" expects = "<red>[No Negative Numbers]</red>[rand]-01[/rand]"
t.Log("Testing string '" + msg + "'") t.Log("Testing string '" + msg + "'")
res = bbcodeFullParse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
@ -114,7 +114,7 @@ func TestBBCodeRender(t *testing.T) {
} }
msg = "[rand]NaN[/rand]" msg = "[rand]NaN[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]NaN[/rand]" expects = "<red>[Invalid Number]</red>[rand]NaN[/rand]"
t.Log("Testing string '" + msg + "'") t.Log("Testing string '" + msg + "'")
res = bbcodeFullParse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
@ -123,7 +123,7 @@ func TestBBCodeRender(t *testing.T) {
} }
msg = "[rand]Inf[/rand]" msg = "[rand]Inf[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]Inf[/rand]" expects = "<red>[Invalid Number]</red>[rand]Inf[/rand]"
t.Log("Testing string '" + msg + "'") t.Log("Testing string '" + msg + "'")
res = bbcodeFullParse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
@ -132,7 +132,7 @@ func TestBBCodeRender(t *testing.T) {
} }
msg = "[rand]+[/rand]" msg = "[rand]+[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]+[/rand]" expects = "<red>[Invalid Number]</red>[rand]+[/rand]"
t.Log("Testing string '" + msg + "'") t.Log("Testing string '" + msg + "'")
res = bbcodeFullParse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
@ -141,7 +141,7 @@ func TestBBCodeRender(t *testing.T) {
} }
msg = "[rand]1+1[/rand]" msg = "[rand]1+1[/rand]"
expects = "<span style='color: red;'>[Invalid Number]</span>[rand]1+1[/rand]" expects = "<red>[Invalid Number]</red>[rand]1+1[/rand]"
t.Log("Testing string '" + msg + "'") t.Log("Testing string '" + msg + "'")
res = bbcodeFullParse(msg) res = bbcodeFullParse(msg)
if res != expects { if res != expects {
@ -191,7 +191,7 @@ func TestBBCodeRender(t *testing.T) {
t.Log("Testing string '" + msg + "'") t.Log("Testing string '" + msg + "'")
res = bbcodeFullParse(msg) res = bbcodeFullParse(msg)
_, err = strconv.Atoi(res) _, err = strconv.Atoi(res)
if err != nil && res != "<span style='color: red;'>[Invalid Number]</span>[rand]18446744073709551615[/rand]" { if err != nil && res != "<red>[Invalid Number]</red>[rand]18446744073709551615[/rand]" {
t.Error("Bad output:", "'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected a number between 0 and 18446744073709551615") t.Error("Expected a number between 0 and 18446744073709551615")
} }
@ -199,7 +199,7 @@ func TestBBCodeRender(t *testing.T) {
t.Log("Testing string '" + msg + "'") t.Log("Testing string '" + msg + "'")
res = bbcodeFullParse(msg) res = bbcodeFullParse(msg)
_, err = strconv.Atoi(res) _, err = strconv.Atoi(res)
if err != nil && res != "<span style='color: red;'>[Invalid Number]</span>[rand]170141183460469231731687303715884105727[/rand]" { if err != nil && res != "<red>[Invalid Number]</red>[rand]170141183460469231731687303715884105727[/rand]" {
t.Error("Bad output:", "'"+res+"'") t.Error("Bad output:", "'"+res+"'")
t.Error("Expected a number between 0 and 170141183460469231731687303715884105727") t.Error("Expected a number between 0 and 170141183460469231731687303715884105727")
} }

View File

@ -1170,13 +1170,24 @@ function mainInit(){
//id="poll_results_{{.Poll.ID}}" class="poll_results auto_hide" //id="poll_results_{{.Poll.ID}}" class="poll_results auto_hide"
$(".poll_results_button").click(function(){ $(".poll_results_button").click(function(){
let pollID = $(this).attr("data-poll-id"); let pollID = $(this).attr("data-poll-id");
$("#poll_results_" + pollID + " .user_content").html("<div id='poll_results_chart_"+pollID+"'></div>");
$("#poll_results_" + pollID).removeClass("auto_hide"); $("#poll_results_" + pollID).removeClass("auto_hide");
fetch("/poll/results/" + pollID, { fetch("/poll/results/" + pollID, {
credentials: 'same-origin' credentials: 'same-origin'
}).then((response) => response.text()).catch((error) => console.error("Error:",error)).then((rawData) => { }).then((response) => response.text()).catch((error) => console.error("Error:",error)).then((rawData) => {
// TODO: Make sure the received data is actually a list of integers // TODO: Make sure the received data is actually a list of integers
let data = JSON.parse(rawData); let data = JSON.parse(rawData);
let allZero = true;
for(let i = 0; i < data.length; i++) {
if(data[i] != "0") allZero = false;
}
if(allZero) {
$("#poll_results_" + pollID + " .poll_no_results").removeClass("auto_hide");
console.log("all zero")
return;
}
$("#poll_results_" + pollID + " .user_content").html("<div id='poll_results_chart_"+pollID+"'></div>");
console.log("rawData: ", rawData); console.log("rawData: ", rawData);
console.log("series: ", data); console.log("series: ", data);
Chartist.Pie('#poll_results_chart_' + pollID, { Chartist.Pie('#poll_results_chart_' + pollID, {

View File

@ -571,11 +571,17 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1] req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
} }
// TODO: Use the same hook table as downstream
hTbl := common.GetHookTable()
skip, ferr := hTbl.VhookSkippable("router_after_filters", w, req, prefix, extraData)
if skip || ferr != nil {
return
}
if prefix != "/ws" { if prefix != "/ws" {
h := w.Header() h := w.Header()
h.Set("X-Frame-Options", "deny") h.Set("X-Frame-Options", "deny")
h.Set("X-XSS-Protection", "1; mode=block") // TODO: Remove when we add a CSP? CSP's are horrendously glitchy things, tread with caution before removing h.Set("X-XSS-Protection", "1; mode=block") // TODO: Remove when we add a CSP? CSP's are horrendously glitchy things, tread with caution before removing
// TODO: Set the content policy header
h.Set("X-Content-Type-Options", "nosniff") h.Set("X-Content-Type-Options", "nosniff")
} }
@ -773,9 +779,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w} w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w}
} }
// TODO: Use the same hook table as downstream skip, ferr = hTbl.VhookSkippable("router_pre_route", w, req, user, prefix, extraData)
hTbl := common.GetHookTable()
skip, ferr := hTbl.VhookSkippable("router_pre_route", w, req, user, prefix, extraData)
if skip || ferr != nil { if skip || ferr != nil {
r.handleError(ferr,w,req,user) r.handleError(ferr,w,req,user)
} }

View File

@ -1,6 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<form id="edit_topic_form" action='/topic/edit/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}' method="post"></form>
{{if gt .Page 1}}<link rel="prev" href="{{.Topic.Link}}?page={{subtract .Page 1}}" /> {{if gt .Page 1}}<link rel="prev" href="{{.Topic.Link}}?page={{subtract .Page 1}}" />
<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="{{lang "paginator.prev_page_aria"}}" rel="prev" href="{{.Topic.Link}}?page={{subtract .Page 1}}">{{lang "paginator.less_than"}}</a></div>{{end}} <div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="{{lang "paginator.prev_page_aria"}}" rel="prev" href="{{.Topic.Link}}?page={{subtract .Page 1}}">{{lang "paginator.less_than"}}</a></div>{{end}}
@ -9,9 +8,9 @@
<a class="next_link" aria-label="{{lang "paginator.next_page_aria"}}" rel="next" href="{{.Topic.Link}}?page={{add .Page 1}}">{{lang "paginator.greater_than"}}</a> <a class="next_link" aria-label="{{lang "paginator.next_page_aria"}}" rel="next" href="{{.Topic.Link}}?page={{add .Page 1}}">{{lang "paginator.greater_than"}}</a>
</div>{{end}} </div>{{end}}
<link rel="canonical" href="//{{.Site.URL}}{{.Topic.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}" /> <main id="topicPage">
<main> <link rel="canonical" href="//{{.Site.URL}}{{.Topic.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}" />
<div {{scope "topic_title_block"}} class="rowblock rowhead topic_block" aria-label="{{lang "topic.opening_post_aria"}}"> <div {{scope "topic_title_block"}} class="rowblock rowhead topic_block" aria-label="{{lang "topic.opening_post_aria"}}">
<div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.IsClosed}} topic_closed_head{{end}}"> <div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.IsClosed}} topic_closed_head{{end}}">
@ -20,6 +19,7 @@
{{/** TODO: Does this need to be guarded by a permission? It's only visible in edit mode anyway, which can't be triggered, if they don't have the permission **/}} {{/** TODO: Does this need to be guarded by a permission? It's only visible in edit mode anyway, which can't be triggered, if they don't have the permission **/}}
{{if not .Topic.IsClosed or .CurrentUser.Perms.CloseTopic}} {{if not .Topic.IsClosed or .CurrentUser.Perms.CloseTopic}}
{{if .CurrentUser.Perms.EditTopic}} {{if .CurrentUser.Perms.EditTopic}}
<form id="edit_topic_form" action='/topic/edit/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}' method="post"></form>
<input form='edit_topic_form' class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" aria-label="{{lang "topic.title_input_aria"}}" /> <input form='edit_topic_form' class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" aria-label="{{lang "topic.title_input_aria"}}" />
<button form='edit_topic_form' name="topic-button" class="formbutton show_on_edit submit_edit">{{lang "topic.update_button"}}</button> <button form='edit_topic_form' name="topic-button" class="formbutton show_on_edit submit_edit">{{lang "topic.update_button"}}</button>
{{end}} {{end}}
@ -27,29 +27,7 @@
</div> </div>
</div> </div>
{{if .Poll.ID}} {{if .Poll.ID}}
<article class="rowblock post_container poll" aria-level="{{lang "topic.poll_aria"}}"> {{template "topic_poll.html" . }}
<div class="rowitem passive editable_parent post_item poll_item {{.Topic.ClassName}}" style="background-image: url({{.Topic.Avatar}}), url(/static/{{.Header.Theme.Name}}/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;">
<div class="topic_content user_content" style="margin:0;padding:0;">
{{range .Poll.QuickOptions}}
<div class="poll_option">
<input form="poll_{{$.Poll.ID}}_form" id="poll_option_{{.ID}}" name="poll_option_input" type="checkbox" value="{{.ID}}" />
<label class="poll_option_label" for="poll_option_{{.ID}}">
<div class="sel"></div>
</label>
<span id="poll_option_text_{{.ID}}" class="poll_option_text">{{.Value}}</span>
</div>
{{end}}
<div class="poll_buttons">
<button form="poll_{{.Poll.ID}}_form" class="poll_vote_button">{{lang "topic.poll_vote"}}</button>
<button class="poll_results_button" data-poll-id="{{.Poll.ID}}">{{lang "topic.poll_results"}}</button>
<a href="#"><button class="poll_cancel_button">{{lang "topic.poll_cancel"}}</button></a>
</div>
</div>
<div id="poll_results_{{.Poll.ID}}" class="poll_results auto_hide">
<div class="topic_content user_content"></div>
</div>
</div>
</article>
{{end}} {{end}}
<article {{scope "opening_post"}} itemscope itemtype="http://schema.org/CreativeWork" class="rowblock post_container top_post" aria-label="{{lang "topic.opening_post_aria"}}"> <article {{scope "opening_post"}} itemscope itemtype="http://schema.org/CreativeWork" class="rowblock post_container top_post" aria-label="{{lang "topic.opening_post_aria"}}">

View File

@ -6,12 +6,11 @@
{{if ne .LastPage .Page}}<link rel="prerender next" href="{{.Topic.Link}}?page={{add .Page 1}}" /> {{if ne .LastPage .Page}}<link rel="prerender next" href="{{.Topic.Link}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" aria-label="{{lang "paginator.next_page_aria"}}" rel="next" href="{{.Topic.Link}}?page={{add .Page 1}}">{{lang "paginator.greater_than"}}</a></div>{{end}} <div id="nextFloat" class="next_button"><a class="next_link" aria-label="{{lang "paginator.next_page_aria"}}" rel="next" href="{{.Topic.Link}}?page={{add .Page 1}}">{{lang "paginator.greater_than"}}</a></div>{{end}}
<main id="topicPage">
<link rel="canonical" href="//{{.Site.URL}}{{.Topic.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}" /> <link rel="canonical" href="//{{.Site.URL}}{{.Topic.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}" />
<main>
<div {{scope "topic_title_block"}} class="rowblock rowhead topic_block" aria-label="{{lang "topic.opening_post_aria"}}"> <div {{scope "topic_title_block"}} class="rowblock rowhead topic_block" aria-label="{{lang "topic.opening_post_aria"}}">
<form action='/topic/edit/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}' method="post">
<div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.IsClosed}} topic_closed_head{{end}}"> <div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.IsClosed}} topic_closed_head{{end}}">
<h1 class='topic_name hide_on_edit' title='{{.Topic.Title}}'>{{.Topic.Title}}</h1> <h1 class='topic_name hide_on_edit' title='{{.Topic.Title}}'>{{.Topic.Title}}</h1>
<span class="topic_name_forum_sep hide_on_edit"> - </span> <span class="topic_name_forum_sep hide_on_edit"> - </span>
@ -20,8 +19,9 @@
{{if .CurrentUser.Loggedin}} {{if .CurrentUser.Loggedin}}
{{if not .Topic.IsClosed or .CurrentUser.Perms.CloseTopic}} {{if not .Topic.IsClosed or .CurrentUser.Perms.CloseTopic}}
{{if .CurrentUser.Perms.EditTopic}} {{if .CurrentUser.Perms.EditTopic}}
<input class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" aria-label="{{lang "topic.title_input_aria"}}" /> <form id="edit_topic_form" action='/topic/edit/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}' method="post"></form>
<button name="topic-button" class="formbutton show_on_edit submit_edit">{{lang "topic.update_button"}}</button> <input form="edit_topic_form" class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" aria-label="{{lang "topic.title_input_aria"}}" />
<button form="edit_topic_form" name="topic-button" class="formbutton show_on_edit submit_edit">{{lang "topic.update_button"}}</button>
{{end}} {{end}}
{{end}} {{end}}
{{end}} {{end}}
@ -29,7 +29,6 @@
{{/** TODO: Inline this CSS **/}} {{/** TODO: Inline this CSS **/}}
{{if .Topic.IsClosed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='{{lang "status.closed_tooltip"}}' aria-label='{{lang "topic.status_closed_aria"}}' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}} {{if .Topic.IsClosed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='{{lang "status.closed_tooltip"}}' aria-label='{{lang "topic.status_closed_aria"}}' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}}
</div> </div>
</form>
</div> </div>
<div class="rowblock post_container"> <div class="rowblock post_container">
@ -56,7 +55,9 @@
</div> </div>
</div> </div>
<div id="poll_results_{{.Poll.ID}}" class="content_container poll_results auto_hide"> <div id="poll_results_{{.Poll.ID}}" class="content_container poll_results auto_hide">
<div class="topic_content user_content"></div> <div class="topic_content user_content">
<div class="auto_hide poll_no_results">{{lang "topic.poll_no_results"}}</div>
</div>
</div> </div>
</article> </article>
{{end}} {{end}}

View File

@ -21,7 +21,7 @@
{{end}} {{end}}
<div class="attach_item attach_item_buttons"> <div class="attach_item attach_item_buttons">
{{if $.CurrentUser.Perms.UploadFiles}} {{if $.CurrentUser.Perms.UploadFiles}}
<input name="upload_files" class="upload_files_post" id="upload_files_post_{{.ID}}" multiple type="file" style="display: none;" /> <input name="upload_files" class="upload_files_post auto_hide" id="upload_files_post_{{.ID}}" multiple type="file" />
<label for="upload_files_post_{{.ID}}" class="formbutton add_file_button">{{lang "topic.upload_button_text"}}</label>{{end}} <label for="upload_files_post_{{.ID}}" class="formbutton add_file_button">{{lang "topic.upload_button_text"}}</label>{{end}}
<button class="attach_item_delete">{{lang "topic.delete_button_text"}}</button> <button class="attach_item_delete">{{lang "topic.delete_button_text"}}</button>
</div> </div>

25
templates/topic_poll.html Normal file
View File

@ -0,0 +1,25 @@
<article class="rowblock post_container poll" aria-level="{{lang "topic.poll_aria"}}">
<div class="rowitem passive editable_parent post_item poll_item {{.Topic.ClassName}}" style="background-image: url({{.Topic.Avatar}}), url(/static/{{.Header.Theme.Name}}/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;">
<div class="topic_content user_content">
{{range .Poll.QuickOptions}}
<div class="poll_option">
<input form="poll_{{$.Poll.ID}}_form" id="poll_option_{{.ID}}" name="poll_option_input" type="checkbox" value="{{.ID}}" />
<label class="poll_option_label" for="poll_option_{{.ID}}">
<div class="sel"></div>
</label>
<span id="poll_option_text_{{.ID}}" class="poll_option_text">{{.Value}}</span>
</div>
{{end}}
<div class="poll_buttons">
<button form="poll_{{.Poll.ID}}_form" class="poll_vote_button">{{lang "topic.poll_vote"}}</button>
<button class="poll_results_button" data-poll-id="{{.Poll.ID}}">{{lang "topic.poll_results"}}</button>
<a href="#"><button class="poll_cancel_button">{{lang "topic.poll_cancel"}}</button></a>
</div>
</div>
<div id="poll_results_{{.Poll.ID}}" class="poll_results auto_hide">
<div class="topic_content user_content">
<div class="auto_hide poll_no_results">{{lang "topic.poll_no_results"}}</div>
</div>
</div>
</div>
</article>

View File

@ -521,10 +521,11 @@ input[type=checkbox] + label {
background-color: var(--element-background-color); background-color: var(--element-background-color);
} }
input[type=checkbox]:checked + label .sel { input[type=checkbox]:checked + label .sel {
display: inline-block; display: block;
width: 5px; width: 5px;
height: 5px; height: 5px;
background: var(--element-border-color); background: var(--element-border-color);
margin-top: -2px;
} }
.poll_content_row { .poll_content_row {
padding-left: 20px; padding-left: 20px;
@ -544,7 +545,7 @@ input[type=checkbox]:checked + label .sel {
.show_on_block_edit:not(.edit_opened), .show_on_block_edit:not(.edit_opened),
.hide_on_block_edit.edit_opened, .hide_on_block_edit.edit_opened,
.link_select:not(.link_opened) { .link_select:not(.link_opened) {
display: none; display: none !important;
} }
input[type=checkbox] + label.poll_option_label { input[type=checkbox] + label.poll_option_label {
@ -552,20 +553,22 @@ input[type=checkbox] + label.poll_option_label {
height: 18px; height: 18px;
} }
input[type=checkbox]:checked + label.poll_option_label .sel { input[type=checkbox]:checked + label.poll_option_label .sel {
display: inline-block; display: block;
width: 10px; width: 10px;
height: 10px; height: 10px;
margin-left: 3px; margin-left: 3px;
margin-top: 3px;
background: var(--element-border-color); background: var(--element-border-color);
} }
/*.poll_option { .poll_option {
margin-bottom: 3px; padding-bottom: 5px;
}*/ display: flex;
}
.poll_option_text { .poll_option_text {
display: inline-block; display: block;
margin-left: 3px; margin-left: 8px;
margin-top: 1px;
font-size: 15px; font-size: 15px;
/*font-weight: bold;*/
position: relative; position: relative;
top: -1px; top: -1px;
color: var(--light-text-color); color: var(--light-text-color);
@ -587,6 +590,9 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
margin-left: 16px; margin-left: 16px;
margin-top: 4px; margin-top: 4px;
} }
.poll_results {
margin-left: 14px;
}
.formbutton { .formbutton {
margin-top: 12px; margin-top: 12px;
@ -1078,6 +1084,9 @@ blockquote:first-child {
.user_content strong h2, .user_content strong h3, .user_content strong h4 { .user_content strong h2, .user_content strong h3, .user_content strong h4 {
font-weight: bold; font-weight: bold;
} }
red {
color: red;
}
.button_container { .button_container {
margin-top: auto; margin-top: auto;
display: flex; display: flex;

View File

@ -868,6 +868,9 @@ blockquote:first-child {
width: 100% !important; width: 100% !important;
padding: 16px; padding: 16px;
} }
red {
color: red;
}
.user_content.in_edit a { .user_content.in_edit a {
display: flex; display: flex;
background-color: #444444; background-color: #444444;
@ -942,6 +945,9 @@ input[type=checkbox]:checked + label .sel {
.poll_buttons button { .poll_buttons button {
margin-right: 8px; margin-right: 8px;
} }
.poll_results {
margin-left: 12px;
}
.ip_item { .ip_item {
display: none; display: none;

View File

@ -280,6 +280,9 @@ h1, h2, h3, h4, h5 {
.user_content strong h2, .user_content strong h3, .user_content strong h4 { .user_content strong h2, .user_content strong h3, .user_content strong h4 {
font-weight: bold; font-weight: bold;
} }
red {
color: red;
}
.controls { .controls {
width: 100%; width: 100%;
@ -682,6 +685,9 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
padding-bottom: 6px; padding-bottom: 6px;
font-size: 13px; font-size: 13px;
} }
.poll_buttons > *:not(:first-child) {
margin-left: 5px;
}
.poll_results { .poll_results {
margin-left: auto; margin-left: auto;
max-height: 120px; max-height: 120px;

View File

@ -689,6 +689,9 @@ button.username {
.user_content strong h2, .user_content strong h3, .user_content strong h4 { .user_content strong h2, .user_content strong h3, .user_content strong h4 {
font-weight: bold; font-weight: bold;
} }
red {
color: red;
}
.user_tag { .user_tag {
float: right; float: right;
@ -859,6 +862,9 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
padding-bottom: 3px; padding-bottom: 3px;
border: 1px solid hsl(0, 0%, 70%); border: 1px solid hsl(0, 0%, 70%);
} }
.poll_buttons > *:not(:first-child) {
margin-left: 5px;
}
.poll_results { .poll_results {
margin-left: auto; margin-left: auto;
} }