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) {
|
func (c *NullUserCache) Get(id int) (*User, error) {
|
||||||
return nil, ErrNoRows
|
return nil, ErrNoRows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *NullUserCache) Getn(id int) *User {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func (c *NullUserCache) BulkGet(ids []int) (list []*User) {
|
func (c *NullUserCache) BulkGet(ids []int) (list []*User) {
|
||||||
return make([]*User, len(ids))
|
return make([]*User, len(ids))
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,7 +192,7 @@ type TopicPage struct {
|
||||||
ItemList []*ReplyUser
|
ItemList []*ReplyUser
|
||||||
Topic TopicUser
|
Topic TopicUser
|
||||||
Forum *Forum
|
Forum *Forum
|
||||||
Poll Poll
|
Poll *Poll
|
||||||
Paginator
|
Paginator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ type ReplyUser struct {
|
||||||
URL string
|
URL string
|
||||||
//URLPrefix string
|
//URLPrefix string
|
||||||
//URLName string
|
//URLName string
|
||||||
|
Group int
|
||||||
Level int
|
Level int
|
||||||
ActionIcon string
|
ActionIcon string
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ type Reply struct {
|
||||||
ParentID int
|
ParentID int
|
||||||
Content string
|
Content string
|
||||||
CreatedBy int
|
CreatedBy int
|
||||||
Group int
|
//Group int
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
LastEdit int
|
LastEdit int
|
||||||
LastEditBy int
|
LastEditBy int
|
||||||
|
@ -49,7 +50,7 @@ type Reply struct {
|
||||||
IP string
|
IP string
|
||||||
Liked bool
|
Liked bool
|
||||||
LikeCount int
|
LikeCount int
|
||||||
AttachCount int
|
AttachCount uint16
|
||||||
ActionType string
|
ActionType string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,6 @@ func (s *SQLReplyStore) Create(t *Topic, content, ip string, uid int) (id int, e
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lastID, err := res.LastInsertId()
|
lastID, err := res.LastInsertId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
|
@ -227,7 +227,7 @@ func compileCommons(c *tmpl.CTemplateSet, head, head2 *Header, forumList []Forum
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
var topicsList []*TopicsRow
|
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}}
|
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", "c.TopicListPage", topicListPage)
|
||||||
o.Add("topics_mini", "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}
|
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
|
var replyList []*ReplyUser
|
||||||
reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
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 := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
|
||||||
ru.Init()
|
ru.Init(user2)
|
||||||
replyList = append(replyList, ru)
|
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)
|
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
|
||||||
o.Add("topic", "c.TopicPage", tpage)
|
o.Add("topic", "c.TopicPage", tpage)
|
||||||
o.Add("topic_mini", "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}
|
//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?
|
// TODO: Do we want the UID on this to be 0?
|
||||||
//avatar, microAvatar = BuildAvatar(0, "")
|
//avatar, microAvatar = BuildAvatar(0, "")
|
||||||
reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
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 := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: "", Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
|
||||||
ru.Init()
|
ru.Init(user)
|
||||||
replyList = append(replyList, ru)
|
replyList = append(replyList, ru)
|
||||||
|
|
||||||
// TODO: Use a dummy forum list to avoid o(n) problems
|
// TODO: Use a dummy forum list to avoid o(n) problems
|
||||||
var forumList []Forum
|
/*var forumList []Forum
|
||||||
forums, err := Forums.GetAll()
|
forums, err := Forums.GetAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, forum := range forums {
|
for _, forum := range forums {
|
||||||
forumList = append(forumList, *forum)
|
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
|
// Convienience function to save a line here and there
|
||||||
htitle := func(name string) *Header {
|
htitle := func(name string) *Header {
|
||||||
|
@ -297,7 +301,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
|
||||||
return header
|
return header
|
||||||
}
|
}
|
||||||
t := TItemHold(make(map[string]TItem))
|
t := TItemHold(make(map[string]TItem))
|
||||||
err = compileCommons(c, header, header2, forumList, t)
|
err := compileCommons(c, header, header2, forumList, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -547,14 +551,14 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri
|
||||||
var replyList []*ReplyUser
|
var replyList []*ReplyUser
|
||||||
// TODO: Do we really want the UID here to be zero?
|
// TODO: Do we really want the UID here to be zero?
|
||||||
avatar, microAvatar = BuildAvatar(0, "")
|
avatar, microAvatar = BuildAvatar(0, "")
|
||||||
reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
|
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 := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Group: Config.DefaultGroup, Level: 0, Attachments: miniAttach}
|
||||||
ru.Init()
|
ru.Init(user)
|
||||||
replyList = append(replyList, ru)
|
replyList = append(replyList, ru)
|
||||||
|
|
||||||
varList = make(map[string]tmpl.VarItem)
|
varList = make(map[string]tmpl.VarItem)
|
||||||
header.Title = "Topic Name"
|
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)
|
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
|
||||||
t.AddStd("topic_posts", "c.TopicPage", tpage)
|
t.AddStd("topic_posts", "c.TopicPage", tpage)
|
||||||
t.AddStd("topic_alt_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()
|
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) {
|
func (con *CContext) StartLoop(body string) (index int) {
|
||||||
con.LoopDepth++
|
con.LoopDepth++
|
||||||
return con.Push("startloop", body)
|
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) {
|
func (con *CContext) AttachVars(vars string, index int) {
|
||||||
outBuf := *con.OutBuf
|
outBuf := *con.OutBuf
|
||||||
node := outBuf[index]
|
n := outBuf[index]
|
||||||
if node.Type != "starttemplate" && node.Type != "startloop" {
|
if n.Type != "starttemplate" && n.Type != "startloop" && n.Type != "startif" {
|
||||||
panic("not a starttemplate node")
|
panic("not a starttemplate, startloop or startif node")
|
||||||
}
|
}
|
||||||
node.Body += vars
|
n.Body += vars
|
||||||
outBuf[index] = node
|
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})
|
*con.OutBuf = append(*con.OutBuf, OutBufferFrame{body, ftype, con.TemplateName, extra1, extra2})
|
||||||
return con.LastBufIndex()
|
return con.LastBufIndex()
|
||||||
}
|
}
|
||||||
|
|
|
@ -513,8 +513,7 @@ if !ok {
|
||||||
}
|
}
|
||||||
`*/
|
`*/
|
||||||
fout += `var iw http.ResponseWriter
|
fout += `var iw http.ResponseWriter
|
||||||
gzw, ok := w.(c.GzipResponseWriter)
|
if gzw, ok := w.(c.GzipResponseWriter); ok {
|
||||||
if ok {
|
|
||||||
iw = gzw.ResponseWriter
|
iw = gzw.ResponseWriter
|
||||||
}
|
}
|
||||||
_ = iw
|
_ = iw
|
||||||
|
@ -711,14 +710,26 @@ func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
|
||||||
return
|
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)
|
c.compileSwitch(con, node.List)
|
||||||
if node.ElseList == nil {
|
if node.ElseList == nil {
|
||||||
c.detail("Selected Branch 1")
|
c.detail("Selected Branch 1")
|
||||||
con.Push("endif", "}\n")
|
con.EndIf(startIf, "}\n")
|
||||||
|
if nilIf {
|
||||||
|
c.afterTemplate(con, startIf)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
c.detail("Selected Branch 2")
|
c.detail("Selected Branch 2")
|
||||||
con.Push("endif", "}")
|
con.EndIf(startIf, "}")
|
||||||
|
if nilIf {
|
||||||
|
c.afterTemplate(con, startIf)
|
||||||
|
}
|
||||||
con.Push("startelse", " else {\n")
|
con.Push("startelse", " else {\n")
|
||||||
c.compileSwitch(con, node.ElseList)
|
c.compileSwitch(con, node.ElseList)
|
||||||
con.Push("endelse", "}\n")
|
con.Push("endelse", "}\n")
|
||||||
|
@ -769,7 +780,7 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
|
||||||
c.detail("Range Kind Switch!")
|
c.detail("Range Kind Switch!")
|
||||||
|
|
||||||
startIf := func(item reflect.Value, useCopy bool) {
|
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")
|
startIndex := con.StartLoop("for _, item := range " + expr + " {\n")
|
||||||
ccon := con
|
ccon := con
|
||||||
var depth string
|
var depth string
|
||||||
|
@ -793,7 +804,7 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
|
||||||
con.EndLoop("}\n")
|
con.EndLoop("}\n")
|
||||||
c.afterTemplate(con, startIndex)
|
c.afterTemplate(con, startIndex)
|
||||||
if node.ElseList != nil {
|
if node.ElseList != nil {
|
||||||
con.Push("endif", "}")
|
con.EndIf(sIndex, "}")
|
||||||
con.Push("startelse", " else {\n")
|
con.Push("startelse", " else {\n")
|
||||||
if !useCopy {
|
if !useCopy {
|
||||||
ccon = con
|
ccon = con
|
||||||
|
@ -801,7 +812,7 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
|
||||||
c.compileSwitch(ccon, node.ElseList)
|
c.compileSwitch(ccon, node.ElseList)
|
||||||
con.Push("endelse", "}\n")
|
con.Push("endelse", "}\n")
|
||||||
} else {
|
} else {
|
||||||
con.Push("endif", "}\n")
|
con.EndIf(sIndex, "}\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1518,6 +1529,9 @@ func (c *CTemplateSet) compileBoolSub(con CContext, varname string) string {
|
||||||
out += "!=\"\""
|
out += "!=\"\""
|
||||||
case reflect.Slice, reflect.Map:
|
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:
|
default:
|
||||||
c.logger.Println("Variable Name:", varname)
|
c.logger.Println("Variable Name:", varname)
|
||||||
c.logger.Println("Variable Holder:", con.VarHolder)
|
c.logger.Println("Variable Holder:", con.VarHolder)
|
||||||
|
@ -1675,9 +1689,9 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
con.Push("startif", "if "+varname+" {\n")
|
startIf := con.StartIf("if " + varname + " {\n")
|
||||||
c.addText(con, []byte("true"))
|
c.addText(con, []byte("true"))
|
||||||
con.Push("endif", "} ")
|
con.EndIf(startIf, "} ")
|
||||||
con.Push("startelse", "else {\n")
|
con.Push("startelse", "else {\n")
|
||||||
c.addText(con, []byte("false"))
|
c.addText(con, []byte("false"))
|
||||||
con.Push("endelse", "}\n")
|
con.Push("endelse", "}\n")
|
||||||
|
@ -1945,42 +1959,66 @@ func (c *CTemplateSet) loadTemplate(fileDir, name string) (content string, err e
|
||||||
return content, nil
|
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)
|
c.dumpCall("afterTemplate", con, startIndex)
|
||||||
defer c.retCall("afterTemplate")
|
defer c.retCall("afterTemplate")
|
||||||
|
|
||||||
loopDepth := 0
|
loopDepth := 0
|
||||||
|
ifNilDepth := 0
|
||||||
var outBuf = *con.OutBuf
|
var outBuf = *con.OutBuf
|
||||||
varcounts := make(map[string]int)
|
varcounts := make(map[string]int)
|
||||||
loopStart := startIndex
|
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++
|
loopStart++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exclude varsubs within loops for now
|
// Exclude varsubs within loops for now
|
||||||
|
OLoop:
|
||||||
for i := loopStart; i < len(outBuf); i++ {
|
for i := loopStart; i < len(outBuf); i++ {
|
||||||
item := outBuf[i]
|
item := outBuf[i]
|
||||||
c.detail("item:", item)
|
c.detail("item:", item)
|
||||||
if item.Type == "startloop" {
|
switch item.Type {
|
||||||
|
case "startloop":
|
||||||
loopDepth++
|
loopDepth++
|
||||||
c.detail("loopDepth:", loopDepth)
|
c.detail("loopDepth:", loopDepth)
|
||||||
} else if item.Type == "endloop" {
|
case "endloop":
|
||||||
loopDepth--
|
loopDepth--
|
||||||
c.detail("loopDepth:", loopDepth)
|
c.detail("loopDepth:", loopDepth)
|
||||||
if loopDepth == -1 {
|
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]
|
count := varcounts[item.Body]
|
||||||
varcounts[item.Body] = count + 1
|
varcounts[item.Body] = count + 1
|
||||||
c.detail("count " + strconv.Itoa(count) + " for " + item.Body)
|
c.detail("count " + strconv.Itoa(count) + " for " + item.Body)
|
||||||
c.detail("loopDepth:", loopDepth)
|
c.detail("loopDepth:", loopDepth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var varstr string
|
var varstr string
|
||||||
var i int
|
var i int
|
||||||
varmap := make(map[string]int)
|
varmap := make(map[string]int)
|
||||||
|
/*for svkey, sventry := range svmap {
|
||||||
|
varmap[svkey] = sventry
|
||||||
|
}*/
|
||||||
for name, count := range varcounts {
|
for name, count := range varcounts {
|
||||||
if count > 1 {
|
if count > 1 {
|
||||||
varstr += "var c_v_" + strconv.Itoa(i) + "=" + name + "\n"
|
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
|
// Exclude varsubs within loops for now
|
||||||
loopDepth = 0
|
loopDepth = 0
|
||||||
|
ifNilDepth = 0
|
||||||
|
OOLoop:
|
||||||
for i := loopStart; i < len(outBuf); i++ {
|
for i := loopStart; i < len(outBuf); i++ {
|
||||||
item := outBuf[i]
|
item := outBuf[i]
|
||||||
if item.Type == "startloop" {
|
switch item.Type {
|
||||||
|
case "startloop":
|
||||||
loopDepth++
|
loopDepth++
|
||||||
} else if item.Type == "endloop" {
|
case "endloop":
|
||||||
loopDepth--
|
loopDepth--
|
||||||
if loopDepth == -1 {
|
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]
|
index, ok := varmap[item.Body]
|
||||||
if ok {
|
if ok {
|
||||||
item.Body = "c_v_" + strconv.Itoa(index)
|
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)
|
con.AttachVars(varstr, startIndex)
|
||||||
}
|
}
|
||||||
|
|
366
common/topic.go
366
common/topic.go
|
@ -188,6 +188,8 @@ func (t *TopicsRow) Topic() *Topic {
|
||||||
type TopicStmts struct {
|
type TopicStmts struct {
|
||||||
getRids *sql.Stmt
|
getRids *sql.Stmt
|
||||||
getReplies *sql.Stmt
|
getReplies *sql.Stmt
|
||||||
|
getReplies2 *sql.Stmt
|
||||||
|
getReplies3 *sql.Stmt
|
||||||
addReplies *sql.Stmt
|
addReplies *sql.Stmt
|
||||||
updateLastReply *sql.Stmt
|
updateLastReply *sql.Stmt
|
||||||
lock *sql.Stmt
|
lock *sql.Stmt
|
||||||
|
@ -218,6 +220,8 @@ func init() {
|
||||||
topicStmts = TopicStmts{
|
topicStmts = TopicStmts{
|
||||||
getRids: acc.Select("replies").Columns("rid").Where("tid=?").Orderby("rid ASC").Limit("?,?").Prepare(),
|
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(),
|
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(),
|
updateLastReply: acc.Update(t).Set("lastReplyID=?").Where("lastReplyID > ? AND tid=?").Prepare(),
|
||||||
lock: acc.Update(t).Set("is_closed=1").Where("tid=?").Prepare(),
|
lock: acc.Update(t).Set("is_closed=1").Where("tid=?").Prepare(),
|
||||||
|
@ -570,8 +574,7 @@ var unlockai = "🔓"
|
||||||
var stickai = "📌"
|
var stickai = "📌"
|
||||||
var unstickai = "📌" + aipost
|
var unstickai = "📌" + aipost
|
||||||
|
|
||||||
func (ru *ReplyUser) Init() (group *Group, err error) {
|
func (ru *ReplyUser) Init(u *User) (group *Group, err error) {
|
||||||
ru.UserLink = BuildProfileURL(NameToSlug(ru.CreatedByName), ru.CreatedBy)
|
|
||||||
ru.ContentLines = strings.Count(ru.Content, "\n")
|
ru.ContentLines = strings.Count(ru.Content, "\n")
|
||||||
|
|
||||||
postGroup, err := Groups.Get(ru.Group)
|
postGroup, err := Groups.Get(ru.Group)
|
||||||
|
@ -583,8 +586,117 @@ func (ru *ReplyUser) Init() (group *Group, err error) {
|
||||||
}
|
}
|
||||||
ru.Tag = postGroup.Tag
|
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?
|
// 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)
|
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...
|
// We really shouldn't have inline HTML, we should do something about this...
|
||||||
if ru.ActionType != "" {
|
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
|
// 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) {
|
func (t *TopicUser) Replies(offset, pFrag int, user *User) (rlist []*ReplyUser /*, ogdesc string*/, err error) {
|
||||||
var likedMap map[int]int
|
var likedMap, attachMap map[int]int
|
||||||
if user.Liked > 0 {
|
var likedQueryList, attachQueryList []int
|
||||||
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{}
|
|
||||||
|
|
||||||
var rid int
|
var rid int
|
||||||
if len(t.Rids) > 0 {
|
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)
|
re, err := Rstore.GetCache().Get(rid)
|
||||||
ucache := Users.GetCache()
|
ucache := Users.GetCache()
|
||||||
var ruser *User
|
var ruser *User
|
||||||
if err == nil && ucache != nil {
|
if ucache != nil {
|
||||||
//log.Print("ucache step")
|
//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()
|
hTbl := GetHookTable()
|
||||||
rf := func(r *ReplyUser) error {
|
rf := func(r *ReplyUser) (err error) {
|
||||||
//log.Printf("before r: %+v\n", r)
|
//log.Printf("before r: %+v\n", r)
|
||||||
group, err := r.Init()
|
group, err := r.Init3(user, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -668,6 +775,7 @@ func (t *TopicUser) Replies(offset, pFrag int, user *User) (rlist []*ReplyUser,
|
||||||
if r.ContentHtml == r.Content {
|
if r.ContentHtml == r.Content {
|
||||||
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?
|
// TODO: This doesn't work properly so pick the first one instead?
|
||||||
/*if r.ID == pFrag {
|
/*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 {
|
return nil
|
||||||
likedMap[r.ID] = len(rlist)
|
|
||||||
likedQueryList = append(likedQueryList, r.ID)
|
|
||||||
}
|
}
|
||||||
if user.Perms.EditReply && r.AttachCount > 0 {
|
|
||||||
attachMap[r.ID] = len(rlist)
|
rf3 := func(r *ReplyUser) error {
|
||||||
attachQueryList = append(attachQueryList, r.ID)
|
//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
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Factor the user fields out and embed a user struct instead
|
// 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")
|
//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}
|
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}
|
||||||
reply.Group = ruser.Group
|
if err = rf3(r); err != nil {
|
||||||
err = rf(reply)
|
return nil, err
|
||||||
if 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 {
|
} else {
|
||||||
//log.Print("reply query serve")
|
//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 {
|
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()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
r := &ReplyUser{}
|
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)
|
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 {
|
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 {
|
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 {
|
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
|
// 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()*/ {
|
if user.Liked > 0 && len(likedQueryList) > 0 /*&& user.LastLiked <= time.Now()*/ {
|
||||||
eids, err := Likes.BulkExists(likedQueryList[1:], user.ID, "replies")
|
eids, err := Likes.BulkExists(likedQueryList, user.ID, "replies")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, eid := range eids {
|
for _, eid := range eids {
|
||||||
rlist[likedMap[eid]].Liked = true
|
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)
|
//log.Printf("attachQueryList: %+v\n", attachQueryList)
|
||||||
amap, err := Attachments.BulkMiniGetList("replies", attachQueryList)
|
amap, err := Attachments.BulkMiniGetList("replies", attachQueryList)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return nil, "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
//log.Printf("amap: %+v\n", amap)
|
//log.Printf("amap: %+v\n", amap)
|
||||||
//log.Printf("attachMap: %+v\n", attachMap)
|
//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
|
// TODO: Test this
|
||||||
|
@ -875,6 +1135,20 @@ func BuildTopicURL(slug string, tid int) string {
|
||||||
return "/topic/" + slug + "." + strconv.Itoa(tid)
|
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.
|
// I don't care if it isn't used,, it will likely be in the future. Nolint.
|
||||||
// nolint
|
// nolint
|
||||||
func getTopicURLPrefix() string {
|
func getTopicURLPrefix() string {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
type UserCache interface {
|
type UserCache interface {
|
||||||
DeallocOverflow(evictPriority bool) (evicted int) // May cause thread contention, looks for items to evict
|
DeallocOverflow(evictPriority bool) (evicted int) // May cause thread contention, looks for items to evict
|
||||||
Get(id int) (*User, error)
|
Get(id int) (*User, error)
|
||||||
|
Getn(id int) *User
|
||||||
GetUnsafe(id int) (*User, error)
|
GetUnsafe(id int) (*User, error)
|
||||||
BulkGet(ids []int) (list []*User)
|
BulkGet(ids []int) (list []*User)
|
||||||
Set(item *User) error
|
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.
|
// Get fetches a user by ID. Returns ErrNoRows if not present.
|
||||||
func (s *MemoryUserCache) Get(id int) (*User, error) {
|
func (s *MemoryUserCache) Get(id int) (*User, error) {
|
||||||
s.RLock()
|
s.RLock()
|
||||||
item, ok := s.items[id]
|
item := s.items[id]
|
||||||
s.RUnlock()
|
s.RUnlock()
|
||||||
if ok {
|
if item == nil {
|
||||||
|
return item, ErrNoRows
|
||||||
|
}
|
||||||
return item, nil
|
return item, nil
|
||||||
}
|
}
|
||||||
return item, ErrNoRows
|
|
||||||
|
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.
|
// 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 {
|
type UserStore interface {
|
||||||
DirtyGet(id int) *User
|
DirtyGet(id int) *User
|
||||||
Get(id int) (*User, error)
|
Get(id int) (*User, error)
|
||||||
|
Getn(id int) *User
|
||||||
GetByName(name string) (*User, error)
|
GetByName(name string) (*User, error)
|
||||||
Exists(id int) bool
|
Exists(id int) bool
|
||||||
GetOffset(offset, perPage int) ([]*User, error)
|
GetOffset(offset, perPage int) ([]*User, error)
|
||||||
|
@ -93,6 +94,15 @@ func (s *DefaultUserStore) Get(id int) (*User, error) {
|
||||||
u = &User{ID: id, Loggedin: true}
|
u = &User{ID: id, Loggedin: true}
|
||||||
var embeds int
|
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)
|
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 err == nil {
|
||||||
if embeds != -1 {
|
if embeds != -1 {
|
||||||
u.ParseSettings = DefaultParseSettings.CopyPtr()
|
u.ParseSettings = DefaultParseSettings.CopyPtr()
|
||||||
|
@ -104,21 +114,43 @@ func (s *DefaultUserStore) Get(id int) (*User, error) {
|
||||||
return u, err
|
return u, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Log weird cache errors? Not just here but in every *Cache?
|
func (s *DefaultUserStore) Getn(id int) *User {
|
||||||
// ! This bypasses the cache, use frugally
|
u := s.cache.Getn(id)
|
||||||
func (s *DefaultUserStore) GetByName(name string) (*User, error) {
|
if u != nil {
|
||||||
u := &User{Loggedin: true}
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
u = &User{ID: id, Loggedin: true}
|
||||||
var embeds int
|
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)
|
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 {
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if embeds != -1 {
|
if embeds != -1 {
|
||||||
u.ParseSettings = DefaultParseSettings.CopyPtr()
|
u.ParseSettings = DefaultParseSettings.CopyPtr()
|
||||||
u.ParseSettings.NoEmbed = embeds == 0
|
u.ParseSettings.NoEmbed = embeds == 0
|
||||||
}
|
}
|
||||||
u.Init()
|
u.Init()
|
||||||
s.cache.Set(u)
|
s.cache.Set(u)
|
||||||
|
return u
|
||||||
}
|
}
|
||||||
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}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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
|
// 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?
|
// 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 reCreatedAt time.Time
|
||||||
var reContent, reCreatedByName, reAvatar string
|
var reContent, reCreatedByName, reAvatar string
|
||||||
var rid, reCreatedBy, reLastEdit, reLastEditBy, reGroup int
|
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?
|
// TODO: Do a 301 if it's the wrong username? Do a canonical too?
|
||||||
_, pid, err := ParseSEOURL(r.URL.Path[len("/user/"):])
|
_, pid, err := ParseSEOURL(r.URL.Path[len("/user/"):])
|
||||||
if err != nil {
|
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?
|
// TODO: Preload this?
|
||||||
header.AddSheet(header.Theme.Name + "/profile.css")
|
h.AddSheet(h.Theme.Name + "/profile.css")
|
||||||
if user.Loggedin {
|
if user.Loggedin {
|
||||||
header.AddScriptAsync("profile_member.js")
|
h.AddScriptAsync("profile_member.js")
|
||||||
}
|
}
|
||||||
|
|
||||||
var puser *c.User
|
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?
|
// TODO: Add a shared function for checking for ErrNoRows and internal erroring if it's not that case?
|
||||||
puser, err = c.Users.Get(pid)
|
puser, err = c.Users.Get(pid)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return c.NotFound(w, r, header)
|
return c.NotFound(w, r, h)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return c.InternalError(err, w, r)
|
return c.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header.Title = phrases.GetTitlePhrasef("profile", puser.Name)
|
h.Title = phrases.GetTitlePhrasef("profile", puser.Name)
|
||||||
header.Path = c.BuildProfileURL(c.NameToSlug(puser.Name), puser.ID)
|
h.Path = c.BuildProfileURL(c.NameToSlug(puser.Name), puser.ID)
|
||||||
|
|
||||||
// Get the replies..
|
// Get the replies..
|
||||||
rows, err := profileStmts.getReplies.Query(puser.ID)
|
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
|
reLiked := false
|
||||||
reLikeCount := 0
|
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}
|
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()
|
_, err = ru.Init(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.InternalError(err, w, r)
|
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
|
canMessage := (!blockedInv && user.Perms.UseConvos) || (!blockedInv && puser.IsSuperMod && user.Perms.UseConvosOnlyWithMod) || user.IsSuperMod
|
||||||
canComment := !blockedInv && user.Perms.CreateProfileReply
|
canComment := !blockedInv && user.Perms.CreateProfileReply
|
||||||
|
|
||||||
ppage := c.ProfilePage{header, reList, *puser, currentScore, nextScore, blocked, canMessage, canComment}
|
ppage := c.ProfilePage{h, reList, *puser, currentScore, nextScore, blocked, canMessage, canComment}
|
||||||
return renderTemplate("profile", w, r, header, ppage)
|
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: 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
|
// 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"
|
js := r.PostFormValue("js") == "1"
|
||||||
rid, err := strconv.Atoi(srid)
|
rid, err := strconv.Atoi(srid)
|
||||||
if err != nil {
|
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
|
// 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 {
|
if ferr != nil {
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
if !user.Perms.ViewTopic || !user.Perms.EditReply {
|
if !u.Perms.ViewTopic || !u.Perms.EditReply {
|
||||||
return c.NoPermissionsJSQ(w, r, user, js)
|
return c.NoPermissionsJSQ(w, r, u, js)
|
||||||
}
|
}
|
||||||
if topic.IsClosed && !user.Perms.CloseTopic {
|
if topic.IsClosed && !u.Perms.CloseTopic {
|
||||||
return c.NoPermissionsJSQ(w, r, user, js)
|
return c.NoPermissionsJSQ(w, r, u, js)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = reply.SetPost(r.PostFormValue("edit_item"))
|
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)
|
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 {
|
if skip || rerr != nil {
|
||||||
return rerr
|
return rerr
|
||||||
}
|
}
|
||||||
|
@ -265,7 +265,7 @@ func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid
|
||||||
if !js {
|
if !js {
|
||||||
http.Redirect(w, r, "/topic/"+strconv.Itoa(topic.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther)
|
http.Redirect(w, r, "/topic/"+strconv.Itoa(topic.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther)
|
||||||
} else {
|
} 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 {
|
if err != nil {
|
||||||
return c.InternalErrorJSQ(err, w, r, js)
|
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: Refactor this
|
||||||
// TODO: Disable stat updates in posts handled by plugin_guilds
|
// 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"
|
js := r.PostFormValue("js") == "1"
|
||||||
rid, err := strconv.Atoi(srid)
|
rid, err := strconv.Atoi(srid)
|
||||||
if err != nil {
|
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
|
// 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 {
|
if ferr != nil {
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
if reply.CreatedBy != user.ID {
|
if reply.CreatedBy != u.ID {
|
||||||
if !user.Perms.ViewTopic || !user.Perms.DeleteReply {
|
if !u.Perms.ViewTopic || !u.Perms.DeleteReply {
|
||||||
return c.NoPermissionsJSQ(w, r, user, js)
|
return c.NoPermissionsJSQ(w, r, u, js)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := reply.Delete(); err != nil {
|
if err := reply.Delete(); err != nil {
|
||||||
return c.InternalErrorJSQ(err, w, r, js)
|
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 {
|
if skip || rerr != nil {
|
||||||
return rerr
|
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)
|
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 {
|
if err != nil {
|
||||||
return c.InternalErrorJSQ(err, w, r, js)
|
return c.InternalErrorJSQ(err, w, r, js)
|
||||||
}
|
}
|
||||||
|
@ -455,7 +455,7 @@ func RemoveAttachFromReplySubmit(w http.ResponseWriter, r *http.Request, u *c.Us
|
||||||
return nil
|
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"
|
js := r.PostFormValue("js") == "1"
|
||||||
rid, err := strconv.Atoi(srid)
|
rid, err := strconv.Atoi(srid)
|
||||||
if err != nil {
|
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
|
// 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 {
|
if ferr != nil {
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
|
if !u.Perms.ViewTopic || !u.Perms.LikeItem {
|
||||||
return c.NoPermissionsJSQ(w, r, user, js)
|
return c.NoPermissionsJSQ(w, r, u, js)
|
||||||
}
|
}
|
||||||
if reply.CreatedBy == user.ID {
|
if reply.CreatedBy == u.ID {
|
||||||
return c.LocalErrorJSQ("You can't like your own replies", w, r, user, js)
|
return c.LocalErrorJSQ("You can't like your own replies", w, r, u, js)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.Users.Get(reply.CreatedBy)
|
_, err = c.Users.Get(reply.CreatedBy)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
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 {
|
} else if err != nil {
|
||||||
return c.InternalErrorJSQ(err, w, r, js)
|
return c.InternalErrorJSQ(err, w, r, js)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = reply.Like(user.ID)
|
err = reply.Like(u.ID)
|
||||||
if err == c.ErrAlreadyLiked {
|
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 {
|
} else if err != nil {
|
||||||
return c.InternalErrorJSQ(err, w, r, js)
|
return c.InternalErrorJSQ(err, w, r, js)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! Be careful about leaking per-route permission state with user ptr
|
// ! 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)
|
err = c.AddActivityAndNotifyTarget(alert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.InternalErrorJSQ(err, w, r, js)
|
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 {
|
if skip || rerr != nil {
|
||||||
return rerr
|
return rerr
|
||||||
}
|
}
|
||||||
|
@ -522,7 +522,7 @@ func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user *c.User, srid
|
||||||
return nil
|
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"
|
js := r.PostFormValue("js") == "1"
|
||||||
rid, err := strconv.Atoi(srid)
|
rid, err := strconv.Atoi(srid)
|
||||||
if err != nil {
|
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
|
// 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 {
|
if ferr != nil {
|
||||||
return ferr
|
return ferr
|
||||||
}
|
}
|
||||||
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
|
if !u.Perms.ViewTopic || !u.Perms.LikeItem {
|
||||||
return c.NoPermissionsJSQ(w, r, user, js)
|
return c.NoPermissionsJSQ(w, r, u, js)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.Users.Get(reply.CreatedBy)
|
_, err = c.Users.Get(reply.CreatedBy)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
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 {
|
} else if err != nil {
|
||||||
return c.InternalErrorJSQ(err, w, r, js)
|
return c.InternalErrorJSQ(err, w, r, js)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = reply.Unlike(user.ID)
|
err = reply.Unlike(u.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.InternalErrorJSQ(err, w, r, js)
|
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)
|
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 {
|
if skip || rerr != nil {
|
||||||
return rerr
|
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)
|
return c.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
var poll c.Poll
|
var poll *c.Poll
|
||||||
if topic.Poll != 0 {
|
if topic.Poll != 0 {
|
||||||
pPoll, err := c.Polls.Get(topic.Poll)
|
pPoll, err := c.Polls.Get(topic.Poll)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("Couldn't find the attached poll for topic " + strconv.Itoa(topic.ID))
|
log.Print("Couldn't find the attached poll for topic " + strconv.Itoa(topic.ID))
|
||||||
return c.InternalError(err, w, r)
|
return c.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
poll = pPoll.Copy()
|
poll = new(c.Poll)
|
||||||
|
*poll = pPoll.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
if topic.LikeCount > 0 && user.Liked > 0 {
|
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-") {
|
if strings.HasPrefix(r.URL.Fragment, "post-") {
|
||||||
pFrag, _ = strconv.Atoi(strings.TrimPrefix(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 {
|
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)
|
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 {
|
} else if err != nil {
|
||||||
return c.InternalError(err, w, r)
|
return c.InternalError(err, w, r)
|
||||||
}
|
}
|
||||||
header.OGDesc = ogdesc
|
|
||||||
tpage.ItemList = rlist
|
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
|
var rerr c.RouteError
|
||||||
tmpl := forum.Tmpl
|
tmpl := forum.Tmpl
|
||||||
if r.FormValue("i") == "1" {
|
if r.FormValue("i") == "1" {
|
||||||
if tpage.Poll.ID != 0 {
|
if tpage.Poll != nil {
|
||||||
header.AddXRes("chartist/chartist.min.css", "chartist/chartist.min.js")
|
header.AddXRes("chartist/chartist.min.css", "chartist/chartist.min.js")
|
||||||
}
|
}
|
||||||
if tmpl == "" {
|
if tmpl == "" {
|
||||||
|
@ -179,7 +179,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user *c.User, header *c.H
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if tpage.Poll.ID != 0 {
|
if tpage.Poll != nil {
|
||||||
header.AddSheet("chartist/chartist.min.css")
|
header.AddSheet("chartist/chartist.min.css")
|
||||||
header.AddScript("chartist/chartist.min.js")
|
header.AddScript("chartist/chartist.min.js")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,32 +26,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rowblock post_container">
|
<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>
|
{{if .Poll}}{{template "topic_alt_poll.html" . }}{{end}}
|
||||||
<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}}
|
|
||||||
<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"}}">
|
<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 }}
|
{{template "topic_alt_userinfo.html" .Topic }}
|
||||||
<div class="content_container">
|
<div class="content_container">
|
||||||
|
|
|
@ -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>
|
|
@ -17,13 +17,13 @@
|
||||||
{{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}}?s={{.CurrentUser.Session}}' method="post"></form>
|
<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>
|
<button form='edit_topic_form' name="topic-button" class="formbutton show_on_edit submit_edit">{{lang "topic.update_button"}}</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</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"}}">
|
<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="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;">
|
||||||
|
@ -75,8 +75,8 @@
|
||||||
{{if not .Topic.IsClosed or .CurrentUser.Perms.CloseTopic}}
|
{{if not .Topic.IsClosed or .CurrentUser.Perms.CloseTopic}}
|
||||||
<div class="rowblock topic_reply_form quick_create_form" aria-label="{{lang "topic.reply_aria"}}">
|
<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>
|
<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" 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" id="has_poll_input" name="has_poll" value=0 type="hidden">
|
||||||
<div class="formrow real_first_child">
|
<div class="formrow real_first_child">
|
||||||
<div class="formitem">
|
<div class="formitem">
|
||||||
<textarea id="input_content" form="quick_post_form" name="content" placeholder="{{lang "topic.reply_content"}}" required></textarea>
|
<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="formrow poll_content_row auto_hide">
|
||||||
<div class="formitem">
|
<div class="formitem">
|
||||||
<div class="pollinput" data-pollinput=0>
|
<div class="pollinput" data-pollinput=0>
|
||||||
<input type="checkbox" disabled />
|
<input type="checkbox" disabled>
|
||||||
<label class="pollinputlabel"></label>
|
<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>
|
</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" 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>
|
<button form="quick_post_form" class="formbutton" id="add_poll_button">{{lang "topic.reply_add_poll_button"}}</button>
|
||||||
{{if .CurrentUser.Perms.UploadFiles}}
|
{{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>
|
<label for="upload_files" class="formbutton add_file_button">{{lang "topic.reply_add_file_button"}}</label>
|
||||||
<div id="upload_file_dock"></div>{{end}}
|
<div id="upload_file_dock"></div>{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue