Weekly Views.

Add avif mime extension.
Optimise skipping watched files which haven't been changed.
Block zgrab to save resources.
Reduce text and boilerplate.

Add route_topic_list_weekviews_start hook.

Add topic_list.week_views_filter phrase.
Changed footer_powered_by phrase.

You will need to run the updater / patcher for this commit.
This commit is contained in:
Azareal 2021-01-03 07:46:24 +10:00
parent 43bace814d
commit 74e0ce492d
27 changed files with 808 additions and 618 deletions

View File

@ -2,11 +2,14 @@ package counters
import (
"database/sql"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/query_gen"
qgen "github.com/Azareal/Gosora/query_gen"
"github.com/pkg/errors"
)
@ -19,19 +22,40 @@ type DefaultTopicViewCounter struct {
oddLock sync.RWMutex
evenLock sync.RWMutex
weekState byte
update *sql.Stmt
resetOdd *sql.Stmt
resetEven *sql.Stmt
resetBoth *sql.Stmt
}
func NewDefaultTopicViewCounter() (*DefaultTopicViewCounter, error) {
acc := qgen.NewAcc()
t := "topics"
co := &DefaultTopicViewCounter{
oddTopics: make(map[int]*RWMutexCounterBucket),
evenTopics: make(map[int]*RWMutexCounterBucket),
update: acc.Update("topics").Set("views = views + ?").Where("tid = ?").Prepare(),
//update: acc.Update(t).Set("views=views+?").Where("tid=?").Prepare(),
update: acc.Update(t).Set("views=views+?,weekEvenViews=weekEvenViews+?,weekOddViews=weekOddViews+?").Where("tid=?").Prepare(),
resetOdd: acc.Update(t).Set("weekOddViews=0").Prepare(),
resetEven: acc.Update(t).Set("weekEvenViews=0").Prepare(),
resetBoth: acc.Update(t).Set("weekOddViews=0,weekEvenViews=0").Prepare(),
}
c.AddScheduledFifteenMinuteTask(co.Tick) // Who knows how many topics we have queued up, we probably don't want this running too frequently
//c.AddScheduledSecondTask(co.Tick)
c.AddShutdownTask(co.Tick)
err := co.WeekResetInit()
if err != nil {
return co, err
}
addTick := func(f func() error) {
c.AddScheduledFifteenMinuteTask(f) // Who knows how many topics we have queued up, we probably don't want this running too frequently
//c.AddScheduledSecondTask(f)
c.AddShutdownTask(f)
}
addTick(co.Tick)
addTick(co.WeekResetTick)
return co, acc.FirstError()
}
@ -50,30 +74,87 @@ func (co *DefaultTopicViewCounter) Tick() error {
l.Lock()
delete(m, topicID)
l.Unlock()
err := co.insertChunk(count, topicID)
if err != nil {
return errors.Wrap(errors.WithStack(err),"topicview counter")
e := co.insertChunk(count, topicID)
if e != nil {
return errors.Wrap(errors.WithStack(e), "topicview counter")
}
l.RLock()
}
l.RUnlock()
return nil
}
err := cLoop(&co.oddLock,co.oddTopics)
err := cLoop(&co.oddLock, co.oddTopics)
if err != nil {
return err
}
return cLoop(&co.evenLock,co.evenTopics)
return cLoop(&co.evenLock, co.evenTopics)
}
func (co *DefaultTopicViewCounter) WeekResetInit() error {
lastWeekResetStr, e := c.Meta.Get("lastWeekReset")
if e != nil && e != sql.ErrNoRows {
return e
}
spl := strings.Split(lastWeekResetStr, "-")
if len(spl) <= 1 {
return nil
}
weekState, e := strconv.Atoi(spl[1])
if e != nil {
return e
}
co.weekState = byte(weekState)
unixLastWeekReset, e := strconv.ParseInt(spl[0], 10, 64)
if e != nil {
return e
}
resetTime := time.Unix(unixLastWeekReset, 0)
if time.Since(resetTime).Hours() >= (24 * 7) {
_, e = co.resetBoth.Exec()
}
return e
}
func (co *DefaultTopicViewCounter) WeekResetTick() (e error) {
now := time.Now()
_, week := now.ISOWeek()
if week != int(co.weekState) {
if week%2 == 0 { // is even?
_, e = co.resetOdd.Exec()
} else {
_, e = co.resetEven.Exec()
}
co.weekState = byte(week)
}
// TODO: Retry?
if e != nil {
return e
}
return c.Meta.Set("lastWeekReset", strconv.FormatInt(now.Unix(), 10)+"-"+strconv.Itoa(week))
}
// TODO: Optimise this further. E.g. Using IN() on every one view topic. Rinse and repeat for two views, three views, four views and five views.
func (co *DefaultTopicViewCounter) insertChunk(count int, topicID int) error {
func (co *DefaultTopicViewCounter) insertChunk(count, topicID int) (err error) {
if count == 0 {
return nil
}
c.DebugLogf("Inserting %d views into topic %d", count, topicID)
_, err := co.update.Exec(count, topicID)
even, odd := 0, 0
_, week := time.Now().ISOWeek()
if week%2 == 0 { // is even?
even += count
} else {
odd += count
}
if true {
_, err = co.update.Exec(count, even, odd, topicID)
} else {
_, err = co.update.Exec(count, topicID)
}
if err == sql.ErrNoRows {
return nil
} else if err != nil {
@ -81,15 +162,15 @@ func (co *DefaultTopicViewCounter) insertChunk(count int, topicID int) error {
}
// TODO: Add a way to disable this for extra speed ;)
tcache := c.Topics.GetCache()
if tcache != nil {
topic, err := tcache.Get(topicID)
tc := c.Topics.GetCache()
if tc != nil {
t, err := tc.Get(topicID)
if err == sql.ErrNoRows {
return nil
} else if err != nil {
return err
}
atomic.AddInt64(&topic.ViewCount, int64(count))
atomic.AddInt64(&t.ViewCount, int64(count))
}
return nil
@ -99,12 +180,12 @@ func (co *DefaultTopicViewCounter) Bump(topicID int) {
// Is the ID even?
if topicID%2 == 0 {
co.evenLock.RLock()
topic, ok := co.evenTopics[topicID]
t, ok := co.evenTopics[topicID]
co.evenLock.RUnlock()
if ok {
topic.Lock()
topic.counter++
topic.Unlock()
t.Lock()
t.counter++
t.Unlock()
} else {
co.evenLock.Lock()
co.evenTopics[topicID] = &RWMutexCounterBucket{counter: 1}
@ -114,12 +195,12 @@ func (co *DefaultTopicViewCounter) Bump(topicID int) {
}
co.oddLock.RLock()
topic, ok := co.oddTopics[topicID]
t, ok := co.oddTopics[topicID]
co.oddLock.RUnlock()
if ok {
topic.Lock()
topic.counter++
topic.Unlock()
t.Lock()
t.counter++
t.Unlock()
} else {
co.oddLock.Lock()
co.oddTopics[topicID] = &RWMutexCounterBucket{counter: 1}

View File

@ -419,6 +419,7 @@ type BasePanelPage struct {
Stats PanelStats
Zone string
ReportForumID int
DebugAdmin bool
}
type PanelPage struct {
*BasePanelPage
@ -789,6 +790,15 @@ type PanelDebugPage struct {
Disk DebugPageDisk
}
type PanelDebugTaskTask struct {
Name string
Type int // 0 = halfsec, 1 = sec, 2 = fifteenmin, 3 = hour, 4 = shutdown
}
type PanelDebugTaskPage struct {
*BasePanelPage
Tasks []PanelDebugTaskTask
}
type PageSimple struct {
Title string
Something interface{}

View File

@ -57,17 +57,17 @@ func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) {
get: acc.Select(p).Columns("parentID, parentTable, type, options, votes").Where("pollID=?").Prepare(),
exists: acc.Select(p).Columns("pollID").Where("pollID=?").Prepare(),
createPoll: acc.Insert(p).Columns("parentID, parentTable, type, options").Fields("?,?,?,?").Prepare(),
createPollOption: acc.Insert("polls_options").Columns("pollID, option, votes").Fields("?,?,0").Prepare(),
createPollOption: acc.Insert("polls_options").Columns("pollID,option,votes").Fields("?,?,0").Prepare(),
//count: acc.SimpleCount(p, "", ""),
}, acc.FirstError()
}
func (s *DefaultPollStore) Exists(id int) bool {
err := s.exists.QueryRow(id).Scan(&id)
if err != nil && err != ErrNoRows {
LogError(err)
e := s.exists.QueryRow(id).Scan(&id)
if e != nil && e != ErrNoRows {
LogError(e)
}
return err != ErrNoRows
return e != ErrNoRows
}
func (s *DefaultPollStore) Get(id int) (*Poll, error) {
@ -198,33 +198,33 @@ func (s *DefaultPollStore) Reload(id int) error {
}
func (s *DefaultPollStore) unpackOptionsMap(rawOptions map[int]string) []PollOption {
options := make([]PollOption, len(rawOptions))
for id, option := range rawOptions {
options[id] = PollOption{id, option}
opts := make([]PollOption, len(rawOptions))
for id, opt := range rawOptions {
opts[id] = PollOption{id, opt}
}
return options
return opts
}
// TODO: Use a transaction for this
func (s *DefaultPollStore) Create(parent Pollable, pollType int, pollOptions map[int]string) (id int, err error) {
func (s *DefaultPollStore) Create(parent Pollable, pollType int, pollOptions map[int]string) (id int, e error) {
// TODO: Move the option names into the polls_options table and get rid of this json sludge?
pollOptionsTxt, err := json.Marshal(pollOptions)
if err != nil {
return 0, err
pollOptionsTxt, e := json.Marshal(pollOptions)
if e != nil {
return 0, e
}
res, err := s.createPoll.Exec(parent.GetID(), parent.GetTable(), pollType, pollOptionsTxt)
if err != nil {
return 0, err
res, e := s.createPoll.Exec(parent.GetID(), parent.GetTable(), pollType, pollOptionsTxt)
if e != nil {
return 0, e
}
lastID, err := res.LastInsertId()
if err != nil {
return 0, err
lastID, e := res.LastInsertId()
if e != nil {
return 0, e
}
for i := 0; i < len(pollOptions); i++ {
_, err := s.createPollOption.Exec(lastID, i)
if err != nil {
return 0, err
_, e := s.createPollOption.Exec(lastID, i)
if e != nil {
return 0, e
}
}

View File

@ -227,7 +227,7 @@ func compileCommons(c *tmpl.CTemplateSet, head, head2 *Header, forumList []Forum
}*/
var topicsList []TopicsRowMut
topic := Topic{1, "/topic/topic-title.1", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 1, "classname", 0, "", nil}
topic := Topic{1, "/topic/topic-title.1", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 1, 1, "classname", 0, "", nil}
topicsList = append(topicsList, TopicsRowMut{&TopicsRow{topic, 1, user2, "", 0, user3, "General", "/forum/general.2"}, false})
topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, []int{1}, QuickTools{false, false, false}, Paginator{[]int{1}, 1, 1}}
o.Add("topics", "c.TopicListPage", topicListPage)
@ -311,7 +311,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
t.Add("profile", "c.ProfilePage", ppage)
var topicsList []TopicsRowMut
topic := Topic{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 1, "classname", 0, "", nil}
topic := Topic{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 1, 1, "classname", 0, "", nil}
topicsList = append(topicsList, TopicsRowMut{&TopicsRow{topic, 0, user2, "", 0, user3, "General", "/forum/general.2"}, false})
topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, []int{1}, QuickTools{false, false, false}, Paginator{[]int{1}, 1, 1}}
@ -375,7 +375,7 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
convoListPage := ConvoListPage{header, cRows, Paginator{[]int{1}, 1, 1}}
t.AddStd("convos", "c.ConvoListPage", convoListPage)
basePage := &BasePanelPage{header, PanelStats{}, "dashboard", ReportForumID}
basePage := &BasePanelPage{header, PanelStats{}, "dashboard", ReportForumID, true}
t.AddStd("panel", "c.Panel", Panel{basePage, "panel_dashboard_right", "", "panel_dashboard", inter})
ges := []GridElement{GridElement{"", "", "", 1, "grid_istat", "", "", ""}}
t.AddStd("panel_dashboard", "c.DashGrids", DashGrids{ges, ges})
@ -540,7 +540,7 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri
t := TItemHold(make(map[string]TItem))
topic := Topic{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 0, "classname", 1, "", nil}
topic := Topic{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 0, 1, "classname", 1, "", nil}
topicsRow := TopicsRowMut{&TopicsRow{topic, 0, user2, "", 0, user3, "General", "/forum/general.2"}, false}
t.AddStd("topics_topic", "c.TopicsRowMut", topicsRow)

View File

@ -45,6 +45,7 @@ type Topic struct {
PostCount int
LikeCount int
AttachCount int
WeekViews int
ClassName string // CSS Class Name
Poll int
Data string // Used for report metadata
@ -1137,6 +1138,7 @@ func GetTopicUser(user *User, tid int) (tu TopicUser, err error) {
tu.Tag = Groups.DirtyGet(tu.Group).Tag
if tcache != nil {
// TODO: weekly views
theTopic := Topic{ID: tu.ID, Link: tu.Link, Title: tu.Title, Content: tu.Content, CreatedBy: tu.CreatedBy, IsClosed: tu.IsClosed, Sticky: tu.Sticky, CreatedAt: tu.CreatedAt, LastReplyAt: tu.LastReplyAt, LastReplyID: tu.LastReplyID, ParentID: tu.ParentID, IP: tu.IP, ViewCount: tu.ViewCount, PostCount: tu.PostCount, LikeCount: tu.LikeCount, AttachCount: tu.AttachCount, Poll: tu.Poll}
//log.Printf("theTopic: %+v\n", theTopic)
_ = tcache.Set(&theTopic)

View File

@ -370,13 +370,12 @@ func (tList *DefaultTopicList) getListByForum(f *Forum, page, orderby int) (topi
// TODO: Use something other than TopicsRow as we don't need to store the forum name and link on each and every topic item?
reqUserList := make(map[int]bool)
for rows.Next() {
t := TopicsRow{Topic: Topic{ID: 0}}
t := TopicsRow{Topic: Topic{ParentID: f.ID}}
err := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.IsClosed, &t.Sticky, &t.CreatedAt, &t.LastReplyAt, &t.LastReplyBy, &t.LastReplyID, &t.ViewCount, &t.PostCount, &t.LikeCount)
if err != nil {
return nil, Paginator{nil, 1, 1}, err
}
t.ParentID = f.ID
t.Link = BuildTopicURL(NameToSlug(t.Title), t.ID)
// TODO: Create a specialised function with a bit less overhead for getting the last page for a post count
_, _, lastPage := PageOffset(t.PostCount, 1, Config.ItemsPerPage)
@ -575,9 +574,9 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
_, week := now.ISOWeek()
day := int(now.Weekday()) + 1
if week%2 == 0 { // is even?
cols = "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data,(weekEvenViews+((weekOddViews/7)*" + strconv.Itoa(day) + ")) AS weekViews"
cols = "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data,FLOOR(weekEvenViews+((weekOddViews/7)*" + strconv.Itoa(day) + ")) AS weekViews"
} else {
cols = "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data,(weekOddViews+((weekEvenViews/7)*" + strconv.Itoa(day) + ")) AS weekViews"
cols = "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data,FLOOR(weekOddViews+((weekEvenViews/7)*" + strconv.Itoa(day) + ")) AS weekViews"
}
topicCount, err = ArgQToWeekViewTopicCount(argList, qlist)
if err != nil {
@ -596,7 +595,7 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
tList.qLock.RUnlock()
if stmt == nil {
orderq = "views DESC,lastReplyAt DESC,createdBy DESC"
cols = "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data"
cols = "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data,weekEvenViews"
}
default:
tList.qLock2.RLock()
@ -604,7 +603,7 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
tList.qLock2.RUnlock()
if stmt == nil {
orderq = "sticky DESC,lastReplyAt DESC,createdBy DESC"
cols = "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data"
cols = "tid,title,content,createdBy,is_closed,sticky,createdAt,lastReplyAt,lastReplyBy,lastReplyID,parentID,views,postCount,likeCount,attachCount,poll,data,weekEvenViews"
}
}
offset, page, lastPage := PageOffset(topicCount, page, Config.ItemsPerPage)
@ -636,10 +635,12 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
// TODO: Embed Topic structs in TopicsRow to make it easier for us to reuse this work in the topic cache
t := TopicsRow{}
//var weekViews []uint8
err := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.IsClosed, &t.Sticky, &t.CreatedAt, &t.LastReplyAt, &t.LastReplyBy, &t.LastReplyID, &t.ParentID, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data /*, &weekViews*/)
err := rows.Scan(&t.ID, &t.Title, &t.Content, &t.CreatedBy, &t.IsClosed, &t.Sticky, &t.CreatedAt, &t.LastReplyAt, &t.LastReplyBy, &t.LastReplyID, &t.ParentID, &t.ViewCount, &t.PostCount, &t.LikeCount, &t.AttachCount, &t.Poll, &t.Data, &t.WeekViews)
if err != nil {
return nil, Paginator{nil, 1, 1}, err
}
//t.WeekViews = int(weekViews[0])
//log.Printf("t: %+v\n", t)
//log.Printf("weekViews: %+v\n", weekViews)
t.Link = BuildTopicURL(NameToSlug(t.Title), t.ID)
@ -666,7 +667,7 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
// Avoid the extra queries on topic list pages, if we already have what we want...
hRids := false
if tc != nil {
if t, err := tc.Get(t.ID); err == nil {
if t, e := tc.Get(t.ID); e == nil {
hRids = len(t.Rids) != 0
}
}

File diff suppressed because it is too large Load Diff

View File

@ -658,6 +658,7 @@
"topic_list.changed_topics":"Click to see %d new or changed topics",
"topic_list.most_recent_filter":"Most Recent",
"topic_list.most_viewed_filter":"Most Viewed",
"topic_list.week_views_filter":"Week Views",
"topic_list.replies_suffix":"replies",
"topic_list.likes_suffix":"likes",
"topic_list.views_suffix":"views",
@ -806,7 +807,7 @@
"error_head":"An error has occurred",
"footer_thingymawhatsit":"Can you please keep the powered by notice? ;)",
"footer_powered_by":"Powered by Gosora",
"footer_powered_by":"Powered by Gosora Forum Software",
"footer_made_with_love":"Made with love by Azareal",
"footer_theme_selector_aria":"Change the site's appearance",

65
main.go
View File

@ -14,6 +14,7 @@ import (
"fmt"
"io"
"log"
"mime"
"net/http"
"os"
"os/signal"
@ -278,6 +279,12 @@ func storeInit() (err error) {
return errors.WithStack(err)
}
log.Print("Initialising the meta store")
c.Meta, err = meta.NewDefaultMetaStore(acc)
if err != nil {
return errors.WithStack(err)
}
log.Print("Initialising the view counters")
if !c.Config.DisableAnalytics {
co.GlobalViewCounter, err = co.NewGlobalViewCounter(acc)
@ -332,12 +339,6 @@ func storeInit() (err error) {
return errors.WithStack(err)
}
log.Print("Initialising the meta store")
c.Meta, err = meta.NewDefaultMetaStore(acc)
if err != nil {
return errors.WithStack(err)
}
return nil
}
@ -345,8 +346,7 @@ func storeInit() (err error) {
func main() {
// TODO: Recover from panics
/*defer func() {
r := recover()
if r != nil {
if r := recover(); r != nil {
log.Print(r)
debug.PrintStack()
return
@ -374,6 +374,11 @@ func main() {
pprof.StartCPUProfile(f)
}
err = mime.AddExtensionType(".avif", "image/avif")
if err != nil {
log.Fatal(err)
}
jsToken, err := c.GenerateSafeString(80)
if err != nil {
log.Fatal(err)
@ -440,6 +445,7 @@ func main() {
defer watcher.Close()
go func() {
var ErrFileSkip = errors.New("skip mod file")
modifiedFileEvent := func(path string) error {
pathBits := strings.Split(path, "\\")
if len(pathBits) == 0 {
@ -458,23 +464,27 @@ func main() {
}
}
}
return nil
return ErrFileSkip
}
// TODO: Expand this to more types of files
var err error
for {
select {
case event := <-watcher.Events:
case ev := <-watcher.Events:
// TODO: Handle file deletes (and renames more graciously by removing the old version of it)
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("modified file:", event.Name)
err = modifiedFileEvent(event.Name)
} else if event.Op&fsnotify.Create == fsnotify.Create {
log.Println("new file:", event.Name)
err = modifiedFileEvent(event.Name)
if ev.Op&fsnotify.Write == fsnotify.Write {
err = modifiedFileEvent(ev.Name)
if err != ErrFileSkip {
log.Println("modified file:", ev.Name)
} else {
log.Println("unknown event:", event)
err = nil
}
} else if ev.Op&fsnotify.Create == fsnotify.Create {
log.Println("new file:", ev.Name)
err = modifiedFileEvent(ev.Name)
} else {
log.Println("unknown event:", ev)
err = nil
}
if err != nil {
@ -503,9 +513,12 @@ func main() {
}
}
/*if err = c.StaticFiles.GenJS(); err != nil {
c.LogError(err)
}*/
log.Print("Checking for init tasks")
err = sched()
if err != nil {
if err = sched(); err != nil {
c.LogError(err)
}
@ -519,9 +532,9 @@ func main() {
// Resource Management Goroutine
go func() {
ucache := c.Users.GetCache()
tcache := c.Topics.GetCache()
if ucache == nil && tcache == nil {
uc := c.Users.GetCache()
tc := c.Topics.GetCache()
if uc == nil && tc == nil {
return
}
@ -532,13 +545,13 @@ func main() {
select {
case <-secondTicker.C:
// TODO: Add a LastRequested field to cached User structs to avoid evicting the same things which wind up getting loaded again anyway?
if ucache != nil {
ucap := ucache.GetCapacity()
if ucache.Length() <= ucap || c.Users.Count() <= ucap {
if uc != nil {
ucap := uc.GetCapacity()
if uc.Length() <= ucap || c.Users.Count() <= ucap {
couldNotDealloc = false
continue
}
lastEvictedCount = ucache.DeallocOverflow(couldNotDealloc)
lastEvictedCount = uc.DeallocOverflow(couldNotDealloc)
couldNotDealloc = (lastEvictedCount == 0)
}
}

View File

@ -52,7 +52,7 @@ func init() {
addPatch(32, patch32)
addPatch(33, patch33)
addPatch(34, patch34)
//addPatch(35, patch35)
addPatch(35, patch35)
}
func bcol(col string, val bool) qgen.DBTableColumn {
@ -937,3 +937,11 @@ func patch34(scanner *bufio.Scanner) error {
return nil
}
func patch35(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("topics", tC{"weekEvenViews", "int", 0, false, false, "0"}, nil))
if err != nil {
return err
}
return execStmt(qgen.Builder.AddColumn("topics", tC{"weekOddViews", "int", 0, false, false, "0"}, nil))
}

View File

@ -431,7 +431,7 @@ func main() {
"Cocolyzebot": "cocolyze",
"Barkrowler": "babbar",
"SurdotlyBot": "surdotly",
"DomCopBot":"domcop",
"DomCopBot": "domcop",
"NetcraftSurveyAgent": "netcraft",
"BLEXBot": "blexbot",
"Burf": "burf",
@ -441,7 +441,7 @@ func main() {
"CCBot": "ccbot",
"zgrab": "zgrab",
"Nimbostratus": "cloudsystemnetworks",
"MauiBot":"maui",
"MauiBot": "maui",
"curl": "curl",
"python": "python",
"Go": "go",
@ -893,7 +893,11 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
agent = {{.AllAgentMap.internetexplorer}}
}
case {{.AllAgentMap.zgrab}}:
r.SuspiciousRequest(req,"Vuln Scanner")
w.WriteHeader(200) // 400
w.Write([]byte(""))
r.DumpRequest(req,"Blocked Scanner")
co.AgentViewCounter.Bump({{.AllAgentMap.zgrab}})
return
}
if agent == 0 {

View File

@ -28,6 +28,7 @@ func routes(r *Router) {
topicGroup := newRouteGroup("/topics/",
View("routes.TopicList", "/topics/"),
View("routes.TopicListMostViewed", "/topics/most-viewed/"),
View("routes.TopicListWeekViews", "/topics/week-views/"),
MView("routes.CreateTopic", "/topics/create/", "extraData"),
)
r.AddGroup(topicGroup)
@ -276,5 +277,6 @@ func panelRoutes() *RouteGroup {
View("panel.LogsMod", "/panel/logs/mod/"),
View("panel.LogsAdmin", "/panel/logs/admin/"),
View("panel.Debug", "/panel/debug/").Before("AdminOnly"),
View("panel.DebugTasks", "/panel/debug/tasks/").Before("AdminOnly"),
)
}

View File

@ -49,6 +49,7 @@ func buildBasePage(w http.ResponseWriter, r *http.Request, u *c.User, titlePhras
return nil, ferr
}
h.Title = p.GetTitlePhrase("panel_" + titlePhrase)
debugAdmin := true
return &c.BasePanelPage{h, stats, zone, c.ReportForumID}, nil
return &c.BasePanelPage{h, stats, zone, c.ReportForumID, debugAdmin}, nil
}

View File

@ -29,7 +29,7 @@ type dashStmts struct {
// TODO: Stop hard-coding these queries
func dashMySQLStmts() (stmts dashStmts, err error) {
db := qgen.Builder.GetConn()
prepareStmt := func(table string, ext string, dur string) *sql.Stmt {
prepStmt := func(table, ext, dur string) *sql.Stmt {
if err != nil {
return nil
}
@ -38,11 +38,11 @@ func dashMySQLStmts() (stmts dashStmts, err error) {
return stmt
}
stmts.todaysPostCount = prepareStmt("replies", "", "day")
stmts.todaysTopicCount = prepareStmt("topics", "", "day")
stmts.todaysNewUserCount = prepareStmt("users", "", "day")
stmts.todaysTopicCountByForum = prepareStmt("topics", " and parentID = ?", "day")
stmts.weeklyTopicCountByForum = prepareStmt("topics", " and parentID = ?", "week")
stmts.todaysPostCount = prepStmt("replies", "", "day")
stmts.todaysTopicCount = prepStmt("topics", "", "day")
stmts.todaysNewUserCount = prepStmt("users", "", "day")
stmts.todaysTopicCountByForum = prepStmt("topics", " and parentID=?", "day")
stmts.weeklyTopicCountByForum = prepStmt("topics", " and parentID=?", "week")
return stmts, err
}
@ -50,7 +50,7 @@ func dashMySQLStmts() (stmts dashStmts, err error) {
// TODO: Stop hard-coding these queries
func dashMSSQLStmts() (stmts dashStmts, err error) {
db := qgen.Builder.GetConn()
prepareStmt := func(table string, ext string, dur string) *sql.Stmt {
prepStmt := func(table, ext, dur string) *sql.Stmt {
if err != nil {
return nil
}
@ -59,11 +59,11 @@ func dashMSSQLStmts() (stmts dashStmts, err error) {
return stmt
}
stmts.todaysPostCount = prepareStmt("replies", "", "DAY")
stmts.todaysTopicCount = prepareStmt("topics", "", "DAY")
stmts.todaysNewUserCount = prepareStmt("users", "", "DAY")
stmts.todaysTopicCountByForum = prepareStmt("topics", " and parentID = ?", "DAY")
stmts.weeklyTopicCountByForum = prepareStmt("topics", " and parentID = ?", "WEEK")
stmts.todaysPostCount = prepStmt("replies", "", "DAY")
stmts.todaysTopicCount = prepStmt("topics", "", "DAY")
stmts.todaysNewUserCount = prepStmt("users", "", "DAY")
stmts.todaysTopicCountByForum = prepStmt("topics", " and parentID=?", "DAY")
stmts.weeklyTopicCountByForum = prepStmt("topics", " and parentID=?", "WEEK")
return stmts, err
}
@ -81,7 +81,7 @@ func Dashboard(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteErro
cpustr := unknown
var cpuColour string
lessThanSwitch := func(number int, lowerBound int, midBound int) string {
lessThanSwitch := func(number, lowerBound, midBound int) string {
switch {
case number < lowerBound:
return "stat_green"
@ -120,7 +120,7 @@ func Dashboard(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteErro
runtime.ReadMemStats(&m)
memCount, memUnit := c.ConvertByteUnit(float64(m.Sys))
greaterThanSwitch := func(number int, lowerBound int, midBound int) string {
greaterThanSwitch := func(number, lowerBound, midBound int) string {
switch {
case number > midBound:
return "stat_green"
@ -174,11 +174,11 @@ func Dashboard(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteErro
}
grid1 := []GE{}
addElem1 := func(id string, href string, body string, order int, class string, back string, textColour string, tooltip string) {
addElem1 := func(id, href, body string, order int, class, back, textColour, tooltip string) {
grid1 = append(grid1, GE{id, href, body, order, class, back, textColour, tooltip})
}
gridElements := []GE{}
addElem := func(id string, href string, body string, order int, class string, back string, textColour string, tooltip string) {
addElem := func(id, href, body string, order int, class, back, textColour, tooltip string) {
gridElements = append(gridElements, GE{id, href, body, order, class, back, textColour, tooltip})
}

View File

@ -129,3 +129,15 @@ func Debug(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
pi := c.PanelDebugPage{basePage, goVersion, dbVersion, uptime, openConnCount, qgen.Builder.GetAdapter().GetName(), goroutines, cpus, debugTasks, memStats, debugCache, debugDatabase, debugDisk}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_dashboard_right", "debug_page", "panel_debug", pi})
}
func DebugTasks(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "debug", "debug")
if ferr != nil {
return ferr
}
var debugTasks []c.PanelDebugTaskTask
pi := c.PanelDebugTaskPage{basePage, debugTasks}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_dashboard_right", "debug_page", "panel_debug_task", pi})
}

View File

@ -17,12 +17,12 @@ func Plugins(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
return c.NoPermissions(w, r, u)
}
var pluginList []interface{}
for _, plugin := range c.Plugins {
pluginList = append(pluginList, plugin)
var plList []interface{}
for _, pl := range c.Plugins {
plList = append(plList, pl)
}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_plugins", c.PanelPage{basePage, pluginList, nil}})
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_plugins", c.PanelPage{basePage, plList, nil}})
}
// TODO: Abstract more of the plugin activation / installation / deactivation logic, so we can test all that more reliably and easily

View File

@ -54,6 +54,14 @@ func TopicListMostViewed(w http.ResponseWriter, r *http.Request, u *c.User, h *c
return TopicListCommon(w, r, u, h, "mostviewed", c.TopicListMostViewed)
}
func TopicListWeekViews(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError {
skip, rerr := h.Hooks.VhookSkippable("route_topic_list_weekviews_start", w, r, u, h)
if skip || rerr != nil {
return rerr
}
return TopicListCommon(w, r, u, h, "weekviews", c.TopicListWeekViews)
}
// TODO: Implement search
func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header, torder string, tsorder int) c.RouteError {
h.Title = phrases.GetTitlePhrase("topics")
@ -67,6 +75,7 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
return c.LocalError("Something weird happened", w, r, user)
}
var forumList []c.Forum
// Get the current page
page, _ := strconv.Atoi(r.FormValue("page"))
sfids := r.FormValue("fids")
@ -80,19 +89,19 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
fids = append(fids, fid)
}
if len(fids) == 1 {
forum, err := c.Forums.Get(fids[0])
f, err := c.Forums.Get(fids[0])
if err != nil {
return c.LocalError("Invalid fid forum", w, r, user)
}
h.Title = forum.Name
h.ZoneID = forum.ID
h.Title = f.Name
h.ZoneID = f.ID
forumList = append(forumList, *f)
}
}
// TODO: Allow multiple forums in searches
// TODO: Simplify this block after initially landing search
var topicList []*c.TopicsRow
var forumList []c.Forum
var pagi c.Paginator
var canDelete, ccanDelete, canLock, ccanLock, canMove, ccanMove bool
q := r.FormValue("q")
@ -245,11 +254,14 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
}
// TODO: Pass a struct back rather than passing back so many variables
//log.Printf("before forumList: %+v\n", forumList)
var fps map[int]c.QuickTools
if user.IsSuperAdmin {
//log.Print("user.IsSuperAdmin")
topicList, forumList, pagi, err = c.TopicList.GetList(page, tsorder, fids)
canLock, canMove = true, true
} else {
//log.Print("!user.IsSuperAdmin")
topicList, forumList, pagi, err = c.TopicList.GetListByGroup(group, page, tsorder, fids)
fps = make(map[int]c.QuickTools)
for _, f := range forumList {
@ -283,6 +295,8 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
if err != nil {
return c.InternalError(err, w, r)
}
//log.Printf("after forumList: %+v\n", forumList)
//log.Printf("after topicList: %+v\n", topicList)
// TODO: Reduce the amount of boilerplate here
if r.FormValue("js") == "1" {

View File

@ -2,7 +2,7 @@
<div class="rowitem"><h1>{{.Name}}{{lang "panel_forum_head_suffix"}}</h1></div>
</div>
<div id="panel_forum"class="colstack_item the_form">
<form action="/panel/forums/edit/submit/{{.ID}}?s={{.CurrentUser.Session}}" method="post">
<form action="/panel/forums/edit/submit/{{.ID}}?s={{.CurrentUser.Session}}"method="post">
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_forum_name"}}</a></div>
<div class="formitem"><input name="forum_name"type="text"value="{{.Name}}"placeholder="{{lang "panel_forum_name_placeholder"}}"></div>

View File

@ -97,7 +97,12 @@
{{if .CurrentUser.IsSuperAdmin}}<div class="rowitem passive">
<a href="/panel/backups/">{{lang "panel_menu_backups"}}</a>
</div>{{end}}
{{if .CurrentUser.IsAdmin}}<div class="rowitem passive">
{{if .CurrentUser.IsAdmin}}
<div class="rowitem passive">
<a href="/panel/debug/">{{lang "panel_menu_debug"}}</a>
</div>
{{if .DebugAdmin}}<div class="rowitem passive">
<a href="/panel/debug/tasks/">{{lang "panel_menu_debug"}}</a>
</div>{{end}}
{{end}}
</div>

View File

@ -1,16 +1,16 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_word_filters_head"}}</h1></div>
</div>
<div id="panel_word_filters" class="colstack_item rowlist micro_grid">
<div id="panel_word_filters"class="colstack_item rowlist micro_grid">
{{range .Something}}
<div class="rowitem panel_compactrow editable_parent">
<a data-field="find" data-type="text" href="/panel/settings/word-filters/edit/{{.ID}}" class="editable_block panel_upshift edit_fields filter_find">{{.Find}}</a>
<a data-field="find"data-type="text"href="/panel/settings/word-filters/edit/{{.ID}}"class="editable_block panel_upshift edit_fields filter_find">{{.Find}}</a>
<span class="itemSeparator"></span>
<a data-field="replace" data-type="text" class="editable_block panel_compacttext filter_replace">{{.Replace}}</a>
<a data-field="replace"data-type="text"class="editable_block panel_compacttext filter_replace">{{.Replace}}</a>
<span class="panel_buttons">
<a class="panel_tag edit_fields hide_on_edit panel_right_button edit_button" aria-label="{{lang "panel_word_filters_edit_button_aria"}}"></a>
<a class="panel_right_button show_on_edit" href="/panel/settings/word-filters/edit/submit/{{.ID}}"><button class='panel_tag submit_edit' type='submit'>{{lang "panel_word_filters_update_button"}}</button></a>
<a href="/panel/settings/word-filters/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="panel_tag panel_right_button hide_on_edit delete_button" aria-label="{{lang "panel_word_filters_delete_button_aria"}}"></a>
<a class="panel_tag edit_fields hide_on_edit panel_right_button edit_button"aria-label="{{lang "panel_word_filters_edit_button_aria"}}"></a>
<a class="panel_right_button show_on_edit"href="/panel/settings/word-filters/edit/submit/{{.ID}}"><button class='panel_tag submit_edit'type='submit'>{{lang "panel_word_filters_update_button"}}</button></a>
<a href="/panel/settings/word-filters/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}"class="panel_tag panel_right_button hide_on_edit delete_button"aria-label="{{lang "panel_word_filters_delete_button_aria"}}"></a>
</span>
</div>
{{else}}
@ -24,14 +24,14 @@
<div class="rowitem"><h1>{{lang "panel_word_filters_create_head"}}</h1></div>
</div>
<div class="colstack_item the_form">
<form action="/panel/settings/word-filters/create/?s={{.CurrentUser.Session}}" method="post">
<form action="/panel/settings/word-filters/create/?s={{.CurrentUser.Session}}"method="post">
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_word_filters_create_find"}}</a></div>
<div class="formitem"><input name="find" type="text" placeholder="{{lang "panel_word_filters_create_find_placeholder"}}"></div>
<div class="formitem"><input name="find"type="text"placeholder="{{lang "panel_word_filters_create_find_placeholder"}}"></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_word_filters_create_replacement"}}</a></div>
<div class="formitem"><input name="replace" type="text" placeholder="{{lang "panel_word_filters_create_replacement_placeholder"}}"></div>
<div class="formitem"><input name="replace"type="text"placeholder="{{lang "panel_word_filters_create_replacement_placeholder"}}"></div>
</div>
<div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton form_middle_button">{{lang "panel_word_filters_create_button"}}</button></div>

View File

@ -5,7 +5,7 @@
<a href="{{.CurrentUser.Link}}"class="the_name"rel="author">{{.CurrentUser.Name}}</a>
<div class="tag_block">
<div class="tag_pre"></div>
{{if .CurrentUser.Tag}}<div class="post_tag">{{.CurrentUser.Tag}}</div>{{else}}<div class="post_tag post_level">{{level .CurrentUser.Level}}</div>{{end}}
{{if .CurrentUser.Tag}}<div class="post_tag">{{.CurrentUser.Tag}}{{else}}<div class="post_tag post_level">{{level .CurrentUser.Level}}{{end}}</div>
<div class="tag_post"></div>
</div>
</div>

View File

@ -4,7 +4,7 @@
<a href="{{.UserLink}}"class="the_name"rel="author">{{.CreatedByName}}</a>
<div class="tag_block">
<div class="tag_pre"></div>
{{if .Tag}}<div class="post_tag">{{.Tag}}</div>{{else}}<div class="post_tag post_level">{{level .Level}}</div>{{end}}
{{if .Tag}}<div class="post_tag">{{.Tag}}{{else}}<div class="post_tag post_level">{{level .Level}}{{end}}</div>
<div class="tag_post"></div>
</div>
</div>

View File

@ -56,7 +56,7 @@
{{if .CurrentUser.Perms.CloseTopic}}{{if .Topic.IsClosed}}<a class="mod_button" href='/topic/unlock/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}'title="{{lang "topic.unlock_tooltip"}}" aria-label="{{lang "topic.unlock_aria"}}"><button class="username unlock_label"></button></a>{{else}}<a href='/topic/lock/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}' class="mod_button"title="{{lang "topic.lock_tooltip"}}" aria-label="{{lang "topic.lock_aria"}}"><button class="username lock_label"></button></a>{{end}}{{end}}
{{if .CurrentUser.Perms.PinTopic}}{{if .Topic.Sticky}}<a class="mod_button" href='/topic/unstick/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}'title="{{lang "topic.unpin_tooltip"}}" aria-label="{{lang "topic.unpin_aria"}}"><button class="username unpin_label"></button></a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}' class="mod_button"title="{{lang "topic.pin_tooltip"}}" aria-label="{{lang "topic.pin_aria"}}"><button class="username pin_label"></button></a>{{end}}{{end}}
{{if .CurrentUser.Perms.ViewIPs}}<a class="mod_button" href='/users/ips/?ip={{.Topic.IP}}' title="{{lang "topic.ip_tooltip"}}" aria-label="The poster's IP is {{.Topic.IP}}"><button class="username ip_label"></button></a>{{end}}
{{if .CurrentUser.Perms.ViewIPs}}<a class="mod_button"href='/users/ips/?ip={{.Topic.IP}}'title="{{lang "topic.ip_tooltip"}}" aria-label="The poster's IP is {{.Topic.IP}}"><button class="username ip_label"></button></a>{{end}}
{{end}}
<a href="/report/submit/{{.Topic.ID}}?s={{.CurrentUser.Session}}&type=topic" class="mod_button report_item"title="{{lang "topic.flag_tooltip"}}" aria-label="{{lang "topic.flag_aria"}}" rel="nofollow"><button class="username flag_label"></button></a>

View File

@ -8,7 +8,7 @@
{{if .ForumList}}
<div class="opt filter_opt">
<a class="filter_opt_sep"> - </a>
<a href="#"class="filter_opt_label link_label"data-for="topic_list_filter_select">{{if eq .Sort.SortBy "mostviewed"}}{{lang "topic_list.most_viewed_filter"}}{{else}}{{lang "topic_list.most_recent_filter"}}{{end}} <span class="filter_opt_pointy"></span></a>
<a href="#"class="filter_opt_label link_label"data-for="topic_list_filter_select">{{if eq .Sort.SortBy "mostviewed"}}{{lang "topic_list.most_viewed_filter"}}{{else if eq .Sort.SortBy "weekviews"}}{{lang "topic_list.week_views_filter"}}{{else}}{{lang "topic_list.most_recent_filter"}}{{end}} <span class="filter_opt_pointy"></span></a>
<div id="topic_list_filter_select"class="link_select">
<div class="link_option link_selected">
<a class="link_recent"href="/topics/{{if .SelectedFids}}?fids={{range .SelectedFids}}{{.}}{{end}}{{end}}">{{lang "topic_list.most_recent_filter"}}</a>
@ -16,6 +16,9 @@
<div class="link_option">
<a class="link_most_viewed"href="/topics/most-viewed/{{if .SelectedFids}}?fids={{range .SelectedFids}}{{.}}{{end}}{{end}}">{{lang "topic_list.most_viewed_filter"}}</a>
</div>
<div class="link_option">
<a class="link_week_views"href="/topics/week-views/{{if .SelectedFids}}?fids={{range .SelectedFids}}{{.}}{{end}}{{end}}">{{lang "topic_list.week_views_filter"}}</a>
</div>
</div>
</div>
<div class="pre_opt auto_hide"></div>

View File

@ -97,7 +97,12 @@
{{if .CurrentUser.IsSuperAdmin}}<div class="rowitem passive">
<a href="/panel/backups/">{{lang "panel_menu_backups"}}</a>
</div>{{end}}
{{if .CurrentUser.IsAdmin}}<div class="rowitem passive">
{{if .CurrentUser.IsAdmin}}
<div class="rowitem passive">
<a href="/panel/debug/">{{lang "panel_menu_debug"}}</a>
</div>
{{if .DebugAdmin}}<div class="rowitem passive">
<a href="/panel/debug/tasks/">{{lang "panel_menu_debug"}}</a>
</div>{{end}}
{{end}}
</div>

View File

@ -1,8 +1,8 @@
<div class="topic_row{{if .Sticky}} topic_sticky{{else if .IsClosed}} topic_closed{{end}}{{if .CanMod}} can_mod{{end}}"data-tid={{.ID}}>
<div class="rowitem topic_left passive datarow">
<a href="{{.Creator.Link}}"><img src="{{.Creator.MicroAvatar}}"height=64 alt="Avatar"title="{{.Creator.Name}}'s Avatar"aria-hidden="true"></a>
<a href="{{.Creator.Link}}"><img src="{{.Creator.MicroAvatar}}"alt="Avatar"title="{{.Creator.Name}}'s Avatar"aria-hidden="true"height=64></a>
<span class="topic_inner_left">
<span class="rowtopic"itemprop="itemListElement"title="{{.Title}}"><a href="{{.Link}}">{{.Title}}</a>{{if .ForumName}}<a class="parent_forum_sep">-</a><a href="{{.ForumLink}}"title="{{.ForumName}}"class="rowsmall parent_forum">{{.ForumName}}</a>{{end}}</span>
<span class="rowtopic"itemprop="itemListElement"title="{{.Title}}"><a href="{{.Link}}">{{.Title}}{{if .ForumName}}</a><a class="parent_forum_sep">-</a><a href="{{.ForumLink}}"title="{{.ForumName}}"class="rowsmall parent_forum">{{.ForumName}}{{end}}</a></span>
<br><a class="rowsmall starter"href="{{.Creator.Link}}"title="{{.Creator.Name}}">{{.Creator.Name}}</a>
</span>
</div>
@ -11,11 +11,12 @@
<span class="replyCount">{{.PostCount}}&nbsp;{{lang "topic_list.replies_suffix"}}</span>
<span class="likeCount">{{.LikeCount}}&nbsp;{{lang "topic_list.likes_suffix"}}</span>
<span class="viewCount">{{.ViewCount}}&nbsp;{{lang "topic_list.views_suffix"}}</span>
<span class="weekViewCount">{{.WeekViews}}&nbsp;{{lang "topic_list.views_suffix"}}</span>
</div>
</div>
<div class="rowitem topic_right passive datarow">
<div class="topic_right_inside">
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}"height=64 alt="Avatar"title="{{.LastUser.Name}}'s Avatar"aria-hidden="true"></a>
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.MicroAvatar}}"alt="Avatar"title="{{.LastUser.Name}}'s Avatar"aria-hidden="true"height=64></a>
<span>
<a href="{{.LastUser.Link}}"class="lastName"title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
<a href="{{.Link}}?page={{.LastPage}}{{if .LastReplyID}}#post-{{.LastReplyID}}{{end}}"class="rowsmall lastReplyAt"title="{{abstime .LastReplyAt}}">{{reltime .LastReplyAt}}</a>

View File

@ -618,7 +618,9 @@ h2 {
.topic_inner_left br,
.topic_right_inside br,
.topic_inner_right,
.topic_list:not(.topic_list_weekviews) .topic_middle .weekViewCount,
.topic_list:not(.topic_list_mostviewed) .topic_middle .viewCount,
.topic_list_weekviews .topic_middle .likeCount,
.topic_list_mostviewed .topic_middle .likeCount {
display: none;
}