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 (
|
||||
"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}
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
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.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
65
main.go
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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}} {{lang "topic_list.replies_suffix"}}</span>
|
||||
<span class="likeCount">{{.LikeCount}} {{lang "topic_list.likes_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 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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue