optimise topic pages
optimise profiles simple nil ptr handling in template boolean expressions
This commit is contained in:
parent
47d88f1744
commit
46a87defee
|
@ -16,6 +16,10 @@ func (c *NullUserCache) DeallocOverflow(evictPriority bool) (evicted int) {
|
|||
func (c *NullUserCache) Get(id int) (*User, error) {
|
||||
return nil, ErrNoRows
|
||||
}
|
||||
|
||||
func (c *NullUserCache) Getn(id int) *User {
|
||||
return nil
|
||||
}
|
||||
func (c *NullUserCache) BulkGet(ids []int) (list []*User) {
|
||||
return make([]*User, len(ids))
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ type TopicPage struct {
|
|||
ItemList []*ReplyUser
|
||||
Topic TopicUser
|
||||
Forum *Forum
|
||||
Poll Poll
|
||||
Poll *Poll
|
||||
Paginator
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ type ReplyUser struct {
|
|||
URL string
|
||||
//URLPrefix string
|
||||
//URLName string
|
||||
Group int
|
||||
Level int
|
||||
ActionIcon string
|
||||
|
||||
|
@ -41,7 +42,7 @@ type Reply struct {
|
|||
ParentID int
|
||||
Content string
|
||||
CreatedBy int
|
||||
Group int
|
||||
//Group int
|
||||
CreatedAt time.Time
|
||||
LastEdit int
|
||||
LastEditBy int
|
||||
|
@ -49,7 +50,7 @@ type Reply struct {
|
|||
IP string
|
||||
Liked bool
|
||||
LikeCount int
|
||||
AttachCount int
|
||||
AttachCount uint16
|
||||
ActionType string
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,6 @@ func (s *SQLReplyStore) Create(t *Topic, content, ip string, uid int) (id int, e
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
lastID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
|
|
@ -227,7 +227,7 @@ func compileCommons(c *tmpl.CTemplateSet, head, head2 *Header, forumList []Forum
|
|||
}*/
|
||||
|
||||
var topicsList []*TopicsRow
|
||||
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "127.0.0.1", 1, 0, 1, 1, 0, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil})
|
||||
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 1, 0, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil})
|
||||
topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
|
||||
o.Add("topics", "c.TopicListPage", topicListPage)
|
||||
o.Add("topics_mini", "c.TopicListPage", topicListPage)
|
||||
|
@ -246,11 +246,11 @@ func compileCommons(c *tmpl.CTemplateSet, head, head2 *Header, forumList []Forum
|
|||
topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", 58, false, miniAttach, nil, false}
|
||||
|
||||
var replyList []*ReplyUser
|
||||
reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
||||
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Level: 0, Attachments: miniAttach}
|
||||
ru.Init()
|
||||
reply := Reply{1, 1, "Yo!", 1 /*, Config.DefaultGroup*/, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
||||
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
|
||||
ru.Init(user2)
|
||||
replyList = append(replyList, ru)
|
||||
tpage := TopicPage{htitle("Topic Name"), replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, Paginator{[]int{1}, 1, 1}}
|
||||
tpage := TopicPage{htitle("Topic Name"), replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, &poll, Paginator{[]int{1}, 1, 1}}
|
||||
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
|
||||
o.Add("topic", "c.TopicPage", tpage)
|
||||
o.Add("topic_mini", "c.TopicPage", tpage)
|
||||
|
@ -276,20 +276,24 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
|
|||
//topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false, miniAttach, nil}
|
||||
// TODO: Do we want the UID on this to be 0?
|
||||
//avatar, microAvatar = BuildAvatar(0, "")
|
||||
reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
||||
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: "", Level: 0, Attachments: miniAttach}
|
||||
ru.Init()
|
||||
reply := Reply{1, 1, "Yo!", 1 /*, Config.DefaultGroup*/, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
||||
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: "", Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
|
||||
ru.Init(user)
|
||||
replyList = append(replyList, ru)
|
||||
|
||||
// TODO: Use a dummy forum list to avoid o(n) problems
|
||||
var forumList []Forum
|
||||
/*var forumList []Forum
|
||||
forums, err := Forums.GetAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, forum := range forums {
|
||||
forumList = append(forumList, *forum)
|
||||
}
|
||||
}*/
|
||||
forum := BlankForum(1, "/forum/d.1", "d", "d desc", true, "", 0, "", 1)
|
||||
forum.LastTopic = BlankTopic()
|
||||
forum.LastReplyer = BlankUser()
|
||||
forumList := []Forum{*forum}
|
||||
|
||||
// Convienience function to save a line here and there
|
||||
htitle := func(name string) *Header {
|
||||
|
@ -297,7 +301,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
|
|||
return header
|
||||
}
|
||||
t := TItemHold(make(map[string]TItem))
|
||||
err = compileCommons(c, header, header2, forumList, t)
|
||||
err := compileCommons(c, header, header2, forumList, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -547,14 +551,14 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri
|
|||
var replyList []*ReplyUser
|
||||
// TODO: Do we really want the UID here to be zero?
|
||||
avatar, microAvatar = BuildAvatar(0, "")
|
||||
reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
||||
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Level: 0, Attachments: miniAttach}
|
||||
ru.Init()
|
||||
reply := Reply{1, 1, "Yo!", 1 /*, Config.DefaultGroup*/, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
||||
ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
|
||||
ru.Init(user)
|
||||
replyList = append(replyList, ru)
|
||||
|
||||
varList = make(map[string]tmpl.VarItem)
|
||||
header.Title = "Topic Name"
|
||||
tpage := TopicPage{header, replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, Paginator{[]int{1}, 1, 1}}
|
||||
tpage := TopicPage{header, replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, &poll, Paginator{[]int{1}, 1, 1}}
|
||||
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
|
||||
t.AddStd("topic_posts", "c.TopicPage", tpage)
|
||||
t.AddStd("topic_alt_posts", "c.TopicPage", tpage)
|
||||
|
|
|
@ -54,6 +54,20 @@ func (con *CContext) PushPhrasef(langIndex int, args string) (index int) {
|
|||
return con.LastBufIndex()
|
||||
}
|
||||
|
||||
func (con *CContext) StartIf(body string) (index int) {
|
||||
*con.OutBuf = append(*con.OutBuf, OutBufferFrame{body, "startif", con.TemplateName, false, nil})
|
||||
return con.LastBufIndex()
|
||||
}
|
||||
func (con *CContext) StartIfPtr(body string) (index int) {
|
||||
*con.OutBuf = append(*con.OutBuf, OutBufferFrame{body, "startif", con.TemplateName, true, nil})
|
||||
return con.LastBufIndex()
|
||||
}
|
||||
|
||||
func (con *CContext) EndIf(startIndex int, body string) (index int) {
|
||||
*con.OutBuf = append(*con.OutBuf, OutBufferFrame{body, "endif", con.TemplateName, startIndex, nil})
|
||||
return con.LastBufIndex()
|
||||
}
|
||||
|
||||
func (con *CContext) StartLoop(body string) (index int) {
|
||||
con.LoopDepth++
|
||||
return con.Push("startloop", body)
|
||||
|
@ -73,15 +87,15 @@ func (con *CContext) EndTemplate(body string) (index int) {
|
|||
|
||||
func (con *CContext) AttachVars(vars string, index int) {
|
||||
outBuf := *con.OutBuf
|
||||
node := outBuf[index]
|
||||
if node.Type != "starttemplate" && node.Type != "startloop" {
|
||||
panic("not a starttemplate node")
|
||||
n := outBuf[index]
|
||||
if n.Type != "starttemplate" && n.Type != "startloop" && n.Type != "startif" {
|
||||
panic("not a starttemplate, startloop or startif node")
|
||||
}
|
||||
node.Body += vars
|
||||
outBuf[index] = node
|
||||
n.Body += vars
|
||||
outBuf[index] = n
|
||||
}
|
||||
|
||||
func (con *CContext) addFrame(body string, ftype string, extra1 interface{}, extra2 interface{}) (index int) {
|
||||
func (con *CContext) addFrame(body, ftype string, extra1 interface{}, extra2 interface{}) (index int) {
|
||||
*con.OutBuf = append(*con.OutBuf, OutBufferFrame{body, ftype, con.TemplateName, extra1, extra2})
|
||||
return con.LastBufIndex()
|
||||
}
|
||||
|
|
|
@ -513,8 +513,7 @@ if !ok {
|
|||
}
|
||||
`*/
|
||||
fout += `var iw http.ResponseWriter
|
||||
gzw, ok := w.(c.GzipResponseWriter)
|
||||
if ok {
|
||||
if gzw, ok := w.(c.GzipResponseWriter); ok {
|
||||
iw = gzw.ResponseWriter
|
||||
}
|
||||
_ = iw
|
||||
|
@ -711,14 +710,26 @@ func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
|
|||
return
|
||||
}
|
||||
|
||||
con.Push("startif", "if "+expr+" {\n")
|
||||
var startIf int
|
||||
var nilIf = strings.HasPrefix(expr, con.RootHolder) && strings.HasSuffix(expr, "!=nil")
|
||||
if nilIf {
|
||||
startIf = con.StartIfPtr("if " + expr + " {\n")
|
||||
} else {
|
||||
startIf = con.StartIf("if " + expr + " {\n")
|
||||
}
|
||||
c.compileSwitch(con, node.List)
|
||||
if node.ElseList == nil {
|
||||
c.detail("Selected Branch 1")
|
||||
con.Push("endif", "}\n")
|
||||
con.EndIf(startIf, "}\n")
|
||||
if nilIf {
|
||||
c.afterTemplate(con, startIf)
|
||||
}
|
||||
} else {
|
||||
c.detail("Selected Branch 2")
|
||||
con.Push("endif", "}")
|
||||
con.EndIf(startIf, "}")
|
||||
if nilIf {
|
||||
c.afterTemplate(con, startIf)
|
||||
}
|
||||
con.Push("startelse", " else {\n")
|
||||
c.compileSwitch(con, node.ElseList)
|
||||
con.Push("endelse", "}\n")
|
||||
|
@ -769,7 +780,7 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
|
|||
c.detail("Range Kind Switch!")
|
||||
|
||||
startIf := func(item reflect.Value, useCopy bool) {
|
||||
con.Push("startif", "if len("+expr+") != 0 {\n")
|
||||
sIndex := con.StartIf("if len(" + expr + ")!=0 {\n")
|
||||
startIndex := con.StartLoop("for _, item := range " + expr + " {\n")
|
||||
ccon := con
|
||||
var depth string
|
||||
|
@ -793,7 +804,7 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
|
|||
con.EndLoop("}\n")
|
||||
c.afterTemplate(con, startIndex)
|
||||
if node.ElseList != nil {
|
||||
con.Push("endif", "}")
|
||||
con.EndIf(sIndex, "}")
|
||||
con.Push("startelse", " else {\n")
|
||||
if !useCopy {
|
||||
ccon = con
|
||||
|
@ -801,7 +812,7 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
|
|||
c.compileSwitch(ccon, node.ElseList)
|
||||
con.Push("endelse", "}\n")
|
||||
} else {
|
||||
con.Push("endif", "}\n")
|
||||
con.EndIf(sIndex, "}\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1512,12 +1523,15 @@ func (c *CTemplateSet) compileBoolSub(con CContext, varname string) string {
|
|||
// TODO: What if it's a pointer or an interface? I *think* we've got pointers handled somewhere, but not interfaces which we don't know the types of at compile time
|
||||
switch val.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
|
||||
out += " > 0"
|
||||
out += ">0"
|
||||
case reflect.Bool: // Do nothing
|
||||
case reflect.String:
|
||||
out += " != \"\""
|
||||
out += "!=\"\""
|
||||
case reflect.Slice, reflect.Map:
|
||||
out = "len(" + out + ") != 0"
|
||||
out = "len(" + out + ")!=0"
|
||||
// TODO: Follow the pointer and evaluate it?
|
||||
case reflect.Ptr:
|
||||
out += "!=nil"
|
||||
default:
|
||||
c.logger.Println("Variable Name:", varname)
|
||||
c.logger.Println("Variable Holder:", con.VarHolder)
|
||||
|
@ -1675,9 +1689,9 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
|
|||
return
|
||||
}
|
||||
}
|
||||
con.Push("startif", "if "+varname+" {\n")
|
||||
startIf := con.StartIf("if " + varname + " {\n")
|
||||
c.addText(con, []byte("true"))
|
||||
con.Push("endif", "} ")
|
||||
con.EndIf(startIf, "} ")
|
||||
con.Push("startelse", "else {\n")
|
||||
c.addText(con, []byte("false"))
|
||||
con.Push("endelse", "}\n")
|
||||
|
@ -1945,42 +1959,66 @@ func (c *CTemplateSet) loadTemplate(fileDir, name string) (content string, err e
|
|||
return content, nil
|
||||
}
|
||||
|
||||
func (c *CTemplateSet) afterTemplate(con CContext, startIndex int) {
|
||||
func (c *CTemplateSet) afterTemplate(con CContext, startIndex int /*, svmap map[string]int*/) {
|
||||
c.dumpCall("afterTemplate", con, startIndex)
|
||||
defer c.retCall("afterTemplate")
|
||||
|
||||
loopDepth := 0
|
||||
ifNilDepth := 0
|
||||
var outBuf = *con.OutBuf
|
||||
varcounts := make(map[string]int)
|
||||
loopStart := startIndex
|
||||
if outBuf[startIndex].Type == "startloop" && (len(outBuf) > startIndex+1) {
|
||||
otype := outBuf[startIndex].Type
|
||||
if otype == "startloop" && (len(outBuf) > startIndex+1) {
|
||||
loopStart++
|
||||
}
|
||||
if otype == "startif" && (len(outBuf) > startIndex+1) {
|
||||
loopStart++
|
||||
}
|
||||
|
||||
// Exclude varsubs within loops for now
|
||||
OLoop:
|
||||
for i := loopStart; i < len(outBuf); i++ {
|
||||
item := outBuf[i]
|
||||
c.detail("item:", item)
|
||||
if item.Type == "startloop" {
|
||||
switch item.Type {
|
||||
case "startloop":
|
||||
loopDepth++
|
||||
c.detail("loopDepth:", loopDepth)
|
||||
} else if item.Type == "endloop" {
|
||||
case "endloop":
|
||||
loopDepth--
|
||||
c.detail("loopDepth:", loopDepth)
|
||||
if loopDepth == -1 {
|
||||
break
|
||||
break OLoop
|
||||
}
|
||||
} else if item.Type == "varsub" && loopDepth == 0 {
|
||||
case "startif":
|
||||
if item.Extra.(bool) == true {
|
||||
ifNilDepth++
|
||||
}
|
||||
case "endif":
|
||||
item2 := outBuf[item.Extra.(int)]
|
||||
if item2.Extra.(bool) == true {
|
||||
ifNilDepth--
|
||||
}
|
||||
if ifNilDepth == -1 {
|
||||
break OLoop
|
||||
}
|
||||
case "varsub":
|
||||
if loopDepth == 0 && ifNilDepth == 0 {
|
||||
count := varcounts[item.Body]
|
||||
varcounts[item.Body] = count + 1
|
||||
c.detail("count " + strconv.Itoa(count) + " for " + item.Body)
|
||||
c.detail("loopDepth:", loopDepth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var varstr string
|
||||
var i int
|
||||
varmap := make(map[string]int)
|
||||
/*for svkey, sventry := range svmap {
|
||||
varmap[svkey] = sventry
|
||||
}*/
|
||||
for name, count := range varcounts {
|
||||
if count > 1 {
|
||||
varstr += "var c_v_" + strconv.Itoa(i) + "=" + name + "\n"
|
||||
|
@ -1991,16 +2029,32 @@ func (c *CTemplateSet) afterTemplate(con CContext, startIndex int) {
|
|||
|
||||
// Exclude varsubs within loops for now
|
||||
loopDepth = 0
|
||||
ifNilDepth = 0
|
||||
OOLoop:
|
||||
for i := loopStart; i < len(outBuf); i++ {
|
||||
item := outBuf[i]
|
||||
if item.Type == "startloop" {
|
||||
switch item.Type {
|
||||
case "startloop":
|
||||
loopDepth++
|
||||
} else if item.Type == "endloop" {
|
||||
case "endloop":
|
||||
loopDepth--
|
||||
if loopDepth == -1 {
|
||||
break
|
||||
break OOLoop
|
||||
} //con.Push("startif", "if "+varname+" {\n")
|
||||
case "startif":
|
||||
if item.Extra.(bool) == true {
|
||||
ifNilDepth++
|
||||
}
|
||||
} else if item.Type == "varsub" && loopDepth == 0 {
|
||||
case "endif":
|
||||
item2 := outBuf[item.Extra.(int)]
|
||||
if item2.Extra.(bool) == true {
|
||||
ifNilDepth--
|
||||
}
|
||||
if ifNilDepth == -1 {
|
||||
break OOLoop
|
||||
}
|
||||
case "varsub":
|
||||
if loopDepth == 0 && ifNilDepth == 0 {
|
||||
index, ok := varmap[item.Body]
|
||||
if ok {
|
||||
item.Body = "c_v_" + strconv.Itoa(index)
|
||||
|
@ -2009,6 +2063,122 @@ func (c *CTemplateSet) afterTemplate(con CContext, startIndex int) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
con.AttachVars(varstr, startIndex)
|
||||
}
|
||||
|
||||
const (
|
||||
ATTmpl = iota
|
||||
ATLoop
|
||||
ATIfPtr
|
||||
)
|
||||
|
||||
func (c *CTemplateSet) afterTemplateV2(con CContext, startIndex int /*, typ int*/, svmap map[string]int) {
|
||||
c.dumpCall("afterTemplateV2", con, startIndex)
|
||||
defer c.retCall("afterTemplateV2")
|
||||
|
||||
loopDepth := 0
|
||||
ifNilDepth := 0
|
||||
var outBuf = *con.OutBuf
|
||||
varcounts := make(map[string]int)
|
||||
loopStart := startIndex
|
||||
otype := outBuf[startIndex].Type
|
||||
if otype == "startloop" && (len(outBuf) > startIndex+1) {
|
||||
loopStart++
|
||||
}
|
||||
if otype == "startif" && (len(outBuf) > startIndex+1) {
|
||||
loopStart++
|
||||
}
|
||||
|
||||
// Exclude varsubs within loops for now
|
||||
OLoop:
|
||||
for i := loopStart; i < len(outBuf); i++ {
|
||||
item := outBuf[i]
|
||||
c.detail("item:", item)
|
||||
switch item.Type {
|
||||
case "startloop":
|
||||
loopDepth++
|
||||
c.detail("loopDepth:", loopDepth)
|
||||
case "endloop":
|
||||
loopDepth--
|
||||
c.detail("loopDepth:", loopDepth)
|
||||
if loopDepth == -1 {
|
||||
break OLoop
|
||||
}
|
||||
case "startif":
|
||||
if item.Extra.(bool) == true {
|
||||
ifNilDepth++
|
||||
}
|
||||
case "endif":
|
||||
item2 := outBuf[item.Extra.(int)]
|
||||
if item2.Extra.(bool) == true {
|
||||
ifNilDepth--
|
||||
}
|
||||
if ifNilDepth == -1 {
|
||||
break OLoop
|
||||
}
|
||||
case "varsub":
|
||||
if loopDepth == 0 && ifNilDepth == 0 {
|
||||
count := varcounts[item.Body]
|
||||
varcounts[item.Body] = count + 1
|
||||
c.detail("count " + strconv.Itoa(count) + " for " + item.Body)
|
||||
c.detail("loopDepth:", loopDepth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var varstr string
|
||||
var i int
|
||||
varmap := make(map[string]int)
|
||||
/*for svkey, sventry := range svmap {
|
||||
varmap[svkey] = sventry
|
||||
}*/
|
||||
for name, count := range varcounts {
|
||||
if count > 1 {
|
||||
varstr += "var c_v_" + strconv.Itoa(i) + "=" + name + "\n"
|
||||
varmap[name] = i
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude varsubs within loops for now
|
||||
loopDepth = 0
|
||||
ifNilDepth = 0
|
||||
OOLoop:
|
||||
for i := loopStart; i < len(outBuf); i++ {
|
||||
item := outBuf[i]
|
||||
switch item.Type {
|
||||
case "startloop":
|
||||
loopDepth++
|
||||
case "endloop":
|
||||
loopDepth--
|
||||
if loopDepth == -1 {
|
||||
break OOLoop
|
||||
} //con.Push("startif", "if "+varname+" {\n")
|
||||
case "startif":
|
||||
if item.Extra.(bool) == true {
|
||||
ifNilDepth++
|
||||
}
|
||||
case "endif":
|
||||
item2 := outBuf[item.Extra.(int)]
|
||||
if item2.Extra.(bool) == true {
|
||||
ifNilDepth--
|
||||
}
|
||||
if ifNilDepth == -1 {
|
||||
break OOLoop
|
||||
}
|
||||
case "varsub":
|
||||
if loopDepth == 0 && ifNilDepth == 0 {
|
||||
index, ok := varmap[item.Body]
|
||||
if ok {
|
||||
item.Body = "c_v_" + strconv.Itoa(index)
|
||||
item.Type = "cvarsub"
|
||||
outBuf[i] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
con.AttachVars(varstr, startIndex)
|
||||
}
|
||||
|
|
368
common/topic.go
368
common/topic.go
|
@ -188,6 +188,8 @@ func (t *TopicsRow) Topic() *Topic {
|
|||
type TopicStmts struct {
|
||||
getRids *sql.Stmt
|
||||
getReplies *sql.Stmt
|
||||
getReplies2 *sql.Stmt
|
||||
getReplies3 *sql.Stmt
|
||||
addReplies *sql.Stmt
|
||||
updateLastReply *sql.Stmt
|
||||
lock *sql.Stmt
|
||||
|
@ -217,7 +219,9 @@ func init() {
|
|||
t := "topics"
|
||||
topicStmts = TopicStmts{
|
||||
getRids: acc.Select("replies").Columns("rid").Where("tid=?").Orderby("rid ASC").Limit("?,?").Prepare(),
|
||||
getReplies: acc.SimpleLeftJoin("replies AS r", "users AS u", "r.rid, r.content, r.createdBy, r.createdAt, r.lastEdit, r.lastEditBy, u.avatar, u.name, u.group, u.level, r.ip, r.likeCount, r.attachCount, r.actionType", "r.createdBy = u.uid", "r.tid = ?", "r.rid ASC", "?,?"),
|
||||
getReplies: acc.SimpleLeftJoin("replies AS r", "users AS u", "r.rid, r.content, r.createdBy, r.createdAt, r.lastEdit, r.lastEditBy, u.avatar, u.name, u.group, u.level, r.ip, r.likeCount, r.attachCount, r.actionType", "r.createdBy=u.uid", "r.tid=?", "r.rid ASC", "?,?"),
|
||||
getReplies2: acc.SimpleLeftJoin("replies AS r", "users AS u", "r.rid, r.content, r.createdBy, r.createdAt, r.lastEdit, r.lastEditBy, u.avatar, u.name, u.group, u.level, r.likeCount, r.attachCount, r.actionType", "r.createdBy=u.uid", "r.tid=?", "r.rid ASC", "?,?"),
|
||||
getReplies3: acc.Select("replies").Columns("rid, content, createdBy, createdAt, lastEdit, lastEditBy, likeCount, attachCount, actionType").Where("tid=?").Orderby("rid ASC").Limit("?,?").Prepare(),
|
||||
addReplies: acc.Update(t).Set("postCount=postCount+?, lastReplyBy=?, lastReplyAt=UTC_TIMESTAMP()").Where("tid=?").Prepare(),
|
||||
updateLastReply: acc.Update(t).Set("lastReplyID=?").Where("lastReplyID > ? AND tid=?").Prepare(),
|
||||
lock: acc.Update(t).Set("is_closed=1").Where("tid=?").Prepare(),
|
||||
|
@ -570,8 +574,7 @@ var unlockai = "🔓"
|
|||
var stickai = "📌"
|
||||
var unstickai = "📌" + aipost
|
||||
|
||||
func (ru *ReplyUser) Init() (group *Group, err error) {
|
||||
ru.UserLink = BuildProfileURL(NameToSlug(ru.CreatedByName), ru.CreatedBy)
|
||||
func (ru *ReplyUser) Init(u *User) (group *Group, err error) {
|
||||
ru.ContentLines = strings.Count(ru.Content, "\n")
|
||||
|
||||
postGroup, err := Groups.Get(ru.Group)
|
||||
|
@ -583,8 +586,117 @@ func (ru *ReplyUser) Init() (group *Group, err error) {
|
|||
}
|
||||
ru.Tag = postGroup.Tag
|
||||
|
||||
if u.ID != ru.CreatedBy {
|
||||
ru.UserLink = BuildProfileURL(NameToSlug(ru.CreatedByName), ru.CreatedBy)
|
||||
// TODO: Make a function for this? Build a more sophisticated noavatar handling system? Do bulk user loads and let the c.UserStore initialise this?
|
||||
ru.Avatar, ru.MicroAvatar = BuildAvatar(ru.CreatedBy, ru.Avatar)
|
||||
} else {
|
||||
ru.UserLink = u.Link
|
||||
ru.Avatar, ru.MicroAvatar = u.Avatar, u.MicroAvatar
|
||||
}
|
||||
|
||||
// We really shouldn't have inline HTML, we should do something about this...
|
||||
if ru.ActionType != "" {
|
||||
aarr := strings.Split(ru.ActionType, "-")
|
||||
action := aarr[0]
|
||||
switch action {
|
||||
case "lock":
|
||||
ru.ActionIcon = lockai
|
||||
case "unlock":
|
||||
ru.ActionIcon = unlockai
|
||||
case "stick":
|
||||
ru.ActionIcon = stickai
|
||||
case "unstick":
|
||||
ru.ActionIcon = unstickai
|
||||
case "move":
|
||||
if len(aarr) == 2 {
|
||||
fid, _ := strconv.Atoi(aarr[1])
|
||||
forum, err := Forums.Get(fid)
|
||||
if err == nil {
|
||||
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_move_dest", forum.Link, forum.Name, ru.UserLink, ru.CreatedByName)
|
||||
return postGroup, nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
// TODO: Only fire this off if a corresponding phrase for the ActionType doesn't exist? Or maybe have some sort of action registry?
|
||||
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_default", ru.ActionType)
|
||||
return postGroup, nil
|
||||
}
|
||||
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_"+action, ru.UserLink, ru.CreatedByName)
|
||||
}
|
||||
|
||||
return postGroup, nil
|
||||
}
|
||||
|
||||
func (ru *ReplyUser) Init2() (group *Group, err error) {
|
||||
//ru.UserLink = BuildProfileURL(NameToSlug(ru.CreatedByName), ru.CreatedBy)
|
||||
ru.ContentLines = strings.Count(ru.Content, "\n")
|
||||
|
||||
postGroup, err := Groups.Get(ru.Group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if postGroup.IsMod {
|
||||
ru.ClassName = Config.StaffCSS
|
||||
}
|
||||
ru.Tag = postGroup.Tag
|
||||
|
||||
// We really shouldn't have inline HTML, we should do something about this...
|
||||
if ru.ActionType != "" {
|
||||
aarr := strings.Split(ru.ActionType, "-")
|
||||
action := aarr[0]
|
||||
switch action {
|
||||
case "lock":
|
||||
ru.ActionIcon = lockai
|
||||
case "unlock":
|
||||
ru.ActionIcon = unlockai
|
||||
case "stick":
|
||||
ru.ActionIcon = stickai
|
||||
case "unstick":
|
||||
ru.ActionIcon = unstickai
|
||||
case "move":
|
||||
if len(aarr) == 2 {
|
||||
fid, _ := strconv.Atoi(aarr[1])
|
||||
forum, err := Forums.Get(fid)
|
||||
if err == nil {
|
||||
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_move_dest", forum.Link, forum.Name, ru.UserLink, ru.CreatedByName)
|
||||
return postGroup, nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
// TODO: Only fire this off if a corresponding phrase for the ActionType doesn't exist? Or maybe have some sort of action registry?
|
||||
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_default", ru.ActionType)
|
||||
return postGroup, nil
|
||||
}
|
||||
ru.ActionType = p.GetTmplPhrasef("topic.action_topic_"+action, ru.UserLink, ru.CreatedByName)
|
||||
}
|
||||
|
||||
return postGroup, nil
|
||||
}
|
||||
|
||||
func (ru *ReplyUser) Init3(u *User, tu *TopicUser) (group *Group, err error) {
|
||||
ru.ContentLines = strings.Count(ru.Content, "\n")
|
||||
|
||||
postGroup, err := Groups.Get(ru.Group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if postGroup.IsMod {
|
||||
ru.ClassName = Config.StaffCSS
|
||||
}
|
||||
ru.Tag = postGroup.Tag
|
||||
|
||||
if u.ID == ru.CreatedBy {
|
||||
ru.UserLink = u.Link
|
||||
ru.Avatar, ru.MicroAvatar = u.Avatar, u.MicroAvatar
|
||||
} else if tu.CreatedBy == ru.CreatedBy {
|
||||
ru.UserLink = tu.UserLink
|
||||
ru.Avatar, ru.MicroAvatar = tu.Avatar, tu.MicroAvatar
|
||||
} else {
|
||||
ru.UserLink = BuildProfileURL(NameToSlug(ru.CreatedByName), ru.CreatedBy)
|
||||
// TODO: Make a function for this? Build a more sophisticated noavatar handling system? Do bulk user loads and let the c.UserStore initialise this?
|
||||
ru.Avatar, ru.MicroAvatar = BuildAvatar(ru.CreatedBy, ru.Avatar)
|
||||
}
|
||||
|
||||
// We really shouldn't have inline HTML, we should do something about this...
|
||||
if ru.ActionType != "" {
|
||||
|
@ -620,18 +732,9 @@ func (ru *ReplyUser) Init() (group *Group, err error) {
|
|||
}
|
||||
|
||||
// TODO: Factor TopicUser into a *Topic and *User, as this starting to become overly complicated x.x
|
||||
func (t *TopicUser) Replies(offset, pFrag int, user *User) (rlist []*ReplyUser, ogdesc string, err error) {
|
||||
var likedMap map[int]int
|
||||
if user.Liked > 0 {
|
||||
likedMap = make(map[int]int)
|
||||
}
|
||||
likedQueryList := []int{user.ID}
|
||||
|
||||
var attachMap map[int]int
|
||||
if user.Perms.EditReply {
|
||||
attachMap = make(map[int]int)
|
||||
}
|
||||
attachQueryList := []int{}
|
||||
func (t *TopicUser) Replies(offset, pFrag int, user *User) (rlist []*ReplyUser /*, ogdesc string*/, err error) {
|
||||
var likedMap, attachMap map[int]int
|
||||
var likedQueryList, attachQueryList []int
|
||||
|
||||
var rid int
|
||||
if len(t.Rids) > 0 {
|
||||
|
@ -641,15 +744,19 @@ func (t *TopicUser) Replies(offset, pFrag int, user *User) (rlist []*ReplyUser,
|
|||
re, err := Rstore.GetCache().Get(rid)
|
||||
ucache := Users.GetCache()
|
||||
var ruser *User
|
||||
if err == nil && ucache != nil {
|
||||
if ucache != nil {
|
||||
//log.Print("ucache step")
|
||||
ruser, err = ucache.Get(re.CreatedBy)
|
||||
if err == nil {
|
||||
ruser = ucache.Getn(re.CreatedBy)
|
||||
} else if t.PostCount == 2 {
|
||||
ruser = ucache.Getn(t.LastReplyBy)
|
||||
}
|
||||
}
|
||||
|
||||
hTbl := GetHookTable()
|
||||
rf := func(r *ReplyUser) error {
|
||||
rf := func(r *ReplyUser) (err error) {
|
||||
//log.Printf("before r: %+v\n", r)
|
||||
group, err := r.Init()
|
||||
group, err := r.Init3(user, t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -668,6 +775,7 @@ func (t *TopicUser) Replies(offset, pFrag int, user *User) (rlist []*ReplyUser,
|
|||
if r.ContentHtml == r.Content {
|
||||
r.ContentHtml = r.Content
|
||||
}
|
||||
r.Deletable = user.Perms.DeleteReply || r.CreatedBy == user.ID
|
||||
|
||||
// TODO: This doesn't work properly so pick the first one instead?
|
||||
/*if r.ID == pFrag {
|
||||
|
@ -677,61 +785,211 @@ func (t *TopicUser) Replies(offset, pFrag int, user *User) (rlist []*ReplyUser,
|
|||
}
|
||||
}*/
|
||||
|
||||
if r.LikeCount > 0 && user.Liked > 0 {
|
||||
likedMap[r.ID] = len(rlist)
|
||||
likedQueryList = append(likedQueryList, r.ID)
|
||||
return nil
|
||||
}
|
||||
if user.Perms.EditReply && r.AttachCount > 0 {
|
||||
attachMap[r.ID] = len(rlist)
|
||||
attachQueryList = append(attachQueryList, r.ID)
|
||||
|
||||
rf3 := func(r *ReplyUser) error {
|
||||
//log.Printf("before r: %+v\n", r)
|
||||
group, err := r.Init2()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var parseSettings *ParseSettings
|
||||
if !group.Perms.AutoEmbed && (user.ParseSettings == nil || !user.ParseSettings.NoEmbed) {
|
||||
parseSettings = DefaultParseSettings.CopyPtr()
|
||||
parseSettings.NoEmbed = true
|
||||
} else {
|
||||
parseSettings = user.ParseSettings
|
||||
}
|
||||
|
||||
r.ContentHtml = ParseMessage(r.Content, t.ParentID, "forums", parseSettings, user)
|
||||
// TODO: Do this more efficiently by avoiding the allocations entirely in ParseMessage, if there's nothing to do.
|
||||
if r.ContentHtml == r.Content {
|
||||
r.ContentHtml = r.Content
|
||||
}
|
||||
r.Deletable = user.Perms.DeleteReply || r.CreatedBy == user.ID
|
||||
|
||||
hTbl.VhookNoRet("topic_reply_row_assign", &rlist, &r)
|
||||
// TODO: Use a pointer instead to make it easier to abstract this loop? What impact would this have on escape analysis?
|
||||
rlist = append(rlist, r)
|
||||
//log.Printf("r: %d-%d", r.ID, len(rlist)-1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Factor the user fields out and embed a user struct instead
|
||||
if err == nil {
|
||||
if err == nil && ruser != nil {
|
||||
//log.Print("reply cached serve")
|
||||
reply := &ReplyUser{ClassName: "", Reply: *re, CreatedByName: ruser.Name, Avatar: ruser.Avatar /*URLPrefix: ruser.URLPrefix, URLName: ruser.URLName, */, Level: ruser.Level, Tag: ruser.Tag}
|
||||
reply.Group = ruser.Group
|
||||
err = rf(reply)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
r := &ReplyUser{ /*ClassName: "", */ Reply: *re, CreatedByName: ruser.Name, UserLink: ruser.Link, Avatar: ruser.Avatar, MicroAvatar: ruser.MicroAvatar /*URLPrefix: ruser.URLPrefix, URLName: ruser.URLName, */, Group: ruser.Group, Level: ruser.Level, Tag: ruser.Tag}
|
||||
if err = rf3(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.LikeCount > 0 && user.Liked > 0 {
|
||||
likedMap = map[int]int{r.ID: len(rlist)}
|
||||
likedQueryList = []int{r.ID}
|
||||
}
|
||||
if user.Perms.EditReply && r.AttachCount > 0 {
|
||||
attachMap = map[int]int{r.ID: len(rlist)}
|
||||
attachQueryList = []int{r.ID}
|
||||
}
|
||||
|
||||
hTbl.VhookNoRet("topic_reply_row_assign", &r)
|
||||
// TODO: Use a pointer instead to make it easier to abstract this loop? What impact would this have on escape analysis?
|
||||
rlist = []*ReplyUser{r}
|
||||
//log.Printf("r: %d-%d", r.ID, len(rlist)-1)
|
||||
} else {
|
||||
//log.Print("reply query serve")
|
||||
rows, err := topicStmts.getReplies.Query(t.ID, offset, Config.ItemsPerPage)
|
||||
rf2 := func(r *ReplyUser) {
|
||||
if r.LikeCount > 0 && user.Liked > 0 {
|
||||
if likedMap == nil {
|
||||
likedMap = map[int]int{r.ID: len(rlist)}
|
||||
likedQueryList = []int{r.ID}
|
||||
} else {
|
||||
likedMap[r.ID] = len(rlist)
|
||||
likedQueryList = append(likedQueryList, r.ID)
|
||||
}
|
||||
}
|
||||
if user.Perms.EditReply && r.AttachCount > 0 {
|
||||
if attachMap == nil {
|
||||
attachMap = map[int]int{r.ID: len(rlist)}
|
||||
attachQueryList = []int{r.ID}
|
||||
} else {
|
||||
attachMap[r.ID] = len(rlist)
|
||||
attachQueryList = append(attachQueryList, r.ID)
|
||||
}
|
||||
}
|
||||
|
||||
hTbl.VhookNoRet("topic_reply_row_assign", &r)
|
||||
// TODO: Use a pointer instead to make it easier to abstract this loop? What impact would this have on escape analysis?
|
||||
rlist = append(rlist, r)
|
||||
//log.Printf("r: %d-%d", r.ID, len(rlist)-1)
|
||||
}
|
||||
if !user.Perms.ViewIPs && ruser != nil {
|
||||
rows, err := topicStmts.getReplies3.Query(t.ID, offset, Config.ItemsPerPage)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
r := &ReplyUser{Avatar: ruser.Avatar, MicroAvatar: ruser.MicroAvatar, UserLink: ruser.Link, CreatedByName: ruser.Name, Group: ruser.Group, Level: ruser.Level}
|
||||
err := rows.Scan(&r.ID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.LikeCount, &r.AttachCount, &r.ActionType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = rf3(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rf2(r)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if user.Perms.ViewIPs {
|
||||
rows, err := topicStmts.getReplies.Query(t.ID, offset, Config.ItemsPerPage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
r := &ReplyUser{}
|
||||
err := rows.Scan(&r.ID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.Avatar, &r.CreatedByName, &r.Group /*&r.URLPrefix, &r.URLName,*/, &r.Level, &r.IP, &r.LikeCount, &r.AttachCount, &r.ActionType)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
err = rf(r)
|
||||
if err = rf(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rf2(r)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if t.PostCount >= 20 {
|
||||
rows, err := topicStmts.getReplies3.Query(t.ID, offset, Config.ItemsPerPage)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
reqUserList := make(map[int]bool)
|
||||
for rows.Next() {
|
||||
r := &ReplyUser{}
|
||||
err := rows.Scan(&r.ID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy /*&r.URLPrefix, &r.URLName,*/, &r.LikeCount, &r.AttachCount, &r.ActionType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.CreatedBy != t.CreatedBy && r.CreatedBy != user.ID {
|
||||
reqUserList[r.CreatedBy] = true
|
||||
}
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var userList map[int]*User
|
||||
if len(reqUserList) > 0 {
|
||||
// Convert the user ID map to a slice, then bulk load the users
|
||||
idSlice := make([]int, len(reqUserList))
|
||||
var i int
|
||||
for userID := range reqUserList {
|
||||
idSlice[i] = userID
|
||||
i++
|
||||
}
|
||||
userList, err = Users.BulkGetMap(idSlice)
|
||||
if err != nil {
|
||||
return nil, nil // TODO: Implement this!
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range rlist {
|
||||
var u *User
|
||||
if r.CreatedBy == t.CreatedBy {
|
||||
r.CreatedByName = t.CreatedByName
|
||||
r.Avatar = t.Avatar
|
||||
r.MicroAvatar = t.MicroAvatar
|
||||
r.Group = t.Group
|
||||
r.Level = t.Level
|
||||
} else {
|
||||
if r.CreatedBy == user.ID {
|
||||
u = user
|
||||
} else {
|
||||
u = userList[r.CreatedBy]
|
||||
}
|
||||
r.CreatedByName = u.Name
|
||||
r.Avatar = u.Avatar
|
||||
r.MicroAvatar = u.MicroAvatar
|
||||
r.Group = u.Group
|
||||
r.Level = u.Level
|
||||
}
|
||||
if err = rf(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rf2(r)
|
||||
}
|
||||
} else {
|
||||
rows, err := topicStmts.getReplies2.Query(t.ID, offset, Config.ItemsPerPage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
r := &ReplyUser{}
|
||||
err := rows.Scan(&r.ID, &r.Content, &r.CreatedBy, &r.CreatedAt, &r.LastEdit, &r.LastEditBy, &r.Avatar, &r.CreatedByName, &r.Group /*&r.URLPrefix, &r.URLName,*/, &r.Level, &r.LikeCount, &r.AttachCount, &r.ActionType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = rf(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rf2(r)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add a config setting to disable the liked query for a burst of extra speed
|
||||
if user.Liked > 0 && len(likedQueryList) > 1 /*&& user.LastLiked <= time.Now()*/ {
|
||||
eids, err := Likes.BulkExists(likedQueryList[1:], user.ID, "replies")
|
||||
if user.Liked > 0 && len(likedQueryList) > 0 /*&& user.LastLiked <= time.Now()*/ {
|
||||
eids, err := Likes.BulkExists(likedQueryList, user.ID, "replies")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
for _, eid := range eids {
|
||||
rlist[likedMap[eid]].Liked = true
|
||||
|
@ -742,7 +1000,7 @@ func (t *TopicUser) Replies(offset, pFrag int, user *User) (rlist []*ReplyUser,
|
|||
//log.Printf("attachQueryList: %+v\n", attachQueryList)
|
||||
amap, err := Attachments.BulkMiniGetList("replies", attachQueryList)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
//log.Printf("amap: %+v\n", amap)
|
||||
//log.Printf("attachMap: %+v\n", attachMap)
|
||||
|
@ -755,7 +1013,9 @@ func (t *TopicUser) Replies(offset, pFrag int, user *User) (rlist []*ReplyUser,
|
|||
}
|
||||
}
|
||||
|
||||
return rlist, ogdesc, nil
|
||||
//hTbl.VhookNoRet("topic_reply_end", &rlist)
|
||||
|
||||
return rlist, nil
|
||||
}
|
||||
|
||||
// TODO: Test this
|
||||
|
@ -875,6 +1135,20 @@ func BuildTopicURL(slug string, tid int) string {
|
|||
return "/topic/" + slug + "." + strconv.Itoa(tid)
|
||||
}
|
||||
|
||||
func BuildTopicURLSb(sb *strings.Builder, slug string, tid int) {
|
||||
if slug == "" || !Config.BuildSlugs {
|
||||
sb.Grow(7 + 2)
|
||||
sb.WriteString("/topic/")
|
||||
sb.WriteString(strconv.Itoa(tid))
|
||||
return
|
||||
}
|
||||
sb.Grow(7 + 3 + len(slug))
|
||||
sb.WriteString("/topic/")
|
||||
sb.WriteString(slug)
|
||||
sb.WriteRune('.')
|
||||
sb.WriteString(strconv.Itoa(tid))
|
||||
}
|
||||
|
||||
// I don't care if it isn't used,, it will likely be in the future. Nolint.
|
||||
// nolint
|
||||
func getTopicURLPrefix() string {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
type UserCache interface {
|
||||
DeallocOverflow(evictPriority bool) (evicted int) // May cause thread contention, looks for items to evict
|
||||
Get(id int) (*User, error)
|
||||
Getn(id int) *User
|
||||
GetUnsafe(id int) (*User, error)
|
||||
BulkGet(ids []int) (list []*User)
|
||||
Set(item *User) error
|
||||
|
@ -95,12 +96,19 @@ func (s *MemoryUserCache) DeallocOverflow(evictPriority bool) (evicted int) {
|
|||
// Get fetches a user by ID. Returns ErrNoRows if not present.
|
||||
func (s *MemoryUserCache) Get(id int) (*User, error) {
|
||||
s.RLock()
|
||||
item, ok := s.items[id]
|
||||
item := s.items[id]
|
||||
s.RUnlock()
|
||||
if ok {
|
||||
return item, nil
|
||||
}
|
||||
if item == nil {
|
||||
return item, ErrNoRows
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *MemoryUserCache) Getn(id int) *User {
|
||||
s.RLock()
|
||||
item := s.items[id]
|
||||
s.RUnlock()
|
||||
return item
|
||||
}
|
||||
|
||||
// BulkGet fetches multiple users by their IDs. Indices without users will be set to nil, so make sure you check for those, we might want to change this behaviour to make it less confusing.
|
||||
|
|
|
@ -18,6 +18,7 @@ var ErrLongUsername = errors.New("this username is too long")
|
|||
type UserStore interface {
|
||||
DirtyGet(id int) *User
|
||||
Get(id int) (*User, error)
|
||||
Getn(id int) *User
|
||||
GetByName(name string) (*User, error)
|
||||
Exists(id int) bool
|
||||
GetOffset(offset, perPage int) ([]*User, error)
|
||||
|
@ -93,6 +94,15 @@ func (s *DefaultUserStore) Get(id int) (*User, error) {
|
|||
u = &User{ID: id, Loggedin: true}
|
||||
var embeds int
|
||||
err = s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &u.CreatedAt, &embeds)
|
||||
/*if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if embeds != -1 {
|
||||
u.ParseSettings = DefaultParseSettings.CopyPtr()
|
||||
u.ParseSettings.NoEmbed = embeds == 0
|
||||
}
|
||||
u.Init()
|
||||
s.cache.Set(u)*/
|
||||
if err == nil {
|
||||
if embeds != -1 {
|
||||
u.ParseSettings = DefaultParseSettings.CopyPtr()
|
||||
|
@ -104,21 +114,43 @@ func (s *DefaultUserStore) Get(id int) (*User, error) {
|
|||
return u, err
|
||||
}
|
||||
|
||||
// TODO: Log weird cache errors? Not just here but in every *Cache?
|
||||
// ! This bypasses the cache, use frugally
|
||||
func (s *DefaultUserStore) GetByName(name string) (*User, error) {
|
||||
u := &User{Loggedin: true}
|
||||
func (s *DefaultUserStore) Getn(id int) *User {
|
||||
u := s.cache.Getn(id)
|
||||
if u != nil {
|
||||
return u
|
||||
}
|
||||
|
||||
u = &User{ID: id, Loggedin: true}
|
||||
var embeds int
|
||||
err := s.getByName.QueryRow(name).Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &u.CreatedAt, &embeds)
|
||||
if err == nil {
|
||||
err := s.get.QueryRow(id).Scan(&u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &u.CreatedAt, &embeds)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if embeds != -1 {
|
||||
u.ParseSettings = DefaultParseSettings.CopyPtr()
|
||||
u.ParseSettings.NoEmbed = embeds == 0
|
||||
}
|
||||
u.Init()
|
||||
s.cache.Set(u)
|
||||
return u
|
||||
}
|
||||
|
||||
// TODO: Log weird cache errors? Not just here but in every *Cache?
|
||||
// ! This bypasses the cache, use frugally
|
||||
func (s *DefaultUserStore) GetByName(name string) (*User, error) {
|
||||
u := &User{Loggedin: true}
|
||||
var embeds int
|
||||
err := s.getByName.QueryRow(name).Scan(&u.ID, &u.Name, &u.Group, &u.Active, &u.IsSuperAdmin, &u.Session, &u.Email, &u.RawAvatar, &u.Message, &u.Level, &u.Score, &u.Posts, &u.Liked, &u.LastIP, &u.TempGroup, &u.CreatedAt, &embeds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u, err
|
||||
if embeds != -1 {
|
||||
u.ParseSettings = DefaultParseSettings.CopyPtr()
|
||||
u.ParseSettings.NoEmbed = embeds == 0
|
||||
}
|
||||
u.Init()
|
||||
s.cache.Set(u)
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// TODO: Optimise this, so we don't wind up hitting the database every-time for small gaps
|
||||
|
|
|
@ -27,7 +27,7 @@ func init() {
|
|||
}
|
||||
|
||||
// TODO: Remove the View part of the name?
|
||||
func ViewProfile(w http.ResponseWriter, r *http.Request, user *c.User, header *c.Header) c.RouteError {
|
||||
func ViewProfile(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header) c.RouteError {
|
||||
var reCreatedAt time.Time
|
||||
var reContent, reCreatedByName, reAvatar string
|
||||
var rid, reCreatedBy, reLastEdit, reLastEditBy, reGroup int
|
||||
|
@ -36,13 +36,13 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user *c.User, header *c
|
|||
// TODO: Do a 301 if it's the wrong username? Do a canonical too?
|
||||
_, pid, err := ParseSEOURL(r.URL.Path[len("/user/"):])
|
||||
if err != nil {
|
||||
return c.SimpleError(phrases.GetErrorPhrase("url_id_must_be_integer"), w, r, header)
|
||||
return c.SimpleError(phrases.GetErrorPhrase("url_id_must_be_integer"), w, r, h)
|
||||
}
|
||||
|
||||
// TODO: Preload this?
|
||||
header.AddSheet(header.Theme.Name + "/profile.css")
|
||||
h.AddSheet(h.Theme.Name + "/profile.css")
|
||||
if user.Loggedin {
|
||||
header.AddScriptAsync("profile_member.js")
|
||||
h.AddScriptAsync("profile_member.js")
|
||||
}
|
||||
|
||||
var puser *c.User
|
||||
|
@ -54,13 +54,13 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user *c.User, header *c
|
|||
// TODO: Add a shared function for checking for ErrNoRows and internal erroring if it's not that case?
|
||||
puser, err = c.Users.Get(pid)
|
||||
if err == sql.ErrNoRows {
|
||||
return c.NotFound(w, r, header)
|
||||
return c.NotFound(w, r, h)
|
||||
} else if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
}
|
||||
header.Title = phrases.GetTitlePhrasef("profile", puser.Name)
|
||||
header.Path = c.BuildProfileURL(c.NameToSlug(puser.Name), puser.ID)
|
||||
h.Title = phrases.GetTitlePhrasef("profile", puser.Name)
|
||||
h.Path = c.BuildProfileURL(c.NameToSlug(puser.Name), puser.ID)
|
||||
|
||||
// Get the replies..
|
||||
rows, err := profileStmts.getReplies.Query(puser.ID)
|
||||
|
@ -77,8 +77,8 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user *c.User, header *c
|
|||
|
||||
reLiked := false
|
||||
reLikeCount := 0
|
||||
ru := &c.ReplyUser{Reply: c.Reply{rid, puser.ID, reContent, reCreatedBy, reGroup, reCreatedAt, reLastEdit, reLastEditBy, 0, "", reLiked, reLikeCount, 0, ""}, ContentHtml: c.ParseMessage(reContent, 0, "", user.ParseSettings, user), CreatedByName: reCreatedByName, Avatar: reAvatar, Level: 0}
|
||||
_, err = ru.Init()
|
||||
ru := &c.ReplyUser{Reply: c.Reply{rid, puser.ID, reContent, reCreatedBy /*, reGroup*/, reCreatedAt, reLastEdit, reLastEditBy, 0, "", reLiked, reLikeCount, 0, ""}, ContentHtml: c.ParseMessage(reContent, 0, "", user.ParseSettings, user), CreatedByName: reCreatedByName, Avatar: reAvatar, Group: reGroup, Level: 0}
|
||||
_, err = ru.Init(user)
|
||||
if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
|
@ -111,6 +111,6 @@ func ViewProfile(w http.ResponseWriter, r *http.Request, user *c.User, header *c
|
|||
canMessage := (!blockedInv && user.Perms.UseConvos) || (!blockedInv && puser.IsSuperMod && user.Perms.UseConvosOnlyWithMod) || user.IsSuperMod
|
||||
canComment := !blockedInv && user.Perms.CreateProfileReply
|
||||
|
||||
ppage := c.ProfilePage{header, reList, *puser, currentScore, nextScore, blocked, canMessage, canComment}
|
||||
return renderTemplate("profile", w, r, header, ppage)
|
||||
ppage := c.ProfilePage{h, reList, *puser, currentScore, nextScore, blocked, canMessage, canComment}
|
||||
return renderTemplate("profile", w, r, h, ppage)
|
||||
}
|
||||
|
|
|
@ -209,7 +209,7 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.R
|
|||
|
||||
// TODO: Disable stat updates in posts handled by plugin_guilds
|
||||
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
|
||||
func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid string) c.RouteError {
|
||||
func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, u *c.User, srid string) c.RouteError {
|
||||
js := r.PostFormValue("js") == "1"
|
||||
rid, err := strconv.Atoi(srid)
|
||||
if err != nil {
|
||||
|
@ -231,15 +231,15 @@ func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid
|
|||
}
|
||||
|
||||
// TODO: Add hooks to make use of headerLite
|
||||
lite, ferr := c.SimpleForumUserCheck(w, r, user, topic.ParentID)
|
||||
lite, ferr := c.SimpleForumUserCheck(w, r, u, topic.ParentID)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ViewTopic || !user.Perms.EditReply {
|
||||
return c.NoPermissionsJSQ(w, r, user, js)
|
||||
if !u.Perms.ViewTopic || !u.Perms.EditReply {
|
||||
return c.NoPermissionsJSQ(w, r, u, js)
|
||||
}
|
||||
if topic.IsClosed && !user.Perms.CloseTopic {
|
||||
return c.NoPermissionsJSQ(w, r, user, js)
|
||||
if topic.IsClosed && !u.Perms.CloseTopic {
|
||||
return c.NoPermissionsJSQ(w, r, u, js)
|
||||
}
|
||||
|
||||
err = reply.SetPost(r.PostFormValue("edit_item"))
|
||||
|
@ -257,7 +257,7 @@ func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid
|
|||
return c.InternalErrorJSQ(err, w, r, js)
|
||||
}
|
||||
|
||||
skip, rerr := lite.Hooks.VhookSkippable("action_end_edit_reply", reply.ID, user)
|
||||
skip, rerr := lite.Hooks.VhookSkippable("action_end_edit_reply", reply.ID, u)
|
||||
if skip || rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
|
@ -265,7 +265,7 @@ func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid
|
|||
if !js {
|
||||
http.Redirect(w, r, "/topic/"+strconv.Itoa(topic.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther)
|
||||
} else {
|
||||
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(reply.Content, topic.ParentID, "forums", user.ParseSettings, user)})
|
||||
outBytes, err := json.Marshal(JsonReply{c.ParseMessage(reply.Content, topic.ParentID, "forums", u.ParseSettings, u)})
|
||||
if err != nil {
|
||||
return c.InternalErrorJSQ(err, w, r, js)
|
||||
}
|
||||
|
@ -277,7 +277,7 @@ func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid
|
|||
|
||||
// TODO: Refactor this
|
||||
// TODO: Disable stat updates in posts handled by plugin_guilds
|
||||
func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid string) c.RouteError {
|
||||
func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, u *c.User, srid string) c.RouteError {
|
||||
js := r.PostFormValue("js") == "1"
|
||||
rid, err := strconv.Atoi(srid)
|
||||
if err != nil {
|
||||
|
@ -298,20 +298,20 @@ func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sri
|
|||
}
|
||||
|
||||
// TODO: Add hooks to make use of headerLite
|
||||
lite, ferr := c.SimpleForumUserCheck(w, r, user, topic.ParentID)
|
||||
lite, ferr := c.SimpleForumUserCheck(w, r, u, topic.ParentID)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if reply.CreatedBy != user.ID {
|
||||
if !user.Perms.ViewTopic || !user.Perms.DeleteReply {
|
||||
return c.NoPermissionsJSQ(w, r, user, js)
|
||||
if reply.CreatedBy != u.ID {
|
||||
if !u.Perms.ViewTopic || !u.Perms.DeleteReply {
|
||||
return c.NoPermissionsJSQ(w, r, u, js)
|
||||
}
|
||||
}
|
||||
if err := reply.Delete(); err != nil {
|
||||
return c.InternalErrorJSQ(err, w, r, js)
|
||||
}
|
||||
|
||||
skip, rerr := lite.Hooks.VhookSkippable("action_end_delete_reply", reply.ID, user)
|
||||
skip, rerr := lite.Hooks.VhookSkippable("action_end_delete_reply", reply.ID, u)
|
||||
if skip || rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
|
@ -334,7 +334,7 @@ func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sri
|
|||
return c.InternalErrorJSQ(err, w, r, js)
|
||||
}*/
|
||||
|
||||
err = c.ModLogs.Create("delete", reply.ParentID, "reply", user.GetIP(), user.ID)
|
||||
err = c.ModLogs.Create("delete", reply.ParentID, "reply", u.GetIP(), u.ID)
|
||||
if err != nil {
|
||||
return c.InternalErrorJSQ(err, w, r, js)
|
||||
}
|
||||
|
@ -455,7 +455,7 @@ func RemoveAttachFromReplySubmit(w http.ResponseWriter, r *http.Request, u *c.Us
|
|||
return nil
|
||||
}
|
||||
|
||||
func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid string) c.RouteError {
|
||||
func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, u *c.User, srid string) c.RouteError {
|
||||
js := r.PostFormValue("js") == "1"
|
||||
rid, err := strconv.Atoi(srid)
|
||||
if err != nil {
|
||||
|
@ -477,39 +477,39 @@ func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid
|
|||
}
|
||||
|
||||
// TODO: Add hooks to make use of headerLite
|
||||
lite, ferr := c.SimpleForumUserCheck(w, r, user, topic.ParentID)
|
||||
lite, ferr := c.SimpleForumUserCheck(w, r, u, topic.ParentID)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
|
||||
return c.NoPermissionsJSQ(w, r, user, js)
|
||||
if !u.Perms.ViewTopic || !u.Perms.LikeItem {
|
||||
return c.NoPermissionsJSQ(w, r, u, js)
|
||||
}
|
||||
if reply.CreatedBy == user.ID {
|
||||
return c.LocalErrorJSQ("You can't like your own replies", w, r, user, js)
|
||||
if reply.CreatedBy == u.ID {
|
||||
return c.LocalErrorJSQ("You can't like your own replies", w, r, u, js)
|
||||
}
|
||||
|
||||
_, err = c.Users.Get(reply.CreatedBy)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return c.LocalErrorJSQ("The target user doesn't exist", w, r, user, js)
|
||||
return c.LocalErrorJSQ("The target user doesn't exist", w, r, u, js)
|
||||
} else if err != nil {
|
||||
return c.InternalErrorJSQ(err, w, r, js)
|
||||
}
|
||||
|
||||
err = reply.Like(user.ID)
|
||||
err = reply.Like(u.ID)
|
||||
if err == c.ErrAlreadyLiked {
|
||||
return c.LocalErrorJSQ("You've already liked this!", w, r, user, js)
|
||||
return c.LocalErrorJSQ("You've already liked this!", w, r, u, js)
|
||||
} else if err != nil {
|
||||
return c.InternalErrorJSQ(err, w, r, js)
|
||||
}
|
||||
|
||||
// ! Be careful about leaking per-route permission state with user ptr
|
||||
alert := c.Alert{ActorID: user.ID, TargetUserID: reply.CreatedBy, Event: "like", ElementType: "post", ElementID: rid, Actor: user}
|
||||
alert := c.Alert{ActorID: u.ID, TargetUserID: reply.CreatedBy, Event: "like", ElementType: "post", ElementID: rid, Actor: u}
|
||||
err = c.AddActivityAndNotifyTarget(alert)
|
||||
if err != nil {
|
||||
return c.InternalErrorJSQ(err, w, r, js)
|
||||
}
|
||||
|
||||
skip, rerr := lite.Hooks.VhookSkippable("action_end_like_reply", reply.ID, user)
|
||||
skip, rerr := lite.Hooks.VhookSkippable("action_end_like_reply", reply.ID, u)
|
||||
if skip || rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
|
@ -522,7 +522,7 @@ func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid
|
|||
return nil
|
||||
}
|
||||
|
||||
func ReplyUnlikeSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid string) c.RouteError {
|
||||
func ReplyUnlikeSubmit(w http.ResponseWriter, r *http.Request, u *c.User, srid string) c.RouteError {
|
||||
js := r.PostFormValue("js") == "1"
|
||||
rid, err := strconv.Atoi(srid)
|
||||
if err != nil {
|
||||
|
@ -544,22 +544,22 @@ func ReplyUnlikeSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sri
|
|||
}
|
||||
|
||||
// TODO: Add hooks to make use of headerLite
|
||||
lite, ferr := c.SimpleForumUserCheck(w, r, user, topic.ParentID)
|
||||
lite, ferr := c.SimpleForumUserCheck(w, r, u, topic.ParentID)
|
||||
if ferr != nil {
|
||||
return ferr
|
||||
}
|
||||
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
|
||||
return c.NoPermissionsJSQ(w, r, user, js)
|
||||
if !u.Perms.ViewTopic || !u.Perms.LikeItem {
|
||||
return c.NoPermissionsJSQ(w, r, u, js)
|
||||
}
|
||||
|
||||
_, err = c.Users.Get(reply.CreatedBy)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return c.LocalErrorJSQ("The target user doesn't exist", w, r, user, js)
|
||||
return c.LocalErrorJSQ("The target user doesn't exist", w, r, u, js)
|
||||
} else if err != nil {
|
||||
return c.InternalErrorJSQ(err, w, r, js)
|
||||
}
|
||||
|
||||
err = reply.Unlike(user.ID)
|
||||
err = reply.Unlike(u.ID)
|
||||
if err != nil {
|
||||
return c.InternalErrorJSQ(err, w, r, js)
|
||||
}
|
||||
|
@ -577,7 +577,7 @@ func ReplyUnlikeSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sri
|
|||
return c.InternalErrorJSQ(err, w, r, js)
|
||||
}
|
||||
|
||||
skip, rerr := lite.Hooks.VhookSkippable("action_end_unlike_reply", reply.ID, user)
|
||||
skip, rerr := lite.Hooks.VhookSkippable("action_end_unlike_reply", reply.ID, u)
|
||||
if skip || rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
|
|
|
@ -109,14 +109,15 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user *c.User, header *c.H
|
|||
return c.InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var poll c.Poll
|
||||
var poll *c.Poll
|
||||
if topic.Poll != 0 {
|
||||
pPoll, err := c.Polls.Get(topic.Poll)
|
||||
if err != nil {
|
||||
log.Print("Couldn't find the attached poll for topic " + strconv.Itoa(topic.ID))
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
poll = pPoll.Copy()
|
||||
poll = new(c.Poll)
|
||||
*poll = pPoll.Copy()
|
||||
}
|
||||
|
||||
if topic.LikeCount > 0 && user.Liked > 0 {
|
||||
|
@ -149,13 +150,12 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user *c.User, header *c.H
|
|||
if strings.HasPrefix(r.URL.Fragment, "post-") {
|
||||
pFrag, _ = strconv.Atoi(strings.TrimPrefix(r.URL.Fragment, "post-"))
|
||||
}
|
||||
rlist, ogdesc, err := topic.Replies(offset, pFrag, user)
|
||||
rlist, err := topic.Replies(offset, pFrag, user)
|
||||
if err == sql.ErrNoRows {
|
||||
return c.LocalError("Bad Page. Some of the posts may have been deleted or you got here by directly typing in the page number.", w, r, user)
|
||||
} else if err != nil {
|
||||
return c.InternalError(err, w, r)
|
||||
}
|
||||
header.OGDesc = ogdesc
|
||||
tpage.ItemList = rlist
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user *c.User, header *c.H
|
|||
var rerr c.RouteError
|
||||
tmpl := forum.Tmpl
|
||||
if r.FormValue("i") == "1" {
|
||||
if tpage.Poll.ID != 0 {
|
||||
if tpage.Poll != nil {
|
||||
header.AddXRes("chartist/chartist.min.css", "chartist/chartist.min.js")
|
||||
}
|
||||
if tmpl == "" {
|
||||
|
@ -179,7 +179,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user *c.User, header *c.H
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if tpage.Poll.ID != 0 {
|
||||
if tpage.Poll != nil {
|
||||
header.AddSheet("chartist/chartist.min.css")
|
||||
header.AddScript("chartist/chartist.min.js")
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<nav class="colstack_left" aria-label="{{lang "panel_menu_aria"}}">
|
||||
<nav class="colstack_left"aria-label="{{lang "panel_menu_aria"}}">
|
||||
<div class="colstack_item colstack_head">
|
||||
<div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_head"}}</a></div>
|
||||
</div>
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
<div id="panel_users" class="colstack_item rowlist bgavatars">
|
||||
{{range .ItemList}}
|
||||
<div class="rowitem" style="background-image:url('{{.Avatar}}');">
|
||||
<a class="rowAvatar"{{if $.CurrentUser.Perms.EditUser}} href="/panel/users/edit/{{.ID}}"{{end}}>
|
||||
<a class="rowAvatar"{{if $.CurrentUser.Perms.EditUser}}href="/panel/users/edit/{{.ID}}"{{end}}>
|
||||
<img class="bgsub"src="{{.Avatar}}"alt="Avatar"aria-hidden="true">
|
||||
</a>
|
||||
<a class="rowTitle"{{if $.CurrentUser.Perms.EditUser}} href="/panel/users/edit/{{.ID}}"{{end}}>{{.Name}}</a>
|
||||
<a class="rowTitle"{{if $.CurrentUser.Perms.EditUser}}href="/panel/users/edit/{{.ID}}"{{end}}>{{.Name}}</a>
|
||||
<span class="panel_floater">
|
||||
<a href="{{.Link}}" class="tag-mini profile_url">{{lang "panel_users_profile"}}</a>
|
||||
{{if (.Tag) and (.IsSuperMod)}}<span class="panel_tag">{{.Tag}}</span></span>{{end}}
|
||||
|
|
|
@ -26,32 +26,7 @@
|
|||
</div>
|
||||
|
||||
<div class="rowblock post_container">
|
||||
{{if .Poll.ID}}<form id="poll_{{.Poll.ID}}_form" action="/poll/vote/{{.Poll.ID}}?s={{.CurrentUser.Session}}" method="post"></form>
|
||||
<article class="rowitem passive deletable_block editable_parent post_item poll_item top_post hide_on_edit">
|
||||
{{/**{{template "topic_alt_userinfo.html" .Topic }}**/}}
|
||||
<div id="poll_voter_{{.Poll.ID}}" class="content_container poll_voter">
|
||||
<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>
|
||||
<div id="poll_results_{{.Poll.ID}}" class="content_container 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>
|
||||
</article>
|
||||
{{end}}
|
||||
{{if .Poll}}{{template "topic_alt_poll.html" . }}{{end}}
|
||||
<article {{scope "opening_post"}} itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item top_post{{if .Topic.Attachments}} has_attachs{{end}}" aria-label="{{lang "topic.opening_post_aria"}}">
|
||||
{{template "topic_alt_userinfo.html" .Topic }}
|
||||
<div class="content_container">
|
||||
|
@ -59,11 +34,11 @@
|
|||
{{if .CurrentUser.Loggedin}}<textarea name="topic_content" class="show_on_edit topic_content_input edit_source">{{.Topic.Content}}</textarea>
|
||||
|
||||
{{if .CurrentUser.Perms.EditTopic}}
|
||||
<div class="show_on_edit attach_edit_bay" type="topic" id="{{.Topic.ID}}">
|
||||
<div class="show_on_edit attach_edit_bay"type="topic"id="{{.Topic.ID}}">
|
||||
{{range .Topic.Attachments}}
|
||||
<div class="attach_item attach_item_item{{if .Image}} attach_image_holder{{end}}">
|
||||
{{if .Image}}<img src="//{{$.Header.Site.URL}}/attachs/{{.Path}}?sid={{.SectionID}}&stype=forums" height=24 width=24>{{end}}
|
||||
<span class="attach_item_path" aid="{{.ID}}" fullPath="//{{$.Header.Site.URL}}/attachs/{{.Path}}">{{.Path}}</span>
|
||||
{{if .Image}}<img src="//{{$.Header.Site.URL}}/attachs/{{.Path}}?sid={{.SectionID}}&stype=forums"height=24 width=24>{{end}}
|
||||
<span class="attach_item_path"aid="{{.ID}}"fullPath="//{{$.Header.Site.URL}}/attachs/{{.Path}}">{{.Path}}</span>
|
||||
<button class="attach_item_select">{{lang "topic.select_button_text"}}</button>
|
||||
<button class="attach_item_copy">{{lang "topic.copy_button_text"}}</button>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<form id="poll_{{.Poll.ID}}_form" action="/poll/vote/{{.Poll.ID}}?s={{.CurrentUser.Session}}"method="post"></form>
|
||||
<article class="rowitem passive deletable_block editable_parent post_item poll_item top_post hide_on_edit">
|
||||
{{/**{{template "topic_alt_userinfo.html" .Topic }}**/}}
|
||||
<div id="poll_voter_{{.Poll.ID}}" class="content_container poll_voter">
|
||||
<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>
|
||||
<div id="poll_results_{{.Poll.ID}}" class="content_container 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>
|
||||
</article>
|
|
@ -1,8 +1,8 @@
|
|||
{{range .ItemList}}<article {{scope "post"}} id="post-{{.ID}}" itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item{{if .ActionType}} action_item{{end}}{{if .Attachments}} has_attachs{{end}}">
|
||||
{{range .ItemList}}<article {{scope "post"}} id="post-{{.ID}}"itemscope itemtype="http://schema.org/CreativeWork"class="rowitem passive deletable_block editable_parent post_item{{if .ActionType}} action_item{{end}}{{if .Attachments}} has_attachs{{end}}">
|
||||
{{if js}}js{{/**{{ptmpl "topic_alt_userinfo" .}}**/}}{{else}}{{template "topic_alt_userinfo.html" . }}{{end}}
|
||||
<div class="content_container">
|
||||
{{if .ActionType}}
|
||||
<span class="action_icon" aria-hidden="true">{{.ActionIcon}}</span><span itemprop="text">{{.ActionType}}</span>
|
||||
<span class="action_icon"aria-hidden="true">{{.ActionIcon}}</span><span itemprop="text">{{.ActionType}}</span>
|
||||
{{else}}
|
||||
<div class="editable_block user_content"itemprop="text">{{.ContentHtml}}</div>
|
||||
{{if $.CurrentUser.Loggedin}}
|
||||
|
@ -13,15 +13,15 @@
|
|||
{{range .Attachments}}
|
||||
<div class="attach_item attach_item_item{{if .Image}} attach_image_holder{{end}}">
|
||||
{{if .Image}}<img src="//{{$.Header.Site.URL}}/attachs/{{.Path}}?sid={{.SectionID}}&stype=forums"height=24 width=24>{{end}}
|
||||
<span class="attach_item_path" aid={{.ID}} fullPath="//{{$.Header.Site.URL}}/attachs/{{.Path}}">{{.Path}}</span>
|
||||
<span class="attach_item_path"aid={{.ID}} fullPath="//{{$.Header.Site.URL}}/attachs/{{.Path}}">{{.Path}}</span>
|
||||
<button class="attach_item_select">{{lang "topic.select_button_text"}}</button>
|
||||
<button class="attach_item_copy">{{lang "topic.copy_button_text"}}</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="attach_item attach_item_buttons">
|
||||
{{if $.CurrentUser.Perms.UploadFiles}}
|
||||
<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}}
|
||||
<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}}
|
||||
<button class="attach_item_delete formbutton">{{lang "topic.delete_button_text"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,23 +31,23 @@
|
|||
<div class="action_button_left">
|
||||
{{if $.CurrentUser.Loggedin}}
|
||||
{{if $.CurrentUser.Perms.LikeItem}}{{if ne $.CurrentUser.ID .CreatedBy}}
|
||||
{{if .Liked}}<a href="/reply/unlike/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="action_button like_item remove_like" aria-label="{{lang "topic.post_unlike_aria"}}" data-action="unlike"></a>{{else}}
|
||||
<a href="/reply/like/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="action_button like_item add_like" aria-label="{{lang "topic.post_like_aria"}}" data-action="like"></a>{{end}}
|
||||
{{if .Liked}}<a href="/reply/unlike/submit/{{.ID}}?s={{$.CurrentUser.Session}}"class="action_button like_item remove_like"aria-label="{{lang "topic.post_unlike_aria"}}"data-action="unlike"></a>{{else}}
|
||||
<a href="/reply/like/submit/{{.ID}}?s={{$.CurrentUser.Session}}"class="action_button like_item add_like"aria-label="{{lang "topic.post_like_aria"}}"data-action="like"></a>{{end}}
|
||||
{{end}}{{end}}
|
||||
<a href="" class="action_button quote_item" aria-label="{{lang "topic.quote_aria"}}" data-action="quote"></a>
|
||||
<a href=""class="action_button quote_item"aria-label="{{lang "topic.quote_aria"}}"data-action="quote"></a>
|
||||
{{if not $.Topic.IsClosed or $.CurrentUser.Perms.CloseTopic}}
|
||||
{{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="action_button edit_item" aria-label="{{lang "topic.post_edit_aria"}}" data-action="edit"></a>{{end}}
|
||||
{{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}"class="action_button edit_item"aria-label="{{lang "topic.post_edit_aria"}}"data-action="edit"></a>{{end}}
|
||||
{{end}}
|
||||
{{if .Deletable}}<a href="/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="action_button delete_item" aria-label="{{lang "topic.post_delete_aria"}}" data-action="delete"></a>{{end}}
|
||||
{{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IP}}" title="{{lang "topic.ip_full_tooltip"}}" class="action_button ip_item_button hide_on_big" aria-label="{{lang "topic.ip_full_aria"}}" data-action="ip"></a>{{end}}
|
||||
<a href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=reply" class="action_button report_item" aria-label="{{lang "topic.report_aria"}}" data-action="report"></a>
|
||||
<a href="#" class="action_button button_menu"></a>
|
||||
{{if .Deletable}}<a href="/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}"class="action_button delete_item"aria-label="{{lang "topic.post_delete_aria"}}"data-action="delete"></a>{{end}}
|
||||
{{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IP}}"title="{{lang "topic.ip_full_tooltip"}}"class="action_button ip_item_button hide_on_big"aria-label="{{lang "topic.ip_full_aria"}}"data-action="ip"></a>{{end}}
|
||||
<a href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=reply"class="action_button report_item"aria-label="{{lang "topic.report_aria"}}"data-action="report"></a>
|
||||
<a href="#"class="action_button button_menu"></a>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="action_button_right">
|
||||
<a class="action_button like_count hide_on_micro" aria-label="{{lang "topic.post_like_count_tooltip"}}">{{.LikeCount}}</a>
|
||||
<a class="action_button created_at hide_on_mobile" title="{{abstime .CreatedAt}}">{{reltime .CreatedAt}}</a>
|
||||
{{if $.CurrentUser.Loggedin}}{{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IP}}"title="IP Address" class="action_button ip_item hide_on_mobile" aria-hidden="true">{{.IP}}</a>{{end}}{{end}}
|
||||
<a class="action_button like_count hide_on_micro"aria-label="{{lang "topic.post_like_count_tooltip"}}">{{.LikeCount}}</a>
|
||||
<a class="action_button created_at hide_on_mobile"title="{{abstime .CreatedAt}}">{{reltime .CreatedAt}}</a>
|
||||
{{if $.CurrentUser.Loggedin}}{{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IP}}"title="IP Address"class="action_button ip_item hide_on_mobile"aria-hidden="true">{{.IP}}</a>{{end}}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
@ -17,17 +17,17 @@
|
|||
{{if not .Topic.IsClosed or .CurrentUser.Perms.CloseTopic}}
|
||||
{{if .CurrentUser.Perms.EditTopic}}
|
||||
<form id="edit_topic_form" action='/topic/edit/submit/{{.Topic.ID}}?s={{.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>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{if .Poll.ID}}{{template "topic_poll.html" . }}{{end}}
|
||||
{{if .Poll}}{{template "topic_poll.html" . }}{{end}}
|
||||
|
||||
<article {{scope "opening_post"}} itemscope itemtype="http://schema.org/CreativeWork" class="rowblock post_container top_post" aria-label="{{lang "topic.opening_post_aria"}}">
|
||||
<div class="rowitem passive editable_parent post_item {{.Topic.ClassName}}" style="background-image:url({{.Topic.Avatar}}),url(/s/{{.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="hide_on_edit topic_content user_content" itemprop="text">{{.Topic.ContentHTML}}</div>
|
||||
<div class="hide_on_edit topic_content user_content"itemprop="text">{{.Topic.ContentHTML}}</div>
|
||||
{{if .CurrentUser.Loggedin}}<textarea name="topic_content" class="show_on_edit topic_content_input edit_source">{{.Topic.Content}}</textarea>{{end}}
|
||||
|
||||
<span class="controls{{if .Topic.LikeCount}} has_likes{{end}}" aria-label="{{lang "topic.post_controls_aria"}}">
|
||||
|
@ -75,8 +75,8 @@
|
|||
{{if not .Topic.IsClosed or .CurrentUser.Perms.CloseTopic}}
|
||||
<div class="rowblock topic_reply_form quick_create_form" aria-label="{{lang "topic.reply_aria"}}">
|
||||
<form id="quick_post_form" enctype="multipart/form-data" action="/reply/create/?s={{.CurrentUser.Session}}" method="post"></form>
|
||||
<input form="quick_post_form" name="tid" value='{{.Topic.ID}}' type="hidden"/>
|
||||
<input form="quick_post_form" id="has_poll_input" name="has_poll" value=0 type="hidden"/>
|
||||
<input form="quick_post_form" name="tid" value='{{.Topic.ID}}' type="hidden">
|
||||
<input form="quick_post_form" id="has_poll_input" name="has_poll" value=0 type="hidden">
|
||||
<div class="formrow real_first_child">
|
||||
<div class="formitem">
|
||||
<textarea id="input_content" form="quick_post_form" name="content" placeholder="{{lang "topic.reply_content"}}" required></textarea>
|
||||
|
@ -85,9 +85,9 @@
|
|||
<div class="formrow poll_content_row auto_hide">
|
||||
<div class="formitem">
|
||||
<div class="pollinput" data-pollinput=0>
|
||||
<input type="checkbox" disabled />
|
||||
<input type="checkbox" disabled>
|
||||
<label class="pollinputlabel"></label>
|
||||
<input form="quick_post_form" name="pollinputitem[0]" class="pollinputinput" type="text" placeholder="{{lang "topic.reply_add_poll_option_first"}}"/>
|
||||
<input form="quick_post_form" name="pollinputitem[0]" class="pollinputinput" type="text" placeholder="{{lang "topic.reply_add_poll_option_first"}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -96,7 +96,7 @@
|
|||
<button form="quick_post_form" name="reply-button" class="formbutton">{{lang "topic.reply_button"}}</button>
|
||||
<button form="quick_post_form" class="formbutton" id="add_poll_button">{{lang "topic.reply_add_poll_button"}}</button>
|
||||
{{if .CurrentUser.Perms.UploadFiles}}
|
||||
<input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" style="display:none;" />
|
||||
<input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" class="auto_hide">
|
||||
<label for="upload_files" class="formbutton add_file_button">{{lang "topic.reply_add_file_button"}}</label>
|
||||
<div id="upload_file_dock"></div>{{end}}
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
<article class="rowblock post_container poll" aria-level="{{lang "topic.poll_aria"}}">
|
||||
<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(/s/{{.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}}">
|
||||
<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>
|
||||
<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>
|
||||
<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 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>
|
||||
|
|
Loading…
Reference in New Issue