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:
parent
43bace814d
commit
74e0ce492d
|
@ -2,11 +2,14 @@ package counters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
c "github.com/Azareal/Gosora/common"
|
c "github.com/Azareal/Gosora/common"
|
||||||
"github.com/Azareal/Gosora/query_gen"
|
qgen "github.com/Azareal/Gosora/query_gen"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,19 +22,40 @@ type DefaultTopicViewCounter struct {
|
||||||
oddLock sync.RWMutex
|
oddLock sync.RWMutex
|
||||||
evenLock sync.RWMutex
|
evenLock sync.RWMutex
|
||||||
|
|
||||||
|
weekState byte
|
||||||
|
|
||||||
update *sql.Stmt
|
update *sql.Stmt
|
||||||
|
resetOdd *sql.Stmt
|
||||||
|
resetEven *sql.Stmt
|
||||||
|
resetBoth *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultTopicViewCounter() (*DefaultTopicViewCounter, error) {
|
func NewDefaultTopicViewCounter() (*DefaultTopicViewCounter, error) {
|
||||||
acc := qgen.NewAcc()
|
acc := qgen.NewAcc()
|
||||||
|
t := "topics"
|
||||||
co := &DefaultTopicViewCounter{
|
co := &DefaultTopicViewCounter{
|
||||||
oddTopics: make(map[int]*RWMutexCounterBucket),
|
oddTopics: make(map[int]*RWMutexCounterBucket),
|
||||||
evenTopics: 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
|
err := co.WeekResetInit()
|
||||||
//c.AddScheduledSecondTask(co.Tick)
|
if err != nil {
|
||||||
c.AddShutdownTask(co.Tick)
|
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()
|
return co, acc.FirstError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,9 +74,9 @@ func (co *DefaultTopicViewCounter) Tick() error {
|
||||||
l.Lock()
|
l.Lock()
|
||||||
delete(m, topicID)
|
delete(m, topicID)
|
||||||
l.Unlock()
|
l.Unlock()
|
||||||
err := co.insertChunk(count, topicID)
|
e := co.insertChunk(count, topicID)
|
||||||
if err != nil {
|
if e != nil {
|
||||||
return errors.Wrap(errors.WithStack(err),"topicview counter")
|
return errors.Wrap(errors.WithStack(e), "topicview counter")
|
||||||
}
|
}
|
||||||
l.RLock()
|
l.RLock()
|
||||||
}
|
}
|
||||||
|
@ -66,14 +90,71 @@ func (co *DefaultTopicViewCounter) Tick() error {
|
||||||
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.
|
// 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 {
|
if count == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.DebugLogf("Inserting %d views into topic %d", count, topicID)
|
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 {
|
if err == sql.ErrNoRows {
|
||||||
return nil
|
return nil
|
||||||
} else if err != 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 ;)
|
// TODO: Add a way to disable this for extra speed ;)
|
||||||
tcache := c.Topics.GetCache()
|
tc := c.Topics.GetCache()
|
||||||
if tcache != nil {
|
if tc != nil {
|
||||||
topic, err := tcache.Get(topicID)
|
t, err := tc.Get(topicID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
atomic.AddInt64(&topic.ViewCount, int64(count))
|
atomic.AddInt64(&t.ViewCount, int64(count))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -99,12 +180,12 @@ func (co *DefaultTopicViewCounter) Bump(topicID int) {
|
||||||
// Is the ID even?
|
// Is the ID even?
|
||||||
if topicID%2 == 0 {
|
if topicID%2 == 0 {
|
||||||
co.evenLock.RLock()
|
co.evenLock.RLock()
|
||||||
topic, ok := co.evenTopics[topicID]
|
t, ok := co.evenTopics[topicID]
|
||||||
co.evenLock.RUnlock()
|
co.evenLock.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
topic.Lock()
|
t.Lock()
|
||||||
topic.counter++
|
t.counter++
|
||||||
topic.Unlock()
|
t.Unlock()
|
||||||
} else {
|
} else {
|
||||||
co.evenLock.Lock()
|
co.evenLock.Lock()
|
||||||
co.evenTopics[topicID] = &RWMutexCounterBucket{counter: 1}
|
co.evenTopics[topicID] = &RWMutexCounterBucket{counter: 1}
|
||||||
|
@ -114,12 +195,12 @@ func (co *DefaultTopicViewCounter) Bump(topicID int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
co.oddLock.RLock()
|
co.oddLock.RLock()
|
||||||
topic, ok := co.oddTopics[topicID]
|
t, ok := co.oddTopics[topicID]
|
||||||
co.oddLock.RUnlock()
|
co.oddLock.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
topic.Lock()
|
t.Lock()
|
||||||
topic.counter++
|
t.counter++
|
||||||
topic.Unlock()
|
t.Unlock()
|
||||||
} else {
|
} else {
|
||||||
co.oddLock.Lock()
|
co.oddLock.Lock()
|
||||||
co.oddTopics[topicID] = &RWMutexCounterBucket{counter: 1}
|
co.oddTopics[topicID] = &RWMutexCounterBucket{counter: 1}
|
||||||
|
|
|
@ -419,6 +419,7 @@ type BasePanelPage struct {
|
||||||
Stats PanelStats
|
Stats PanelStats
|
||||||
Zone string
|
Zone string
|
||||||
ReportForumID int
|
ReportForumID int
|
||||||
|
DebugAdmin bool
|
||||||
}
|
}
|
||||||
type PanelPage struct {
|
type PanelPage struct {
|
||||||
*BasePanelPage
|
*BasePanelPage
|
||||||
|
@ -789,6 +790,15 @@ type PanelDebugPage struct {
|
||||||
Disk DebugPageDisk
|
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 {
|
type PageSimple struct {
|
||||||
Title string
|
Title string
|
||||||
Something interface{}
|
Something interface{}
|
||||||
|
|
|
@ -63,11 +63,11 @@ func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultPollStore) Exists(id int) bool {
|
func (s *DefaultPollStore) Exists(id int) bool {
|
||||||
err := s.exists.QueryRow(id).Scan(&id)
|
e := s.exists.QueryRow(id).Scan(&id)
|
||||||
if err != nil && err != ErrNoRows {
|
if e != nil && e != ErrNoRows {
|
||||||
LogError(err)
|
LogError(e)
|
||||||
}
|
}
|
||||||
return err != ErrNoRows
|
return e != ErrNoRows
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultPollStore) Get(id int) (*Poll, error) {
|
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 {
|
func (s *DefaultPollStore) unpackOptionsMap(rawOptions map[int]string) []PollOption {
|
||||||
options := make([]PollOption, len(rawOptions))
|
opts := make([]PollOption, len(rawOptions))
|
||||||
for id, option := range rawOptions {
|
for id, opt := range rawOptions {
|
||||||
options[id] = PollOption{id, option}
|
opts[id] = PollOption{id, opt}
|
||||||
}
|
}
|
||||||
return options
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use a transaction for this
|
// 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?
|
// TODO: Move the option names into the polls_options table and get rid of this json sludge?
|
||||||
pollOptionsTxt, err := json.Marshal(pollOptions)
|
pollOptionsTxt, e := json.Marshal(pollOptions)
|
||||||
if err != nil {
|
if e != nil {
|
||||||
return 0, err
|
return 0, e
|
||||||
}
|
}
|
||||||
res, err := s.createPoll.Exec(parent.GetID(), parent.GetTable(), pollType, pollOptionsTxt)
|
res, e := s.createPoll.Exec(parent.GetID(), parent.GetTable(), pollType, pollOptionsTxt)
|
||||||
if err != nil {
|
if e != nil {
|
||||||
return 0, err
|
return 0, e
|
||||||
}
|
}
|
||||||
lastID, err := res.LastInsertId()
|
lastID, e := res.LastInsertId()
|
||||||
if err != nil {
|
if e != nil {
|
||||||
return 0, err
|
return 0, e
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(pollOptions); i++ {
|
for i := 0; i < len(pollOptions); i++ {
|
||||||
_, err := s.createPollOption.Exec(lastID, i)
|
_, e := s.createPollOption.Exec(lastID, i)
|
||||||
if err != nil {
|
if e != nil {
|
||||||
return 0, err
|
return 0, e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -227,7 +227,7 @@ func compileCommons(c *tmpl.CTemplateSet, head, head2 *Header, forumList []Forum
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
var topicsList []TopicsRowMut
|
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})
|
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}}
|
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)
|
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)
|
t.Add("profile", "c.ProfilePage", ppage)
|
||||||
|
|
||||||
var topicsList []TopicsRowMut
|
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})
|
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}}
|
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}}
|
convoListPage := ConvoListPage{header, cRows, Paginator{[]int{1}, 1, 1}}
|
||||||
t.AddStd("convos", "c.ConvoListPage", convoListPage)
|
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})
|
t.AddStd("panel", "c.Panel", Panel{basePage, "panel_dashboard_right", "", "panel_dashboard", inter})
|
||||||
ges := []GridElement{GridElement{"", "", "", 1, "grid_istat", "", "", ""}}
|
ges := []GridElement{GridElement{"", "", "", 1, "grid_istat", "", "", ""}}
|
||||||
t.AddStd("panel_dashboard", "c.DashGrids", DashGrids{ges, ges})
|
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))
|
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}
|
topicsRow := TopicsRowMut{&TopicsRow{topic, 0, user2, "", 0, user3, "General", "/forum/general.2"}, false}
|
||||||
t.AddStd("topics_topic", "c.TopicsRowMut", topicsRow)
|
t.AddStd("topics_topic", "c.TopicsRowMut", topicsRow)
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ type Topic struct {
|
||||||
PostCount int
|
PostCount int
|
||||||
LikeCount int
|
LikeCount int
|
||||||
AttachCount int
|
AttachCount int
|
||||||
|
WeekViews int
|
||||||
ClassName string // CSS Class Name
|
ClassName string // CSS Class Name
|
||||||
Poll int
|
Poll int
|
||||||
Data string // Used for report metadata
|
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
|
tu.Tag = Groups.DirtyGet(tu.Group).Tag
|
||||||
|
|
||||||
if tcache != nil {
|
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}
|
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)
|
//log.Printf("theTopic: %+v\n", theTopic)
|
||||||
_ = tcache.Set(&theTopic)
|
_ = tcache.Set(&theTopic)
|
||||||
|
|
|
@ -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?
|
// 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)
|
reqUserList := make(map[int]bool)
|
||||||
for rows.Next() {
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, Paginator{nil, 1, 1}, err
|
return nil, Paginator{nil, 1, 1}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.ParentID = f.ID
|
|
||||||
t.Link = BuildTopicURL(NameToSlug(t.Title), t.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
|
// 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)
|
_, _, lastPage := PageOffset(t.PostCount, 1, Config.ItemsPerPage)
|
||||||
|
@ -575,9 +574,9 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
|
||||||
_, week := now.ISOWeek()
|
_, week := now.ISOWeek()
|
||||||
day := int(now.Weekday()) + 1
|
day := int(now.Weekday()) + 1
|
||||||
if week%2 == 0 { // is even?
|
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 {
|
} 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)
|
topicCount, err = ArgQToWeekViewTopicCount(argList, qlist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -596,7 +595,7 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
|
||||||
tList.qLock.RUnlock()
|
tList.qLock.RUnlock()
|
||||||
if stmt == nil {
|
if stmt == nil {
|
||||||
orderq = "views DESC,lastReplyAt DESC,createdBy DESC"
|
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:
|
default:
|
||||||
tList.qLock2.RLock()
|
tList.qLock2.RLock()
|
||||||
|
@ -604,7 +603,7 @@ func (tList *DefaultTopicList) getList(page, orderby, topicCount int, argList []
|
||||||
tList.qLock2.RUnlock()
|
tList.qLock2.RUnlock()
|
||||||
if stmt == nil {
|
if stmt == nil {
|
||||||
orderq = "sticky DESC,lastReplyAt DESC,createdBy DESC"
|
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)
|
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
|
// TODO: Embed Topic structs in TopicsRow to make it easier for us to reuse this work in the topic cache
|
||||||
t := TopicsRow{}
|
t := TopicsRow{}
|
||||||
//var weekViews []uint8
|
//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 {
|
if err != nil {
|
||||||
return nil, Paginator{nil, 1, 1}, err
|
return nil, Paginator{nil, 1, 1}, err
|
||||||
}
|
}
|
||||||
|
//t.WeekViews = int(weekViews[0])
|
||||||
|
//log.Printf("t: %+v\n", t)
|
||||||
//log.Printf("weekViews: %+v\n", weekViews)
|
//log.Printf("weekViews: %+v\n", weekViews)
|
||||||
|
|
||||||
t.Link = BuildTopicURL(NameToSlug(t.Title), t.ID)
|
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...
|
// Avoid the extra queries on topic list pages, if we already have what we want...
|
||||||
hRids := false
|
hRids := false
|
||||||
if tc != nil {
|
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
|
hRids = len(t.Rids) != 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
989
gen_router.go
989
gen_router.go
File diff suppressed because it is too large
Load Diff
|
@ -658,6 +658,7 @@
|
||||||
"topic_list.changed_topics":"Click to see %d new or changed topics",
|
"topic_list.changed_topics":"Click to see %d new or changed topics",
|
||||||
"topic_list.most_recent_filter":"Most Recent",
|
"topic_list.most_recent_filter":"Most Recent",
|
||||||
"topic_list.most_viewed_filter":"Most Viewed",
|
"topic_list.most_viewed_filter":"Most Viewed",
|
||||||
|
"topic_list.week_views_filter":"Week Views",
|
||||||
"topic_list.replies_suffix":"replies",
|
"topic_list.replies_suffix":"replies",
|
||||||
"topic_list.likes_suffix":"likes",
|
"topic_list.likes_suffix":"likes",
|
||||||
"topic_list.views_suffix":"views",
|
"topic_list.views_suffix":"views",
|
||||||
|
@ -806,7 +807,7 @@
|
||||||
|
|
||||||
"error_head":"An error has occurred",
|
"error_head":"An error has occurred",
|
||||||
"footer_thingymawhatsit":"Can you please keep the powered by notice? ;)",
|
"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_made_with_love":"Made with love by Azareal",
|
||||||
"footer_theme_selector_aria":"Change the site's appearance",
|
"footer_theme_selector_aria":"Change the site's appearance",
|
||||||
|
|
||||||
|
|
65
main.go
65
main.go
|
@ -14,6 +14,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
@ -278,6 +279,12 @@ func storeInit() (err error) {
|
||||||
return errors.WithStack(err)
|
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")
|
log.Print("Initialising the view counters")
|
||||||
if !c.Config.DisableAnalytics {
|
if !c.Config.DisableAnalytics {
|
||||||
co.GlobalViewCounter, err = co.NewGlobalViewCounter(acc)
|
co.GlobalViewCounter, err = co.NewGlobalViewCounter(acc)
|
||||||
|
@ -332,12 +339,6 @@ func storeInit() (err error) {
|
||||||
return errors.WithStack(err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,8 +346,7 @@ func storeInit() (err error) {
|
||||||
func main() {
|
func main() {
|
||||||
// TODO: Recover from panics
|
// TODO: Recover from panics
|
||||||
/*defer func() {
|
/*defer func() {
|
||||||
r := recover()
|
if r := recover(); r != nil {
|
||||||
if r != nil {
|
|
||||||
log.Print(r)
|
log.Print(r)
|
||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
return
|
return
|
||||||
|
@ -374,6 +374,11 @@ func main() {
|
||||||
pprof.StartCPUProfile(f)
|
pprof.StartCPUProfile(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = mime.AddExtensionType(".avif", "image/avif")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
jsToken, err := c.GenerateSafeString(80)
|
jsToken, err := c.GenerateSafeString(80)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -440,6 +445,7 @@ func main() {
|
||||||
defer watcher.Close()
|
defer watcher.Close()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
var ErrFileSkip = errors.New("skip mod file")
|
||||||
modifiedFileEvent := func(path string) error {
|
modifiedFileEvent := func(path string) error {
|
||||||
pathBits := strings.Split(path, "\\")
|
pathBits := strings.Split(path, "\\")
|
||||||
if len(pathBits) == 0 {
|
if len(pathBits) == 0 {
|
||||||
|
@ -458,23 +464,27 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return ErrFileSkip
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Expand this to more types of files
|
// TODO: Expand this to more types of files
|
||||||
var err error
|
var err error
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event := <-watcher.Events:
|
case ev := <-watcher.Events:
|
||||||
// TODO: Handle file deletes (and renames more graciously by removing the old version of it)
|
// TODO: Handle file deletes (and renames more graciously by removing the old version of it)
|
||||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
if ev.Op&fsnotify.Write == fsnotify.Write {
|
||||||
log.Println("modified file:", event.Name)
|
err = modifiedFileEvent(ev.Name)
|
||||||
err = modifiedFileEvent(event.Name)
|
if err != ErrFileSkip {
|
||||||
} else if event.Op&fsnotify.Create == fsnotify.Create {
|
log.Println("modified file:", ev.Name)
|
||||||
log.Println("new file:", event.Name)
|
|
||||||
err = modifiedFileEvent(event.Name)
|
|
||||||
} else {
|
} 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
|
err = nil
|
||||||
}
|
}
|
||||||
if 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")
|
log.Print("Checking for init tasks")
|
||||||
err = sched()
|
if err = sched(); err != nil {
|
||||||
if err != nil {
|
|
||||||
c.LogError(err)
|
c.LogError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,9 +532,9 @@ func main() {
|
||||||
|
|
||||||
// Resource Management Goroutine
|
// Resource Management Goroutine
|
||||||
go func() {
|
go func() {
|
||||||
ucache := c.Users.GetCache()
|
uc := c.Users.GetCache()
|
||||||
tcache := c.Topics.GetCache()
|
tc := c.Topics.GetCache()
|
||||||
if ucache == nil && tcache == nil {
|
if uc == nil && tc == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -532,13 +545,13 @@ func main() {
|
||||||
select {
|
select {
|
||||||
case <-secondTicker.C:
|
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?
|
// 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 {
|
if uc != nil {
|
||||||
ucap := ucache.GetCapacity()
|
ucap := uc.GetCapacity()
|
||||||
if ucache.Length() <= ucap || c.Users.Count() <= ucap {
|
if uc.Length() <= ucap || c.Users.Count() <= ucap {
|
||||||
couldNotDealloc = false
|
couldNotDealloc = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lastEvictedCount = ucache.DeallocOverflow(couldNotDealloc)
|
lastEvictedCount = uc.DeallocOverflow(couldNotDealloc)
|
||||||
couldNotDealloc = (lastEvictedCount == 0)
|
couldNotDealloc = (lastEvictedCount == 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ func init() {
|
||||||
addPatch(32, patch32)
|
addPatch(32, patch32)
|
||||||
addPatch(33, patch33)
|
addPatch(33, patch33)
|
||||||
addPatch(34, patch34)
|
addPatch(34, patch34)
|
||||||
//addPatch(35, patch35)
|
addPatch(35, patch35)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bcol(col string, val bool) qgen.DBTableColumn {
|
func bcol(col string, val bool) qgen.DBTableColumn {
|
||||||
|
@ -937,3 +937,11 @@ func patch34(scanner *bufio.Scanner) error {
|
||||||
|
|
||||||
return nil
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -893,7 +893,11 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
agent = {{.AllAgentMap.internetexplorer}}
|
agent = {{.AllAgentMap.internetexplorer}}
|
||||||
}
|
}
|
||||||
case {{.AllAgentMap.zgrab}}:
|
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 {
|
if agent == 0 {
|
||||||
|
|
|
@ -28,6 +28,7 @@ func routes(r *Router) {
|
||||||
topicGroup := newRouteGroup("/topics/",
|
topicGroup := newRouteGroup("/topics/",
|
||||||
View("routes.TopicList", "/topics/"),
|
View("routes.TopicList", "/topics/"),
|
||||||
View("routes.TopicListMostViewed", "/topics/most-viewed/"),
|
View("routes.TopicListMostViewed", "/topics/most-viewed/"),
|
||||||
|
View("routes.TopicListWeekViews", "/topics/week-views/"),
|
||||||
MView("routes.CreateTopic", "/topics/create/", "extraData"),
|
MView("routes.CreateTopic", "/topics/create/", "extraData"),
|
||||||
)
|
)
|
||||||
r.AddGroup(topicGroup)
|
r.AddGroup(topicGroup)
|
||||||
|
@ -276,5 +277,6 @@ func panelRoutes() *RouteGroup {
|
||||||
View("panel.LogsMod", "/panel/logs/mod/"),
|
View("panel.LogsMod", "/panel/logs/mod/"),
|
||||||
View("panel.LogsAdmin", "/panel/logs/admin/"),
|
View("panel.LogsAdmin", "/panel/logs/admin/"),
|
||||||
View("panel.Debug", "/panel/debug/").Before("AdminOnly"),
|
View("panel.Debug", "/panel/debug/").Before("AdminOnly"),
|
||||||
|
View("panel.DebugTasks", "/panel/debug/tasks/").Before("AdminOnly"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ func buildBasePage(w http.ResponseWriter, r *http.Request, u *c.User, titlePhras
|
||||||
return nil, ferr
|
return nil, ferr
|
||||||
}
|
}
|
||||||
h.Title = p.GetTitlePhrase("panel_" + titlePhrase)
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ type dashStmts struct {
|
||||||
// TODO: Stop hard-coding these queries
|
// TODO: Stop hard-coding these queries
|
||||||
func dashMySQLStmts() (stmts dashStmts, err error) {
|
func dashMySQLStmts() (stmts dashStmts, err error) {
|
||||||
db := qgen.Builder.GetConn()
|
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 {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -38,11 +38,11 @@ func dashMySQLStmts() (stmts dashStmts, err error) {
|
||||||
return stmt
|
return stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
stmts.todaysPostCount = prepareStmt("replies", "", "day")
|
stmts.todaysPostCount = prepStmt("replies", "", "day")
|
||||||
stmts.todaysTopicCount = prepareStmt("topics", "", "day")
|
stmts.todaysTopicCount = prepStmt("topics", "", "day")
|
||||||
stmts.todaysNewUserCount = prepareStmt("users", "", "day")
|
stmts.todaysNewUserCount = prepStmt("users", "", "day")
|
||||||
stmts.todaysTopicCountByForum = prepareStmt("topics", " and parentID = ?", "day")
|
stmts.todaysTopicCountByForum = prepStmt("topics", " and parentID=?", "day")
|
||||||
stmts.weeklyTopicCountByForum = prepareStmt("topics", " and parentID = ?", "week")
|
stmts.weeklyTopicCountByForum = prepStmt("topics", " and parentID=?", "week")
|
||||||
|
|
||||||
return stmts, err
|
return stmts, err
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ func dashMySQLStmts() (stmts dashStmts, err error) {
|
||||||
// TODO: Stop hard-coding these queries
|
// TODO: Stop hard-coding these queries
|
||||||
func dashMSSQLStmts() (stmts dashStmts, err error) {
|
func dashMSSQLStmts() (stmts dashStmts, err error) {
|
||||||
db := qgen.Builder.GetConn()
|
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 {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -59,11 +59,11 @@ func dashMSSQLStmts() (stmts dashStmts, err error) {
|
||||||
return stmt
|
return stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
stmts.todaysPostCount = prepareStmt("replies", "", "DAY")
|
stmts.todaysPostCount = prepStmt("replies", "", "DAY")
|
||||||
stmts.todaysTopicCount = prepareStmt("topics", "", "DAY")
|
stmts.todaysTopicCount = prepStmt("topics", "", "DAY")
|
||||||
stmts.todaysNewUserCount = prepareStmt("users", "", "DAY")
|
stmts.todaysNewUserCount = prepStmt("users", "", "DAY")
|
||||||
stmts.todaysTopicCountByForum = prepareStmt("topics", " and parentID = ?", "DAY")
|
stmts.todaysTopicCountByForum = prepStmt("topics", " and parentID=?", "DAY")
|
||||||
stmts.weeklyTopicCountByForum = prepareStmt("topics", " and parentID = ?", "WEEK")
|
stmts.weeklyTopicCountByForum = prepStmt("topics", " and parentID=?", "WEEK")
|
||||||
|
|
||||||
return stmts, err
|
return stmts, err
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ func Dashboard(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteErro
|
||||||
cpustr := unknown
|
cpustr := unknown
|
||||||
var cpuColour string
|
var cpuColour string
|
||||||
|
|
||||||
lessThanSwitch := func(number int, lowerBound int, midBound int) string {
|
lessThanSwitch := func(number, lowerBound, midBound int) string {
|
||||||
switch {
|
switch {
|
||||||
case number < lowerBound:
|
case number < lowerBound:
|
||||||
return "stat_green"
|
return "stat_green"
|
||||||
|
@ -120,7 +120,7 @@ func Dashboard(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteErro
|
||||||
runtime.ReadMemStats(&m)
|
runtime.ReadMemStats(&m)
|
||||||
memCount, memUnit := c.ConvertByteUnit(float64(m.Sys))
|
memCount, memUnit := c.ConvertByteUnit(float64(m.Sys))
|
||||||
|
|
||||||
greaterThanSwitch := func(number int, lowerBound int, midBound int) string {
|
greaterThanSwitch := func(number, lowerBound, midBound int) string {
|
||||||
switch {
|
switch {
|
||||||
case number > midBound:
|
case number > midBound:
|
||||||
return "stat_green"
|
return "stat_green"
|
||||||
|
@ -174,11 +174,11 @@ func Dashboard(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteErro
|
||||||
}
|
}
|
||||||
|
|
||||||
grid1 := []GE{}
|
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})
|
grid1 = append(grid1, GE{id, href, body, order, class, back, textColour, tooltip})
|
||||||
}
|
}
|
||||||
gridElements := []GE{}
|
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})
|
gridElements = append(gridElements, GE{id, href, body, order, class, back, textColour, tooltip})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}
|
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})
|
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})
|
||||||
|
}
|
||||||
|
|
|
@ -17,12 +17,12 @@ func Plugins(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
|
||||||
return c.NoPermissions(w, r, u)
|
return c.NoPermissions(w, r, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
var pluginList []interface{}
|
var plList []interface{}
|
||||||
for _, plugin := range c.Plugins {
|
for _, pl := range c.Plugins {
|
||||||
pluginList = append(pluginList, plugin)
|
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
|
// TODO: Abstract more of the plugin activation / installation / deactivation logic, so we can test all that more reliably and easily
|
||||||
|
|
|
@ -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)
|
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
|
// TODO: Implement search
|
||||||
func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header, torder string, tsorder int) c.RouteError {
|
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")
|
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)
|
return c.LocalError("Something weird happened", w, r, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var forumList []c.Forum
|
||||||
// Get the current page
|
// Get the current page
|
||||||
page, _ := strconv.Atoi(r.FormValue("page"))
|
page, _ := strconv.Atoi(r.FormValue("page"))
|
||||||
sfids := r.FormValue("fids")
|
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)
|
fids = append(fids, fid)
|
||||||
}
|
}
|
||||||
if len(fids) == 1 {
|
if len(fids) == 1 {
|
||||||
forum, err := c.Forums.Get(fids[0])
|
f, err := c.Forums.Get(fids[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.LocalError("Invalid fid forum", w, r, user)
|
return c.LocalError("Invalid fid forum", w, r, user)
|
||||||
}
|
}
|
||||||
h.Title = forum.Name
|
h.Title = f.Name
|
||||||
h.ZoneID = forum.ID
|
h.ZoneID = f.ID
|
||||||
|
forumList = append(forumList, *f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Allow multiple forums in searches
|
// TODO: Allow multiple forums in searches
|
||||||
// TODO: Simplify this block after initially landing search
|
// TODO: Simplify this block after initially landing search
|
||||||
var topicList []*c.TopicsRow
|
var topicList []*c.TopicsRow
|
||||||
var forumList []c.Forum
|
|
||||||
var pagi c.Paginator
|
var pagi c.Paginator
|
||||||
var canDelete, ccanDelete, canLock, ccanLock, canMove, ccanMove bool
|
var canDelete, ccanDelete, canLock, ccanLock, canMove, ccanMove bool
|
||||||
q := r.FormValue("q")
|
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
|
// 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
|
var fps map[int]c.QuickTools
|
||||||
if user.IsSuperAdmin {
|
if user.IsSuperAdmin {
|
||||||
|
//log.Print("user.IsSuperAdmin")
|
||||||
topicList, forumList, pagi, err = c.TopicList.GetList(page, tsorder, fids)
|
topicList, forumList, pagi, err = c.TopicList.GetList(page, tsorder, fids)
|
||||||
canLock, canMove = true, true
|
canLock, canMove = true, true
|
||||||
} else {
|
} else {
|
||||||
|
//log.Print("!user.IsSuperAdmin")
|
||||||
topicList, forumList, pagi, err = c.TopicList.GetListByGroup(group, page, tsorder, fids)
|
topicList, forumList, pagi, err = c.TopicList.GetListByGroup(group, page, tsorder, fids)
|
||||||
fps = make(map[int]c.QuickTools)
|
fps = make(map[int]c.QuickTools)
|
||||||
for _, f := range forumList {
|
for _, f := range forumList {
|
||||||
|
@ -283,6 +295,8 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.InternalError(err, w, r)
|
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
|
// TODO: Reduce the amount of boilerplate here
|
||||||
if r.FormValue("js") == "1" {
|
if r.FormValue("js") == "1" {
|
||||||
|
|
|
@ -97,7 +97,12 @@
|
||||||
{{if .CurrentUser.IsSuperAdmin}}<div class="rowitem passive">
|
{{if .CurrentUser.IsSuperAdmin}}<div class="rowitem passive">
|
||||||
<a href="/panel/backups/">{{lang "panel_menu_backups"}}</a>
|
<a href="/panel/backups/">{{lang "panel_menu_backups"}}</a>
|
||||||
</div>{{end}}
|
</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>
|
<a href="/panel/debug/">{{lang "panel_menu_debug"}}</a>
|
||||||
</div>{{end}}
|
</div>
|
||||||
|
{{if .DebugAdmin}}<div class="rowitem passive">
|
||||||
|
<a href="/panel/debug/tasks/">{{lang "panel_menu_debug"}}</a>
|
||||||
|
</div>{{end}}
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
|
@ -5,7 +5,7 @@
|
||||||
<a href="{{.CurrentUser.Link}}"class="the_name"rel="author">{{.CurrentUser.Name}}</a>
|
<a href="{{.CurrentUser.Link}}"class="the_name"rel="author">{{.CurrentUser.Name}}</a>
|
||||||
<div class="tag_block">
|
<div class="tag_block">
|
||||||
<div class="tag_pre"></div>
|
<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 class="tag_post"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<a href="{{.UserLink}}"class="the_name"rel="author">{{.CreatedByName}}</a>
|
<a href="{{.UserLink}}"class="the_name"rel="author">{{.CreatedByName}}</a>
|
||||||
<div class="tag_block">
|
<div class="tag_block">
|
||||||
<div class="tag_pre"></div>
|
<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 class="tag_post"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
{{if .ForumList}}
|
{{if .ForumList}}
|
||||||
<div class="opt filter_opt">
|
<div class="opt filter_opt">
|
||||||
<a class="filter_opt_sep"> - </a>
|
<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 id="topic_list_filter_select"class="link_select">
|
||||||
<div class="link_option link_selected">
|
<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>
|
<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">
|
<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>
|
<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>
|
||||||
|
<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>
|
</div>
|
||||||
<div class="pre_opt auto_hide"></div>
|
<div class="pre_opt auto_hide"></div>
|
||||||
|
|
|
@ -97,7 +97,12 @@
|
||||||
{{if .CurrentUser.IsSuperAdmin}}<div class="rowitem passive">
|
{{if .CurrentUser.IsSuperAdmin}}<div class="rowitem passive">
|
||||||
<a href="/panel/backups/">{{lang "panel_menu_backups"}}</a>
|
<a href="/panel/backups/">{{lang "panel_menu_backups"}}</a>
|
||||||
</div>{{end}}
|
</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>
|
<a href="/panel/debug/">{{lang "panel_menu_debug"}}</a>
|
||||||
</div>{{end}}
|
</div>
|
||||||
|
{{if .DebugAdmin}}<div class="rowitem passive">
|
||||||
|
<a href="/panel/debug/tasks/">{{lang "panel_menu_debug"}}</a>
|
||||||
|
</div>{{end}}
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
|
@ -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="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">
|
<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="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>
|
<br><a class="rowsmall starter"href="{{.Creator.Link}}"title="{{.Creator.Name}}">{{.Creator.Name}}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,11 +11,12 @@
|
||||||
<span class="replyCount">{{.PostCount}} {{lang "topic_list.replies_suffix"}}</span>
|
<span class="replyCount">{{.PostCount}} {{lang "topic_list.replies_suffix"}}</span>
|
||||||
<span class="likeCount">{{.LikeCount}} {{lang "topic_list.likes_suffix"}}</span>
|
<span class="likeCount">{{.LikeCount}} {{lang "topic_list.likes_suffix"}}</span>
|
||||||
<span class="viewCount">{{.ViewCount}} {{lang "topic_list.views_suffix"}}</span>
|
<span class="viewCount">{{.ViewCount}} {{lang "topic_list.views_suffix"}}</span>
|
||||||
|
<span class="weekViewCount">{{.WeekViews}} {{lang "topic_list.views_suffix"}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rowitem topic_right passive datarow">
|
<div class="rowitem topic_right passive datarow">
|
||||||
<div class="topic_right_inside">
|
<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>
|
<span>
|
||||||
<a href="{{.LastUser.Link}}"class="lastName"title="{{.LastUser.Name}}">{{.LastUser.Name}}</a><br>
|
<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>
|
<a href="{{.Link}}?page={{.LastPage}}{{if .LastReplyID}}#post-{{.LastReplyID}}{{end}}"class="rowsmall lastReplyAt"title="{{abstime .LastReplyAt}}">{{reltime .LastReplyAt}}</a>
|
||||||
|
|
|
@ -618,7 +618,9 @@ h2 {
|
||||||
.topic_inner_left br,
|
.topic_inner_left br,
|
||||||
.topic_right_inside br,
|
.topic_right_inside br,
|
||||||
.topic_inner_right,
|
.topic_inner_right,
|
||||||
|
.topic_list:not(.topic_list_weekviews) .topic_middle .weekViewCount,
|
||||||
.topic_list:not(.topic_list_mostviewed) .topic_middle .viewCount,
|
.topic_list:not(.topic_list_mostviewed) .topic_middle .viewCount,
|
||||||
|
.topic_list_weekviews .topic_middle .likeCount,
|
||||||
.topic_list_mostviewed .topic_middle .likeCount {
|
.topic_list_mostviewed .topic_middle .likeCount {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue