The Cosora Theme is almost complete and is being rolled out on the site to demo it.

We now track the views on a per-route basis. We have plans for an admin UI for this, global views, etc. which will come in a future commit.

The local error JSON is now properly formed.
Fixed an outdated line in topic.go which was using the old cache system.
We now use fuzzy dates for relative times between three months ago and a year ago.
Added the super admin middleware and the associated tests.
Added the route column to the viewchunks table.
Added more alt attributes to images.
Added a few missing ARIA attributes.

Began refactoring the route generator to use text/template instead of generating everything procedurally.
Began work on per-topic view counts.
This commit is contained in:
Azareal 2017-12-19 03:53:13 +00:00
parent abfe0a472a
commit 10a0c62823
39 changed files with 1440 additions and 473 deletions

View File

@ -107,3 +107,15 @@ func debugLogf(str string, args ...interface{}) {
log.Printf(str, args...) log.Printf(str, args...)
} }
} }
// TODO: Make a neater API for this
var routeMapEnum map[string]int
var reverseRouteMapEnum map[int]string
func SetRouteMapEnum(rme map[string]int) {
routeMapEnum = rme
}
func SetReverseRouteMapEnum(rrme map[int]string) {
reverseRouteMapEnum = rrme
}

View File

@ -2,32 +2,35 @@ package common
import ( import (
"database/sql" "database/sql"
"log"
"sync"
"sync/atomic" "sync/atomic"
"../query_gen/lib" "../query_gen/lib"
) )
var GlobalViewCounter *BufferedViewCounter var GlobalViewCounter *ChunkedViewCounter
var RouteViewCounter *RouteViewCounterImpl
type BufferedViewCounter struct { type ChunkedViewCounter struct {
buckets [2]int64 buckets [2]int64
currentBucket int64 currentBucket int64
insert *sql.Stmt insert *sql.Stmt
} }
func NewGlobalViewCounter() (*BufferedViewCounter, error) { func NewChunkedViewCounter() (*ChunkedViewCounter, error) {
acc := qgen.Builder.Accumulator() acc := qgen.Builder.Accumulator()
counter := &BufferedViewCounter{ counter := &ChunkedViewCounter{
currentBucket: 0, currentBucket: 0,
insert: acc.SimpleInsert("viewchunks", "count, createdAt", "?,UTC_TIMESTAMP()"), insert: acc.Insert("viewchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(),
} }
//AddScheduledFifteenMinuteTask(counter.Tick) AddScheduledFifteenMinuteTask(counter.Tick) // This is run once every fifteen minutes to match the frequency of the RouteViewCounter
AddScheduledSecondTask(counter.Tick) //AddScheduledSecondTask(counter.Tick)
return counter, acc.FirstError() return counter, acc.FirstError()
} }
func (counter *BufferedViewCounter) Tick() (err error) { func (counter *ChunkedViewCounter) Tick() (err error) {
var oldBucket = counter.currentBucket var oldBucket = counter.currentBucket
var nextBucket int64 var nextBucket int64
if counter.currentBucket == 1 { if counter.currentBucket == 1 {
@ -38,22 +41,17 @@ func (counter *BufferedViewCounter) Tick() (err error) {
atomic.AddInt64(&counter.buckets[oldBucket], counter.buckets[nextBucket]) atomic.AddInt64(&counter.buckets[oldBucket], counter.buckets[nextBucket])
atomic.StoreInt64(&counter.buckets[nextBucket], 0) atomic.StoreInt64(&counter.buckets[nextBucket], 0)
atomic.StoreInt64(&counter.currentBucket, nextBucket) atomic.StoreInt64(&counter.currentBucket, nextBucket)
/*debugLog("counter.buckets[nextBucket]: ", counter.buckets[nextBucket])
debugLog("counter.buckets[oldBucket]: ", counter.buckets[oldBucket])
debugLog("counter.currentBucket:", counter.currentBucket)
debugLog("oldBucket:", oldBucket)
debugLog("nextBucket:", nextBucket)*/
var previousViewChunk = counter.buckets[oldBucket] var previousViewChunk = counter.buckets[oldBucket]
atomic.AddInt64(&counter.buckets[oldBucket], -previousViewChunk) atomic.AddInt64(&counter.buckets[oldBucket], -previousViewChunk)
return counter.insertChunk(previousViewChunk) return counter.insertChunk(previousViewChunk)
} }
func (counter *BufferedViewCounter) Bump() { func (counter *ChunkedViewCounter) Bump() {
atomic.AddInt64(&counter.buckets[counter.currentBucket], 1) atomic.AddInt64(&counter.buckets[counter.currentBucket], 1)
} }
func (counter *BufferedViewCounter) insertChunk(count int64) error { func (counter *ChunkedViewCounter) insertChunk(count int64) error {
if count == 0 { if count == 0 {
return nil return nil
} }
@ -61,3 +59,194 @@ func (counter *BufferedViewCounter) insertChunk(count int64) error {
_, err := counter.insert.Exec(count) _, err := counter.insert.Exec(count)
return err return err
} }
type RWMutexCounterBucket struct {
counter int
sync.RWMutex
}
// The name of the struct clashes with the name of the variable, so we're adding Impl to the end
type RouteViewCounterImpl struct {
routeBuckets []*RWMutexCounterBucket //[RouteID]count
insert *sql.Stmt
}
func NewRouteViewCounter() (*RouteViewCounterImpl, error) {
acc := qgen.Builder.Accumulator()
var routeBuckets = make([]*RWMutexCounterBucket, len(routeMapEnum))
for bucketID, _ := range routeBuckets {
routeBuckets[bucketID] = &RWMutexCounterBucket{counter: 0}
}
counter := &RouteViewCounterImpl{
routeBuckets: routeBuckets,
insert: acc.Insert("viewchunks").Columns("count, createdAt, route").Fields("?,UTC_TIMESTAMP(),?").Prepare(),
}
AddScheduledFifteenMinuteTask(counter.Tick) // There could be a lot of routes, so we don't want to be running this every second
//AddScheduledSecondTask(counter.Tick)
return counter, acc.FirstError()
}
func (counter *RouteViewCounterImpl) Tick() error {
for routeID, routeBucket := range counter.routeBuckets {
var count int
routeBucket.RLock()
count = routeBucket.counter
routeBucket.counter = 0
routeBucket.RUnlock()
err := counter.insertChunk(count, routeID) // TODO: Bulk insert for speed?
if err != nil {
return err
}
}
return nil
}
func (counter *RouteViewCounterImpl) insertChunk(count int, route int) error {
if count == 0 {
return nil
}
var routeName = reverseRouteMapEnum[route]
debugLogf("Inserting a viewchunk with a count of %d for route %s (%d)", count, routeName, route)
_, err := counter.insert.Exec(count, routeName)
return err
}
func (counter *RouteViewCounterImpl) Bump(route int) {
// TODO: Test this check
log.Print("counter.routeBuckets[route]: ", counter.routeBuckets[route])
if len(counter.routeBuckets) <= route {
return
}
counter.routeBuckets[route].Lock()
counter.routeBuckets[route].counter++
counter.routeBuckets[route].Unlock()
}
// TODO: The ForumViewCounter and TopicViewCounter
// TODO: Unload forum counters without any views over the past 15 minutes, if the admin has configured the forumstore with a cap and it's been hit?
// Forums can be reloaded from the database at any time, so we want to keep the counters separate from them
type ForumViewCounter struct {
buckets [2]int64
currentBucket int64
}
/*func (counter *ForumViewCounter) insertChunk(count int, forum int) error {
if count == 0 {
return nil
}
debugLogf("Inserting a viewchunk with a count of %d for forum %d", count, forum)
_, err := counter.insert.Exec(count, forum)
return err
}*/
// TODO: Use two odd-even maps for now, and move to something more concurrent later, maybe a sharded map?
type TopicViewCounter struct {
oddTopics map[int]*RWMutexCounterBucket // map[tid]struct{counter,sync.RWMutex}
evenTopics map[int]*RWMutexCounterBucket
oddLock sync.RWMutex
evenLock sync.RWMutex
update *sql.Stmt
}
func NewTopicViewCounter() (*TopicViewCounter, error) {
acc := qgen.Builder.Accumulator()
counter := &TopicViewCounter{
oddTopics: make(map[int]*RWMutexCounterBucket),
evenTopics: make(map[int]*RWMutexCounterBucket),
update: acc.Update("topics").Set("views = ?").Where("tid = ?").Prepare(), // TODO: Add the views column to the topics table
}
AddScheduledFifteenMinuteTask(counter.Tick) // There could be a lot of routes, so we don't want to be running this every second
//AddScheduledSecondTask(counter.Tick)
return counter, acc.FirstError()
}
func (counter *TopicViewCounter) Tick() error {
counter.oddLock.RLock()
for topicID, topic := range counter.oddTopics {
var count int
topic.RLock()
count = topic.counter
topic.RUnlock()
err := counter.insertChunk(count, topicID)
if err != nil {
return err
}
}
counter.oddLock.RUnlock()
counter.evenLock.RLock()
for topicID, topic := range counter.evenTopics {
var count int
topic.RLock()
count = topic.counter
topic.RUnlock()
err := counter.insertChunk(count, topicID)
if err != nil {
return err
}
}
counter.evenLock.RUnlock()
return nil
}
func (counter *TopicViewCounter) insertChunk(count int, topicID int) error {
if count == 0 {
return nil
}
debugLogf("Inserting %d views into topic %d", count, topicID)
_, err := counter.update.Exec(count, topicID)
return err
}
func (counter *TopicViewCounter) Bump(topicID int) {
// Is the ID even?
if topicID%2 == 0 {
counter.evenLock.Lock()
topic, ok := counter.evenTopics[topicID]
counter.evenLock.Unlock()
if ok {
topic.Lock()
topic.counter++
topic.Unlock()
} else {
counter.evenLock.Lock()
counter.evenTopics[topicID] = &RWMutexCounterBucket{counter: 1}
counter.evenLock.Unlock()
}
return
}
counter.oddLock.Lock()
topic, ok := counter.oddTopics[topicID]
counter.oddLock.Unlock()
if ok {
topic.Lock()
topic.counter++
topic.Unlock()
} else {
counter.oddLock.Lock()
counter.oddTopics[topicID] = &RWMutexCounterBucket{counter: 1}
counter.oddLock.Unlock()
}
}
type TreeCounterNode struct {
Value int64
Zero *TreeCounterNode
One *TreeCounterNode
Parent *TreeCounterNode
}
// MEGA EXPERIMENTAL. Start from the right-most bits in the integer and move leftwards
type TreeTopicViewCounter struct {
zero *TreeCounterNode
one *TreeCounterNode
}
func (counter *TreeTopicViewCounter) Bump(topicID int64) {
}

View File

@ -26,6 +26,7 @@ func SendValidationEmail(username string, email string, token string) bool {
} }
// TODO: Refactor this // TODO: Refactor this
// TODO: Add support for TLS
func SendEmail(email string, subject string, msg string) bool { func SendEmail(email string, subject string, msg string) bool {
// This hook is useful for plugin_sendmail or for testing tools. Possibly to hook it into some sort of mail server? // This hook is useful for plugin_sendmail or for testing tools. Possibly to hook it into some sort of mail server?
if Vhooks["email_send_intercept"] != nil { if Vhooks["email_send_intercept"] != nil {
@ -68,6 +69,6 @@ func SendEmail(email string, subject string, msg string) bool {
if err != nil { if err != nil {
return false return false
} }
err = con.Quit()
return err == nil return con.Quit() == nil
} }

View File

@ -136,7 +136,7 @@ func LocalErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, user U
func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteError { func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteError {
w.WriteHeader(500) w.WriteHeader(500)
_, _ = w.Write([]byte(`{'errmsg': '` + errmsg + `'}`)) _, _ = w.Write([]byte(`{"errmsg": "` + errmsg + `"}`))
return HandledRouteError() return HandledRouteError()
} }

View File

@ -284,6 +284,14 @@ func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) {
return *usercpy, true return *usercpy, true
} }
// SuperAdminOnly makes sure that only super admin can access certain critical panel routes
func SuperAdminOnly(w http.ResponseWriter, r *http.Request, user User) RouteError {
if !user.IsSuperAdmin {
return NoPermissions(w, r, user)
}
return nil
}
// AdminOnly makes sure that only admins can access certain panel routes // AdminOnly makes sure that only admins can access certain panel routes
func AdminOnly(w http.ResponseWriter, r *http.Request, user User) RouteError { func AdminOnly(w http.ResponseWriter, r *http.Request, user User) RouteError {
if !user.IsAdmin { if !user.IsAdmin {

View File

@ -59,6 +59,7 @@ type config struct {
SMTPUsername string SMTPUsername string
SMTPPassword string SMTPPassword string
SMTPPort string SMTPPort string
//SMTPEnableTLS bool
DefaultRoute func(http.ResponseWriter, *http.Request, User) RouteError DefaultRoute func(http.ResponseWriter, *http.Request, User) RouteError
DefaultGroup int DefaultGroup int

View File

@ -149,7 +149,7 @@ func init() {
// Flush the topic out of the cache // Flush the topic out of the cache
// ? - We do a CacheRemove() here instead of mutating the pointer to avoid creating a race condition // ? - We do a CacheRemove() here instead of mutating the pointer to avoid creating a race condition
func (topic *Topic) cacheRemove() { func (topic *Topic) cacheRemove() {
tcache := Topics.(TopicCache) tcache := Topics.GetCache()
if tcache != nil { if tcache != nil {
tcache.Remove(topic.ID) tcache.Remove(topic.ID)
} }

View File

@ -73,9 +73,12 @@ func RelativeTime(t time.Time) string {
weeks := int(hours / 24 / 7) weeks := int(hours / 24 / 7)
months := int(hours / 24 / 31) months := int(hours / 24 / 31)
switch { switch {
case months > 11: case months > 3:
//return t.Format("Mon Jan 2 2006") if t.Year() != time.Now().Year() {
return t.Format("Jan 2 2006") //return t.Format("Mon Jan 2 2006")
return t.Format("Jan 2 2006")
}
return t.Format("Jan 2")
case months > 1: case months > 1:
return fmt.Sprintf("%d months ago", months) return fmt.Sprintf("%d months ago", months)
case months == 1: case months == 1:

View File

@ -13,7 +13,8 @@ import (
) )
var ErrNoRoute = errors.New("That route doesn't exist.") var ErrNoRoute = errors.New("That route doesn't exist.")
var RouteMap = map[string]interface{}{ // TODO: What about the /uploads/ route? x.x
var RouteMap = map[string]interface{}{
"routeAPI": routeAPI, "routeAPI": routeAPI,
"routeOverview": routeOverview, "routeOverview": routeOverview,
"routeForums": routeForums, "routeForums": routeForums,
@ -72,9 +73,133 @@ var RouteMap = map[string]interface{}{
"routeIps": routeIps, "routeIps": routeIps,
} }
// ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS
var routeMapEnum = map[string]int{
"routeAPI": 0,
"routeOverview": 1,
"routeForums": 2,
"routeForum": 3,
"routeChangeTheme": 4,
"routeShowAttachment": 5,
"routeReportSubmit": 6,
"routeTopicCreate": 7,
"routeTopics": 8,
"routePanelForums": 9,
"routePanelForumsCreateSubmit": 10,
"routePanelForumsDelete": 11,
"routePanelForumsDeleteSubmit": 12,
"routePanelForumsEdit": 13,
"routePanelForumsEditSubmit": 14,
"routePanelForumsEditPermsSubmit": 15,
"routePanelSettings": 16,
"routePanelSettingEdit": 17,
"routePanelSettingEditSubmit": 18,
"routePanelWordFilters": 19,
"routePanelWordFiltersCreate": 20,
"routePanelWordFiltersEdit": 21,
"routePanelWordFiltersEditSubmit": 22,
"routePanelWordFiltersDeleteSubmit": 23,
"routePanelThemes": 24,
"routePanelThemesSetDefault": 25,
"routePanelPlugins": 26,
"routePanelPluginsActivate": 27,
"routePanelPluginsDeactivate": 28,
"routePanelPluginsInstall": 29,
"routePanelUsers": 30,
"routePanelUsersEdit": 31,
"routePanelUsersEditSubmit": 32,
"routePanelGroups": 33,
"routePanelGroupsEdit": 34,
"routePanelGroupsEditPerms": 35,
"routePanelGroupsEditSubmit": 36,
"routePanelGroupsEditPermsSubmit": 37,
"routePanelGroupsCreateSubmit": 38,
"routePanelBackups": 39,
"routePanelLogsMod": 40,
"routePanelDebug": 41,
"routePanel": 42,
"routeAccountEditCritical": 43,
"routeAccountEditCriticalSubmit": 44,
"routeAccountEditAvatar": 45,
"routeAccountEditAvatarSubmit": 46,
"routeAccountEditUsername": 47,
"routeAccountEditUsernameSubmit": 48,
"routeAccountEditEmail": 49,
"routeAccountEditEmailTokenSubmit": 50,
"routeProfile": 51,
"routeBanSubmit": 52,
"routeUnban": 53,
"routeActivate": 54,
"routeIps": 55,
}
var reverseRouteMapEnum = map[int]string{
0: "routeAPI",
1: "routeOverview",
2: "routeForums",
3: "routeForum",
4: "routeChangeTheme",
5: "routeShowAttachment",
6: "routeReportSubmit",
7: "routeTopicCreate",
8: "routeTopics",
9: "routePanelForums",
10: "routePanelForumsCreateSubmit",
11: "routePanelForumsDelete",
12: "routePanelForumsDeleteSubmit",
13: "routePanelForumsEdit",
14: "routePanelForumsEditSubmit",
15: "routePanelForumsEditPermsSubmit",
16: "routePanelSettings",
17: "routePanelSettingEdit",
18: "routePanelSettingEditSubmit",
19: "routePanelWordFilters",
20: "routePanelWordFiltersCreate",
21: "routePanelWordFiltersEdit",
22: "routePanelWordFiltersEditSubmit",
23: "routePanelWordFiltersDeleteSubmit",
24: "routePanelThemes",
25: "routePanelThemesSetDefault",
26: "routePanelPlugins",
27: "routePanelPluginsActivate",
28: "routePanelPluginsDeactivate",
29: "routePanelPluginsInstall",
30: "routePanelUsers",
31: "routePanelUsersEdit",
32: "routePanelUsersEditSubmit",
33: "routePanelGroups",
34: "routePanelGroupsEdit",
35: "routePanelGroupsEditPerms",
36: "routePanelGroupsEditSubmit",
37: "routePanelGroupsEditPermsSubmit",
38: "routePanelGroupsCreateSubmit",
39: "routePanelBackups",
40: "routePanelLogsMod",
41: "routePanelDebug",
42: "routePanel",
43: "routeAccountEditCritical",
44: "routeAccountEditCriticalSubmit",
45: "routeAccountEditAvatar",
46: "routeAccountEditAvatarSubmit",
47: "routeAccountEditUsername",
48: "routeAccountEditUsernameSubmit",
49: "routeAccountEditEmail",
50: "routeAccountEditEmailTokenSubmit",
51: "routeProfile",
52: "routeBanSubmit",
53: "routeUnban",
54: "routeActivate",
55: "routeIps",
}
// TODO: Stop spilling these into the package scope?
func init() {
common.SetRouteMapEnum(routeMapEnum)
common.SetReverseRouteMapEnum(reverseRouteMapEnum)
}
type GenRouter struct { type GenRouter struct {
UploadHandler func(http.ResponseWriter, *http.Request) UploadHandler func(http.ResponseWriter, *http.Request)
extra_routes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError extraRoutes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError
sync.RWMutex sync.RWMutex
} }
@ -82,7 +207,7 @@ type GenRouter struct {
func NewGenRouter(uploads http.Handler) *GenRouter { func NewGenRouter(uploads http.Handler) *GenRouter {
return &GenRouter{ return &GenRouter{
UploadHandler: http.StripPrefix("/uploads/",uploads).ServeHTTP, UploadHandler: http.StripPrefix("/uploads/",uploads).ServeHTTP,
extra_routes: make(map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError), extraRoutes: make(map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError),
} }
} }
@ -104,17 +229,17 @@ func (router *GenRouter) Handle(_ string, _ http.Handler) {
func (router *GenRouter) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request, common.User) common.RouteError) { func (router *GenRouter) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request, common.User) common.RouteError) {
router.Lock() router.Lock()
defer router.Unlock() defer router.Unlock()
router.extra_routes[pattern] = handle router.extraRoutes[pattern] = handle
} }
func (router *GenRouter) RemoveFunc(pattern string) error { func (router *GenRouter) RemoveFunc(pattern string) error {
router.Lock() router.Lock()
defer router.Unlock() defer router.Unlock()
_, ok := router.extra_routes[pattern] _, ok := router.extraRoutes[pattern]
if !ok { if !ok {
return ErrNoRoute return ErrNoRoute
} }
delete(router.extra_routes, pattern) delete(router.extraRoutes, pattern)
return nil return nil
} }
@ -125,10 +250,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
var prefix, extra_data string var prefix, extraData string
prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 1] prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 1]
if req.URL.Path[len(req.URL.Path) - 1] != '/' { if req.URL.Path[len(req.URL.Path) - 1] != '/' {
extra_data = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:] extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1] req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
} }
@ -136,12 +261,12 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("before routeStatic") log.Print("before routeStatic")
log.Print("prefix: ", prefix) log.Print("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extra_data: ", extra_data) log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer()) log.Print("req.Referer(): ", req.Referer())
} }
if prefix == "/static" { if prefix == "/static" {
req.URL.Path += extra_data req.URL.Path += extraData
routeStatic(w, req) routeStatic(w, req)
return return
} }
@ -159,27 +284,32 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
if common.Dev.SuperDebug { if common.Dev.SuperDebug {
log.Print("after PreRoute") log.Print("after PreRoute")
log.Print("routeMapEnum: ", routeMapEnum)
} }
var err common.RouteError var err common.RouteError
switch(prefix) { switch(prefix) {
case "/api": case "/api":
common.RouteViewCounter.Bump(0)
err = routeAPI(w,req,user) err = routeAPI(w,req,user)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
case "/overview": case "/overview":
common.RouteViewCounter.Bump(1)
err = routeOverview(w,req,user) err = routeOverview(w,req,user)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
case "/forums": case "/forums":
common.RouteViewCounter.Bump(2)
err = routeForums(w,req,user) err = routeForums(w,req,user)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
case "/forum": case "/forum":
err = routeForum(w,req,user,extra_data) common.RouteViewCounter.Bump(3)
err = routeForum(w,req,user,extraData)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
@ -190,6 +320,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(4)
err = routeChangeTheme(w,req,user) err = routeChangeTheme(w,req,user)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
@ -201,7 +332,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routeShowAttachment(w,req,user,extra_data) common.RouteViewCounter.Bump(5)
err = routeShowAttachment(w,req,user,extraData)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
@ -226,7 +358,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routeReportSubmit(w,req,user,extra_data) common.RouteViewCounter.Bump(6)
err = routeReportSubmit(w,req,user,extraData)
} }
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
@ -240,8 +373,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routeTopicCreate(w,req,user,extra_data) common.RouteViewCounter.Bump(7)
err = routeTopicCreate(w,req,user,extraData)
default: default:
common.RouteViewCounter.Bump(8)
err = routeTopics(w,req,user) err = routeTopics(w,req,user)
} }
if err != nil { if err != nil {
@ -256,6 +391,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch(req.URL.Path) { switch(req.URL.Path) {
case "/panel/forums/": case "/panel/forums/":
common.RouteViewCounter.Bump(9)
err = routePanelForums(w,req,user) err = routePanelForums(w,req,user)
case "/panel/forums/create/": case "/panel/forums/create/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -264,6 +400,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(10)
err = routePanelForumsCreateSubmit(w,req,user) err = routePanelForumsCreateSubmit(w,req,user)
case "/panel/forums/delete/": case "/panel/forums/delete/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -272,7 +409,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelForumsDelete(w,req,user,extra_data) common.RouteViewCounter.Bump(11)
err = routePanelForumsDelete(w,req,user,extraData)
case "/panel/forums/delete/submit/": case "/panel/forums/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -280,9 +418,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelForumsDeleteSubmit(w,req,user,extra_data) common.RouteViewCounter.Bump(12)
err = routePanelForumsDeleteSubmit(w,req,user,extraData)
case "/panel/forums/edit/": case "/panel/forums/edit/":
err = routePanelForumsEdit(w,req,user,extra_data) common.RouteViewCounter.Bump(13)
err = routePanelForumsEdit(w,req,user,extraData)
case "/panel/forums/edit/submit/": case "/panel/forums/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -290,7 +430,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelForumsEditSubmit(w,req,user,extra_data) common.RouteViewCounter.Bump(14)
err = routePanelForumsEditSubmit(w,req,user,extraData)
case "/panel/forums/edit/perms/submit/": case "/panel/forums/edit/perms/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -298,11 +439,14 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelForumsEditPermsSubmit(w,req,user,extra_data) common.RouteViewCounter.Bump(15)
err = routePanelForumsEditPermsSubmit(w,req,user,extraData)
case "/panel/settings/": case "/panel/settings/":
common.RouteViewCounter.Bump(16)
err = routePanelSettings(w,req,user) err = routePanelSettings(w,req,user)
case "/panel/settings/edit/": case "/panel/settings/edit/":
err = routePanelSettingEdit(w,req,user,extra_data) common.RouteViewCounter.Bump(17)
err = routePanelSettingEdit(w,req,user,extraData)
case "/panel/settings/edit/submit/": case "/panel/settings/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -310,8 +454,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelSettingEditSubmit(w,req,user,extra_data) common.RouteViewCounter.Bump(18)
err = routePanelSettingEditSubmit(w,req,user,extraData)
case "/panel/settings/word-filters/": case "/panel/settings/word-filters/":
common.RouteViewCounter.Bump(19)
err = routePanelWordFilters(w,req,user) err = routePanelWordFilters(w,req,user)
case "/panel/settings/word-filters/create/": case "/panel/settings/word-filters/create/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -320,9 +466,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(20)
err = routePanelWordFiltersCreate(w,req,user) err = routePanelWordFiltersCreate(w,req,user)
case "/panel/settings/word-filters/edit/": case "/panel/settings/word-filters/edit/":
err = routePanelWordFiltersEdit(w,req,user,extra_data) common.RouteViewCounter.Bump(21)
err = routePanelWordFiltersEdit(w,req,user,extraData)
case "/panel/settings/word-filters/edit/submit/": case "/panel/settings/word-filters/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -330,7 +478,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelWordFiltersEditSubmit(w,req,user,extra_data) common.RouteViewCounter.Bump(22)
err = routePanelWordFiltersEditSubmit(w,req,user,extraData)
case "/panel/settings/word-filters/delete/submit/": case "/panel/settings/word-filters/delete/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -338,8 +487,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelWordFiltersDeleteSubmit(w,req,user,extra_data) common.RouteViewCounter.Bump(23)
err = routePanelWordFiltersDeleteSubmit(w,req,user,extraData)
case "/panel/themes/": case "/panel/themes/":
common.RouteViewCounter.Bump(24)
err = routePanelThemes(w,req,user) err = routePanelThemes(w,req,user)
case "/panel/themes/default/": case "/panel/themes/default/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -348,8 +499,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelThemesSetDefault(w,req,user,extra_data) common.RouteViewCounter.Bump(25)
err = routePanelThemesSetDefault(w,req,user,extraData)
case "/panel/plugins/": case "/panel/plugins/":
common.RouteViewCounter.Bump(26)
err = routePanelPlugins(w,req,user) err = routePanelPlugins(w,req,user)
case "/panel/plugins/activate/": case "/panel/plugins/activate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -358,7 +511,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelPluginsActivate(w,req,user,extra_data) common.RouteViewCounter.Bump(27)
err = routePanelPluginsActivate(w,req,user,extraData)
case "/panel/plugins/deactivate/": case "/panel/plugins/deactivate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -366,7 +520,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelPluginsDeactivate(w,req,user,extra_data) common.RouteViewCounter.Bump(28)
err = routePanelPluginsDeactivate(w,req,user,extraData)
case "/panel/plugins/install/": case "/panel/plugins/install/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -374,11 +529,14 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelPluginsInstall(w,req,user,extra_data) common.RouteViewCounter.Bump(29)
err = routePanelPluginsInstall(w,req,user,extraData)
case "/panel/users/": case "/panel/users/":
common.RouteViewCounter.Bump(30)
err = routePanelUsers(w,req,user) err = routePanelUsers(w,req,user)
case "/panel/users/edit/": case "/panel/users/edit/":
err = routePanelUsersEdit(w,req,user,extra_data) common.RouteViewCounter.Bump(31)
err = routePanelUsersEdit(w,req,user,extraData)
case "/panel/users/edit/submit/": case "/panel/users/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -386,13 +544,17 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelUsersEditSubmit(w,req,user,extra_data) common.RouteViewCounter.Bump(32)
err = routePanelUsersEditSubmit(w,req,user,extraData)
case "/panel/groups/": case "/panel/groups/":
common.RouteViewCounter.Bump(33)
err = routePanelGroups(w,req,user) err = routePanelGroups(w,req,user)
case "/panel/groups/edit/": case "/panel/groups/edit/":
err = routePanelGroupsEdit(w,req,user,extra_data) common.RouteViewCounter.Bump(34)
err = routePanelGroupsEdit(w,req,user,extraData)
case "/panel/groups/edit/perms/": case "/panel/groups/edit/perms/":
err = routePanelGroupsEditPerms(w,req,user,extra_data) common.RouteViewCounter.Bump(35)
err = routePanelGroupsEditPerms(w,req,user,extraData)
case "/panel/groups/edit/submit/": case "/panel/groups/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -400,7 +562,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelGroupsEditSubmit(w,req,user,extra_data) common.RouteViewCounter.Bump(36)
err = routePanelGroupsEditSubmit(w,req,user,extraData)
case "/panel/groups/edit/perms/submit/": case "/panel/groups/edit/perms/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -408,7 +571,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routePanelGroupsEditPermsSubmit(w,req,user,extra_data) common.RouteViewCounter.Bump(37)
err = routePanelGroupsEditPermsSubmit(w,req,user,extraData)
case "/panel/groups/create/": case "/panel/groups/create/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -416,10 +580,19 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(38)
err = routePanelGroupsCreateSubmit(w,req,user) err = routePanelGroupsCreateSubmit(w,req,user)
case "/panel/backups/": case "/panel/backups/":
err = routePanelBackups(w,req,user,extra_data) err = common.SuperAdminOnly(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
common.RouteViewCounter.Bump(39)
err = routePanelBackups(w,req,user,extraData)
case "/panel/logs/mod/": case "/panel/logs/mod/":
common.RouteViewCounter.Bump(40)
err = routePanelLogsMod(w,req,user) err = routePanelLogsMod(w,req,user)
case "/panel/debug/": case "/panel/debug/":
err = common.AdminOnly(w,req,user) err = common.AdminOnly(w,req,user)
@ -428,8 +601,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(41)
err = routePanelDebug(w,req,user) err = routePanelDebug(w,req,user)
default: default:
common.RouteViewCounter.Bump(42)
err = routePanel(w,req,user) err = routePanel(w,req,user)
} }
if err != nil { if err != nil {
@ -444,6 +619,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(43)
err = routeAccountEditCritical(w,req,user) err = routeAccountEditCritical(w,req,user)
case "/user/edit/critical/submit/": case "/user/edit/critical/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -458,6 +634,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(44)
err = routeAccountEditCriticalSubmit(w,req,user) err = routeAccountEditCriticalSubmit(w,req,user)
case "/user/edit/avatar/": case "/user/edit/avatar/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -466,6 +643,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(45)
err = routeAccountEditAvatar(w,req,user) err = routeAccountEditAvatar(w,req,user)
case "/user/edit/avatar/submit/": case "/user/edit/avatar/submit/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -474,6 +652,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(46)
err = routeAccountEditAvatarSubmit(w,req,user) err = routeAccountEditAvatarSubmit(w,req,user)
case "/user/edit/username/": case "/user/edit/username/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -482,6 +661,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(47)
err = routeAccountEditUsername(w,req,user) err = routeAccountEditUsername(w,req,user)
case "/user/edit/username/submit/": case "/user/edit/username/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -496,6 +676,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(48)
err = routeAccountEditUsernameSubmit(w,req,user) err = routeAccountEditUsernameSubmit(w,req,user)
case "/user/edit/email/": case "/user/edit/email/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -504,6 +685,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(49)
err = routeAccountEditEmail(w,req,user) err = routeAccountEditEmail(w,req,user)
case "/user/edit/token/": case "/user/edit/token/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -518,9 +700,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
err = routeAccountEditEmailTokenSubmit(w,req,user,extra_data) common.RouteViewCounter.Bump(50)
err = routeAccountEditEmailTokenSubmit(w,req,user,extraData)
default: default:
req.URL.Path += extra_data req.URL.Path += extraData
common.RouteViewCounter.Bump(51)
err = routeProfile(w,req,user) err = routeProfile(w,req,user)
} }
if err != nil { if err != nil {
@ -541,6 +725,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(52)
err = routeBanSubmit(w,req,user) err = routeBanSubmit(w,req,user)
case "/users/unban/": case "/users/unban/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -555,6 +740,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(53)
err = routeUnban(w,req,user) err = routeUnban(w,req,user)
case "/users/activate/": case "/users/activate/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
@ -569,6 +755,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(54)
err = routeActivate(w,req,user) err = routeActivate(w,req,user)
case "/users/ips/": case "/users/ips/":
err = common.MemberOnly(w,req,user) err = common.MemberOnly(w,req,user)
@ -577,50 +764,51 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
common.RouteViewCounter.Bump(55)
err = routeIps(w,req,user) err = routeIps(w,req,user)
} }
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
case "/uploads": case "/uploads":
if extra_data == "" { if extraData == "" {
common.NotFound(w,req) common.NotFound(w,req)
return return
} }
req.URL.Path += extra_data req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this? // TODO: Find a way to propagate errors up from this?
router.UploadHandler(w,req) router.UploadHandler(w,req) // TODO: Count these views
case "": case "":
// Stop the favicons, robots.txt file, etc. resolving to the topics list // Stop the favicons, robots.txt file, etc. resolving to the topics list
// TODO: Add support for favicons and robots.txt files // TODO: Add support for favicons and robots.txt files
switch(extra_data) { switch(extraData) {
case "robots.txt": case "robots.txt":
err = routeRobotsTxt(w,req) err = routeRobotsTxt(w,req) // TODO: Count these views
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
return return
} }
if extra_data != "" { if extraData != "" {
common.NotFound(w,req) common.NotFound(w,req)
return return
} }
common.Config.DefaultRoute(w,req,user) common.Config.DefaultRoute(w,req,user) // TODO: Count these views
default: default:
// A fallback for the routes which haven't been converted to the new router yet or plugins // A fallback for the routes which haven't been converted to the new router yet or plugins
router.RLock() router.RLock()
handle, ok := router.extra_routes[req.URL.Path] handle, ok := router.extraRoutes[req.URL.Path]
router.RUnlock() router.RUnlock()
if ok { if ok {
req.URL.Path += extra_data req.URL.Path += extraData
err = handle(w,req,user) err = handle(w,req,user) // TODO: Count these views
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
return return
} }
common.NotFound(w,req) common.NotFound(w,req) // TODO: Collect all the error view counts so we can add a replacement for GlobalViewCounter by adding up the view counts of every route? Complex and may be inaccurate, collecting it globally and locally would at-least help find places we aren't capturing views
} }
} }

View File

@ -80,7 +80,11 @@ func afterDBInit() (err error) {
if err != nil { if err != nil {
return err return err
} }
common.GlobalViewCounter, err = common.NewGlobalViewCounter() common.GlobalViewCounter, err = common.NewChunkedViewCounter()
if err != nil {
return err
}
common.RouteViewCounter, err = common.NewRouteViewCounter()
if err != nil { if err != nil {
return err return err
} }

View File

@ -478,6 +478,28 @@ func TestPermsMiddleware(t *testing.T) {
expect(t, ferr == nil, "Logged in users should be able to access member areas") expect(t, ferr == nil, "Logged in users should be able to access member areas")
// TODO: Loop over the /user/ routes and make sure only members can access the ones other than /user/username // TODO: Loop over the /user/ routes and make sure only members can access the ones other than /user/username
// TODO: Write tests for AdminOnly()
user = common.BlankUser()
ferr = common.SuperAdminOnly(dummyResponseRecorder, dummyRequest, *user)
expect(t, ferr != nil, "Blank users shouldn't be considered super admins")
user.IsSuperAdmin = false
ferr = common.SuperAdminOnly(dummyResponseRecorder, dummyRequest, *user)
expect(t, ferr != nil, "Non-super admins shouldn't be allowed through the super admin gate")
user.IsSuperAdmin = true
ferr = common.SuperAdminOnly(dummyResponseRecorder, dummyRequest, *user)
expect(t, ferr == nil, "Super admins should be allowed through super admin gates")
// TODO: Make sure only super admins can access the backups route
//dummyResponseRecorder = httptest.NewRecorder()
//bytesBuffer = bytes.NewBuffer([]byte(""))
//dummyRequest = httptest.NewRequest("", "/panel/backups/", bytesBuffer)
} }
func TestTopicStore(t *testing.T) { func TestTopicStore(t *testing.T) {

View File

@ -1569,9 +1569,6 @@ func routePanelBackups(w http.ResponseWriter, r *http.Request, user common.User,
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.IsSuperAdmin {
return common.NoPermissions(w, r, user)
}
if backupURL != "" { if backupURL != "" {
// We don't want them trying to break out of this directory, it shouldn't hurt since it's a super admin, but it's always good to practice good security hygiene, especially if this is one of many instances on a managed server not controlled by the superadmin/s // We don't want them trying to break out of this directory, it shouldn't hurt since it's a super admin, but it's always good to practice good security hygiene, especially if this is one of many instances on a managed server not controlled by the superadmin/s

View File

@ -159,6 +159,7 @@ func createTables(adapter qgen.Adapter) error {
qgen.DBTableColumn{"postCount", "int", 0, false, false, "1"}, qgen.DBTableColumn{"postCount", "int", 0, false, false, "1"},
qgen.DBTableColumn{"likeCount", "int", 0, false, false, "0"}, qgen.DBTableColumn{"likeCount", "int", 0, false, false, "0"},
qgen.DBTableColumn{"words", "int", 0, false, false, "0"}, qgen.DBTableColumn{"words", "int", 0, false, false, "0"},
//qgen.DBTableColumn{"views", "int", 0, false, false, "0"},
qgen.DBTableColumn{"css_class", "varchar", 100, false, false, "''"}, qgen.DBTableColumn{"css_class", "varchar", 100, false, false, "''"},
qgen.DBTableColumn{"data", "varchar", 200, false, false, "''"}, qgen.DBTableColumn{"data", "varchar", 200, false, false, "''"},
}, },
@ -358,10 +359,22 @@ func createTables(adapter qgen.Adapter) error {
[]qgen.DBTableColumn{ []qgen.DBTableColumn{
qgen.DBTableColumn{"count", "int", 0, false, false, "0"}, qgen.DBTableColumn{"count", "int", 0, false, false, "0"},
qgen.DBTableColumn{"createdAt", "datetime", 0, false, false, ""}, qgen.DBTableColumn{"createdAt", "datetime", 0, false, false, ""},
qgen.DBTableColumn{"route", "varchar", 200, false, false, ""},
}, },
[]qgen.DBTableKey{}, []qgen.DBTableKey{},
) )
/*
qgen.Install.CreateTable("viewchunks_forums", "", "",
[]qgen.DBTableColumn{
qgen.DBTableColumn{"count", "int", 0, false, false, "0"},
qgen.DBTableColumn{"createdAt", "datetime", 0, false, false, ""},
qgen.DBTableColumn{"forum", "int", 0, false, false, ""},
},
[]qgen.DBTableKey{},
)
*/
qgen.Install.CreateTable("sync", "", "", qgen.Install.CreateTable("sync", "", "",
[]qgen.DBTableColumn{ []qgen.DBTableColumn{
qgen.DBTableColumn{"last_update", "datetime", 0, false, false, ""}, qgen.DBTableColumn{"last_update", "datetime", 0, false, false, ""},

View File

@ -2,25 +2,40 @@
package main package main
import ( import (
"bytes"
"log" "log"
"os" "os"
"strconv"
"text/template"
) )
//import "strings"
var routeList []*RouteImpl var routeList []*RouteImpl
var routeGroups []*RouteGroup var routeGroups []*RouteGroup
type TmplVars struct {
RouteList []*RouteImpl
RouteGroups []*RouteGroup
AllRouteNames []string
AllRouteMap map[string]int
}
func main() { func main() {
log.Println("Generating the router...") log.Println("Generating the router...")
// Load all the routes... // Load all the routes...
routes() routes()
var out, routeMap string var tmplVars = TmplVars{
RouteList: routeList,
RouteGroups: routeGroups,
}
var allRouteNames []string
var allRouteMap = make(map[string]int)
var out string
var mapIt = func(name string) { var mapIt = func(name string) {
routeMap += "\t\"" + name + "\": " + name + ",\n" allRouteNames = append(allRouteNames, name)
//reverseRouteMap += "\t" + name + ": \"" + name + "\",\n" allRouteMap[name] = len(allRouteNames) - 1
} }
var countToIndents = func(indent int) (indentor string) { var countToIndents = func(indent int) (indentor string) {
for i := 0; i < indent; i++ { for i := 0; i < indent; i++ {
@ -47,9 +62,11 @@ func main() {
} }
for _, route := range routeList { for _, route := range routeList {
mapIt(route.Name)
var end = len(route.Path) - 1 var end = len(route.Path) - 1
out += "\n\t\tcase \"" + route.Path[0:end] + "\":" out += "\n\t\tcase \"" + route.Path[0:end] + "\":"
out += runBefore(route.RunBefore, 4) out += runBefore(route.RunBefore, 4)
out += "\n\t\t\tcommon.RouteViewCounter.Bump(" + strconv.Itoa(allRouteMap[route.Name]) + ")"
out += "\n\t\t\terr = " + route.Name + "(w,req,user" out += "\n\t\t\terr = " + route.Name + "(w,req,user"
for _, item := range route.Vars { for _, item := range route.Vars {
out += "," + item out += "," + item
@ -58,7 +75,6 @@ func main() {
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
}` }`
mapIt(route.Name)
} }
for _, group := range routeGroups { for _, group := range routeGroups {
@ -73,6 +89,7 @@ func main() {
defaultRoute = route defaultRoute = route
continue continue
} }
mapIt(route.Name)
out += "\n\t\t\t\tcase \"" + route.Path + "\":" out += "\n\t\t\t\tcase \"" + route.Path + "\":"
if len(route.RunBefore) > 0 { if len(route.RunBefore) > 0 {
@ -107,23 +124,24 @@ func main() {
} }
} }
} }
out += "\n\t\t\t\t\tcommon.RouteViewCounter.Bump(" + strconv.Itoa(allRouteMap[route.Name]) + ")"
out += "\n\t\t\t\t\terr = " + route.Name + "(w,req,user" out += "\n\t\t\t\t\terr = " + route.Name + "(w,req,user"
for _, item := range route.Vars { for _, item := range route.Vars {
out += "," + item out += "," + item
} }
out += ")" out += ")"
mapIt(route.Name)
} }
if defaultRoute.Name != "" { if defaultRoute.Name != "" {
mapIt(defaultRoute.Name)
out += "\n\t\t\t\tdefault:" out += "\n\t\t\t\tdefault:"
out += runBefore(defaultRoute.RunBefore, 4) out += runBefore(defaultRoute.RunBefore, 4)
out += "\n\t\t\t\t\tcommon.RouteViewCounter.Bump(" + strconv.Itoa(allRouteMap[defaultRoute.Name]) + ")"
out += "\n\t\t\t\t\terr = " + defaultRoute.Name + "(w,req,user" out += "\n\t\t\t\t\terr = " + defaultRoute.Name + "(w,req,user"
for _, item := range defaultRoute.Vars { for _, item := range defaultRoute.Vars {
out += ", " + item out += ", " + item
} }
out += ")" out += ")"
mapIt(defaultRoute.Name)
} }
out += ` out += `
} }
@ -132,6 +150,9 @@ func main() {
}` }`
} }
tmplVars.AllRouteNames = allRouteNames
tmplVars.AllRouteMap = allRouteMap
var fileData = `// Code generated by. DO NOT EDIT. var fileData = `// Code generated by. DO NOT EDIT.
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
@ -147,12 +168,28 @@ import (
) )
var ErrNoRoute = errors.New("That route doesn't exist.") var ErrNoRoute = errors.New("That route doesn't exist.")
var RouteMap = map[string]interface{}{ // TODO: What about the /uploads/ route? x.x
` + routeMap + `} var RouteMap = map[string]interface{}{ {{range .AllRouteNames}}
"{{.}}": {{.}},{{end}}
}
// ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS
var routeMapEnum = map[string]int{ {{range $index, $element := .AllRouteNames}}
"{{$element}}": {{$index}},{{end}}
}
var reverseRouteMapEnum = map[int]string{ {{range $index, $element := .AllRouteNames}}
{{$index}}: "{{$element}}",{{end}}
}
// TODO: Stop spilling these into the package scope?
func init() {
common.SetRouteMapEnum(routeMapEnum)
common.SetReverseRouteMapEnum(reverseRouteMapEnum)
}
type GenRouter struct { type GenRouter struct {
UploadHandler func(http.ResponseWriter, *http.Request) UploadHandler func(http.ResponseWriter, *http.Request)
extra_routes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError extraRoutes map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError
sync.RWMutex sync.RWMutex
} }
@ -160,7 +197,7 @@ type GenRouter struct {
func NewGenRouter(uploads http.Handler) *GenRouter { func NewGenRouter(uploads http.Handler) *GenRouter {
return &GenRouter{ return &GenRouter{
UploadHandler: http.StripPrefix("/uploads/",uploads).ServeHTTP, UploadHandler: http.StripPrefix("/uploads/",uploads).ServeHTTP,
extra_routes: make(map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError), extraRoutes: make(map[string]func(http.ResponseWriter, *http.Request, common.User) common.RouteError),
} }
} }
@ -182,17 +219,17 @@ func (router *GenRouter) Handle(_ string, _ http.Handler) {
func (router *GenRouter) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request, common.User) common.RouteError) { func (router *GenRouter) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request, common.User) common.RouteError) {
router.Lock() router.Lock()
defer router.Unlock() defer router.Unlock()
router.extra_routes[pattern] = handle router.extraRoutes[pattern] = handle
} }
func (router *GenRouter) RemoveFunc(pattern string) error { func (router *GenRouter) RemoveFunc(pattern string) error {
router.Lock() router.Lock()
defer router.Unlock() defer router.Unlock()
_, ok := router.extra_routes[pattern] _, ok := router.extraRoutes[pattern]
if !ok { if !ok {
return ErrNoRoute return ErrNoRoute
} }
delete(router.extra_routes, pattern) delete(router.extraRoutes, pattern)
return nil return nil
} }
@ -203,10 +240,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
var prefix, extra_data string var prefix, extraData string
prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 1] prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 1]
if req.URL.Path[len(req.URL.Path) - 1] != '/' { if req.URL.Path[len(req.URL.Path) - 1] != '/' {
extra_data = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:] extraData = req.URL.Path[strings.LastIndexByte(req.URL.Path,'/') + 1:]
req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1] req.URL.Path = req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]
} }
@ -214,12 +251,12 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("before routeStatic") log.Print("before routeStatic")
log.Print("prefix: ", prefix) log.Print("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path) log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extra_data: ", extra_data) log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer()) log.Print("req.Referer(): ", req.Referer())
} }
if prefix == "/static" { if prefix == "/static" {
req.URL.Path += extra_data req.URL.Path += extraData
routeStatic(w, req) routeStatic(w, req)
return return
} }
@ -237,54 +274,62 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
if common.Dev.SuperDebug { if common.Dev.SuperDebug {
log.Print("after PreRoute") log.Print("after PreRoute")
log.Print("routeMapEnum: ", routeMapEnum)
} }
var err common.RouteError var err common.RouteError
switch(prefix) {` + out + ` switch(prefix) {` + out + `
case "/uploads": case "/uploads":
if extra_data == "" { if extraData == "" {
common.NotFound(w,req) common.NotFound(w,req)
return return
} }
req.URL.Path += extra_data req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this? // TODO: Find a way to propagate errors up from this?
router.UploadHandler(w,req) router.UploadHandler(w,req) // TODO: Count these views
case "": case "":
// Stop the favicons, robots.txt file, etc. resolving to the topics list // Stop the favicons, robots.txt file, etc. resolving to the topics list
// TODO: Add support for favicons and robots.txt files // TODO: Add support for favicons and robots.txt files
switch(extra_data) { switch(extraData) {
case "robots.txt": case "robots.txt":
err = routeRobotsTxt(w,req) err = routeRobotsTxt(w,req) // TODO: Count these views
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
return return
} }
if extra_data != "" { if extraData != "" {
common.NotFound(w,req) common.NotFound(w,req)
return return
} }
common.Config.DefaultRoute(w,req,user) common.Config.DefaultRoute(w,req,user) // TODO: Count these views
default: default:
// A fallback for the routes which haven't been converted to the new router yet or plugins // A fallback for the routes which haven't been converted to the new router yet or plugins
router.RLock() router.RLock()
handle, ok := router.extra_routes[req.URL.Path] handle, ok := router.extraRoutes[req.URL.Path]
router.RUnlock() router.RUnlock()
if ok { if ok {
req.URL.Path += extra_data req.URL.Path += extraData
err = handle(w,req,user) err = handle(w,req,user) // TODO: Count these views
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
return return
} }
common.NotFound(w,req) common.NotFound(w,req) // TODO: Collect all the error view counts so we can add a replacement for GlobalViewCounter by adding up the view counts of every route? Complex and may be inaccurate, collecting it globally and locally would at-least help find places we aren't capturing views
} }
} }
` `
writeFile("./gen_router.go", fileData) var tmpl = template.Must(template.New("router").Parse(fileData))
var b bytes.Buffer
err := tmpl.Execute(&b, tmplVars)
if err != nil {
log.Fatal(err)
}
writeFile("./gen_router.go", string(b.Bytes()))
log.Println("Successfully generated the router") log.Println("Successfully generated the router")
} }

View File

@ -4,23 +4,23 @@ package main
func routes() { func routes() {
addRoute(View("routeAPI", "/api/")) addRoute(View("routeAPI", "/api/"))
addRoute(View("routeOverview", "/overview/")) addRoute(View("routeOverview", "/overview/"))
//addRoute("routeCustomPage","/pages/",""/*,"&extra_data"*/) //addRoute("routeCustomPage","/pages/",""/*,"&extraData"*/)
addRoute(View("routeForums", "/forums/" /*,"&forums"*/)) addRoute(View("routeForums", "/forums/" /*,"&forums"*/))
addRoute(View("routeForum", "/forum/", "extra_data")) addRoute(View("routeForum", "/forum/", "extraData"))
addRoute(AnonAction("routeChangeTheme", "/theme/")) addRoute(AnonAction("routeChangeTheme", "/theme/"))
addRoute( addRoute(
View("routeShowAttachment", "/attachs/", "extra_data").Before("ParseForm"), View("routeShowAttachment", "/attachs/", "extraData").Before("ParseForm"),
) )
// TODO: Reduce the number of Befores. With a new method, perhaps? // TODO: Reduce the number of Befores. With a new method, perhaps?
reportGroup := newRouteGroup("/report/", reportGroup := newRouteGroup("/report/",
Action("routeReportSubmit", "/report/submit/", "extra_data"), Action("routeReportSubmit", "/report/submit/", "extraData"),
).Before("NoBanned") ).Before("NoBanned")
addRouteGroup(reportGroup) addRouteGroup(reportGroup)
topicGroup := newRouteGroup("/topics/", topicGroup := newRouteGroup("/topics/",
View("routeTopics", "/topics/"), View("routeTopics", "/topics/"),
MemberView("routeTopicCreate", "/topics/create/", "extra_data"), MemberView("routeTopicCreate", "/topics/create/", "extraData"),
) )
addRouteGroup(topicGroup) addRouteGroup(topicGroup)
@ -32,7 +32,7 @@ func routes() {
func buildUserRoutes() { func buildUserRoutes() {
userGroup := newRouteGroup("/user/") userGroup := newRouteGroup("/user/")
userGroup.Routes( userGroup.Routes(
View("routeProfile", "/user/").LitBefore("req.URL.Path += extra_data"), View("routeProfile", "/user/").LitBefore("req.URL.Path += extraData"),
MemberView("routeAccountEditCritical", "/user/edit/critical/"), MemberView("routeAccountEditCritical", "/user/edit/critical/"),
Action("routeAccountEditCriticalSubmit", "/user/edit/critical/submit/"), // TODO: Full test this Action("routeAccountEditCriticalSubmit", "/user/edit/critical/submit/"), // TODO: Full test this
MemberView("routeAccountEditAvatar", "/user/edit/avatar/"), MemberView("routeAccountEditAvatar", "/user/edit/avatar/"),
@ -40,7 +40,7 @@ func buildUserRoutes() {
MemberView("routeAccountEditUsername", "/user/edit/username/"), MemberView("routeAccountEditUsername", "/user/edit/username/"),
Action("routeAccountEditUsernameSubmit", "/user/edit/username/submit/"), // TODO: Full test this Action("routeAccountEditUsernameSubmit", "/user/edit/username/submit/"), // TODO: Full test this
MemberView("routeAccountEditEmail", "/user/edit/email/"), MemberView("routeAccountEditEmail", "/user/edit/email/"),
Action("routeAccountEditEmailTokenSubmit", "/user/edit/token/", "extra_data"), Action("routeAccountEditEmailTokenSubmit", "/user/edit/token/", "extraData"),
) )
addRouteGroup(userGroup) addRouteGroup(userGroup)
@ -50,7 +50,7 @@ func buildUserRoutes() {
Action("routeBanSubmit", "/users/ban/submit/"), Action("routeBanSubmit", "/users/ban/submit/"),
Action("routeUnban", "/users/unban/"), Action("routeUnban", "/users/unban/"),
Action("routeActivate", "/users/activate/"), Action("routeActivate", "/users/activate/"),
MemberView("routeIps", "/users/ips/"), MemberView("routeIps", "/users/ips/"), // TODO: .Perms("ViewIPs")?
) )
addRouteGroup(userGroup) addRouteGroup(userGroup)
} }
@ -61,42 +61,42 @@ func buildPanelRoutes() {
View("routePanel", "/panel/"), View("routePanel", "/panel/"),
View("routePanelForums", "/panel/forums/"), View("routePanelForums", "/panel/forums/"),
Action("routePanelForumsCreateSubmit", "/panel/forums/create/"), Action("routePanelForumsCreateSubmit", "/panel/forums/create/"),
Action("routePanelForumsDelete", "/panel/forums/delete/", "extra_data"), Action("routePanelForumsDelete", "/panel/forums/delete/", "extraData"),
Action("routePanelForumsDeleteSubmit", "/panel/forums/delete/submit/", "extra_data"), Action("routePanelForumsDeleteSubmit", "/panel/forums/delete/submit/", "extraData"),
View("routePanelForumsEdit", "/panel/forums/edit/", "extra_data"), View("routePanelForumsEdit", "/panel/forums/edit/", "extraData"),
Action("routePanelForumsEditSubmit", "/panel/forums/edit/submit/", "extra_data"), Action("routePanelForumsEditSubmit", "/panel/forums/edit/submit/", "extraData"),
Action("routePanelForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extra_data"), Action("routePanelForumsEditPermsSubmit", "/panel/forums/edit/perms/submit/", "extraData"),
View("routePanelSettings", "/panel/settings/"), View("routePanelSettings", "/panel/settings/"),
View("routePanelSettingEdit", "/panel/settings/edit/", "extra_data"), View("routePanelSettingEdit", "/panel/settings/edit/", "extraData"),
Action("routePanelSettingEditSubmit", "/panel/settings/edit/submit/", "extra_data"), Action("routePanelSettingEditSubmit", "/panel/settings/edit/submit/", "extraData"),
View("routePanelWordFilters", "/panel/settings/word-filters/"), View("routePanelWordFilters", "/panel/settings/word-filters/"),
Action("routePanelWordFiltersCreate", "/panel/settings/word-filters/create/"), Action("routePanelWordFiltersCreate", "/panel/settings/word-filters/create/"),
View("routePanelWordFiltersEdit", "/panel/settings/word-filters/edit/", "extra_data"), View("routePanelWordFiltersEdit", "/panel/settings/word-filters/edit/", "extraData"),
Action("routePanelWordFiltersEditSubmit", "/panel/settings/word-filters/edit/submit/", "extra_data"), Action("routePanelWordFiltersEditSubmit", "/panel/settings/word-filters/edit/submit/", "extraData"),
Action("routePanelWordFiltersDeleteSubmit", "/panel/settings/word-filters/delete/submit/", "extra_data"), Action("routePanelWordFiltersDeleteSubmit", "/panel/settings/word-filters/delete/submit/", "extraData"),
View("routePanelThemes", "/panel/themes/"), View("routePanelThemes", "/panel/themes/"),
Action("routePanelThemesSetDefault", "/panel/themes/default/", "extra_data"), Action("routePanelThemesSetDefault", "/panel/themes/default/", "extraData"),
View("routePanelPlugins", "/panel/plugins/"), View("routePanelPlugins", "/panel/plugins/"),
Action("routePanelPluginsActivate", "/panel/plugins/activate/", "extra_data"), Action("routePanelPluginsActivate", "/panel/plugins/activate/", "extraData"),
Action("routePanelPluginsDeactivate", "/panel/plugins/deactivate/", "extra_data"), Action("routePanelPluginsDeactivate", "/panel/plugins/deactivate/", "extraData"),
Action("routePanelPluginsInstall", "/panel/plugins/install/", "extra_data"), Action("routePanelPluginsInstall", "/panel/plugins/install/", "extraData"),
View("routePanelUsers", "/panel/users/"), View("routePanelUsers", "/panel/users/"),
View("routePanelUsersEdit", "/panel/users/edit/", "extra_data"), View("routePanelUsersEdit", "/panel/users/edit/", "extraData"),
Action("routePanelUsersEditSubmit", "/panel/users/edit/submit/", "extra_data"), Action("routePanelUsersEditSubmit", "/panel/users/edit/submit/", "extraData"),
View("routePanelGroups", "/panel/groups/"), View("routePanelGroups", "/panel/groups/"),
View("routePanelGroupsEdit", "/panel/groups/edit/", "extra_data"), View("routePanelGroupsEdit", "/panel/groups/edit/", "extraData"),
View("routePanelGroupsEditPerms", "/panel/groups/edit/perms/", "extra_data"), View("routePanelGroupsEditPerms", "/panel/groups/edit/perms/", "extraData"),
Action("routePanelGroupsEditSubmit", "/panel/groups/edit/submit/", "extra_data"), Action("routePanelGroupsEditSubmit", "/panel/groups/edit/submit/", "extraData"),
Action("routePanelGroupsEditPermsSubmit", "/panel/groups/edit/perms/submit/", "extra_data"), Action("routePanelGroupsEditPermsSubmit", "/panel/groups/edit/perms/submit/", "extraData"),
Action("routePanelGroupsCreateSubmit", "/panel/groups/create/"), Action("routePanelGroupsCreateSubmit", "/panel/groups/create/"),
View("routePanelBackups", "/panel/backups/", "extra_data"), View("routePanelBackups", "/panel/backups/", "extraData").Before("SuperAdminOnly"), // TODO: Test
View("routePanelLogsMod", "/panel/logs/mod/"), View("routePanelLogsMod", "/panel/logs/mod/"),
View("routePanelDebug", "/panel/debug/").Before("AdminOnly"), View("routePanelDebug", "/panel/debug/").Before("AdminOnly"),
) )

View File

@ -1,4 +1,5 @@
CREATE TABLE [viewchunks] ( CREATE TABLE [viewchunks] (
[count] int DEFAULT 0 not null, [count] int DEFAULT 0 not null,
[createdAt] datetime not null [createdAt] datetime not null,
[route] nvarchar (200) not null
); );

View File

@ -1,4 +1,5 @@
CREATE TABLE `viewchunks` ( CREATE TABLE `viewchunks` (
`count` int DEFAULT 0 not null, `count` int DEFAULT 0 not null,
`createdAt` datetime not null `createdAt` datetime not null,
`route` varchar(200) not null
); );

View File

@ -1,4 +1,5 @@
CREATE TABLE `viewchunks` ( CREATE TABLE `viewchunks` (
`count` int DEFAULT 0 not null, `count` int DEFAULT 0 not null,
`createdAt` timestamp not null `createdAt` timestamp not null,
`route` varchar (200) not null
); );

View File

@ -3,9 +3,9 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "strconv"
import "net/http" import "net/http"
import "./common" import "./common"
import "strconv"
// nolint // nolint
func init() { func init() {
@ -143,54 +143,58 @@ w.Write([]byte(item.Creator.Link))
w.Write(forum_30) w.Write(forum_30)
w.Write([]byte(item.Creator.Avatar)) w.Write([]byte(item.Creator.Avatar))
w.Write(forum_31) w.Write(forum_31)
w.Write([]byte(item.Link)) w.Write([]byte(item.Creator.Avatar))
w.Write(forum_32) w.Write(forum_32)
w.Write([]byte(item.Title)) w.Write([]byte(item.Link))
w.Write(forum_33) w.Write(forum_33)
w.Write([]byte(item.Creator.Link)) w.Write([]byte(item.Title))
w.Write(forum_34) w.Write(forum_34)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Link))
w.Write(forum_35) w.Write(forum_35)
if item.IsClosed { w.Write([]byte(item.Creator.Name))
w.Write(forum_36) w.Write(forum_36)
} if item.IsClosed {
if item.Sticky {
w.Write(forum_37) w.Write(forum_37)
} }
w.Write(forum_38)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(forum_39)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(forum_40)
if item.Sticky { if item.Sticky {
w.Write(forum_38)
}
w.Write(forum_39)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(forum_40)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(forum_41) w.Write(forum_41)
if item.Sticky {
w.Write(forum_42)
} else { } else {
if item.IsClosed { if item.IsClosed {
w.Write(forum_42)
}
}
w.Write(forum_43) w.Write(forum_43)
w.Write([]byte(item.LastUser.Link)) }
}
w.Write(forum_44) w.Write(forum_44)
w.Write([]byte(item.LastUser.Avatar))
w.Write(forum_45)
w.Write([]byte(item.LastUser.Link)) w.Write([]byte(item.LastUser.Link))
w.Write(forum_45)
w.Write([]byte(item.LastUser.Avatar))
w.Write(forum_46) w.Write(forum_46)
w.Write([]byte(item.LastUser.Name)) w.Write([]byte(item.LastUser.Name))
w.Write(forum_47) w.Write(forum_47)
w.Write([]byte(item.RelativeLastReplyAt)) w.Write([]byte(item.LastUser.Link))
w.Write(forum_48) w.Write(forum_48)
w.Write([]byte(item.LastUser.Name))
w.Write(forum_49)
w.Write([]byte(item.RelativeLastReplyAt))
w.Write(forum_50)
} }
} else { } else {
w.Write(forum_49)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_50)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_51) w.Write(forum_51)
} if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_52) w.Write(forum_52)
} w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_53) w.Write(forum_53)
}
w.Write(forum_54)
}
w.Write(forum_55)
w.Write(footer_0) w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_forum_vars.Header))) w.Write([]byte(common.BuildWidget("footer",tmpl_forum_vars.Header)))
w.Write(footer_1) w.Write(footer_1)

View File

@ -352,127 +352,139 @@ var topic_alt_29 = []byte(`</textarea>
<div class="button_container"> <div class="button_container">
`) `)
var topic_alt_30 = []byte(`<a href="/topic/like/submit/`) var topic_alt_30 = []byte(`<a href="/topic/like/submit/`)
var topic_alt_31 = []byte(`" class="action_button like_item add_like">+1</a>`) var topic_alt_31 = []byte(`" class="action_button like_item add_like" aria-label="Like this post" data-action="like"></a>`)
var topic_alt_32 = []byte(`<a href="/topic/edit/`) var topic_alt_32 = []byte(`<a href="/topic/edit/`)
var topic_alt_33 = []byte(`" class="action_button open_edit">Edit</a>`) var topic_alt_33 = []byte(`" class="action_button open_edit" aria-label="Edit this post" data-action="edit"></a>`)
var topic_alt_34 = []byte(`<a href="/topic/delete/submit/`) var topic_alt_34 = []byte(`<a href="/topic/delete/submit/`)
var topic_alt_35 = []byte(`" class="action_button delete_item">Delete</a>`) var topic_alt_35 = []byte(`" class="action_button delete_item" aria-label="Delete this post" data-action="delete"></a>`)
var topic_alt_36 = []byte(`<a href='/topic/unlock/submit/`) var topic_alt_36 = []byte(`<a href='/topic/unlock/submit/`)
var topic_alt_37 = []byte(`' class="action_button">Unlock</a>`) var topic_alt_37 = []byte(`' class="action_button unlock_item" data-action="unlock"></a>`)
var topic_alt_38 = []byte(`<a href='/topic/lock/submit/`) var topic_alt_38 = []byte(`<a href='/topic/lock/submit/`)
var topic_alt_39 = []byte(`' class="action_button">Lock</a>`) var topic_alt_39 = []byte(`' class="action_button lock_item" data-action="lock"></a>`)
var topic_alt_40 = []byte(`<a href='/topic/unstick/submit/`) var topic_alt_40 = []byte(`<a href='/topic/unstick/submit/`)
var topic_alt_41 = []byte(`' class="action_button">Unpin</a>`) var topic_alt_41 = []byte(`' class="action_button unpin_item" data-action="unpin"></a>`)
var topic_alt_42 = []byte(`<a href='/topic/stick/submit/`) var topic_alt_42 = []byte(`<a href='/topic/stick/submit/`)
var topic_alt_43 = []byte(`' class="action_button">Pin</a>`) var topic_alt_43 = []byte(`' class="action_button pin_item" data-action="pin"></a>`)
var topic_alt_44 = []byte(` var topic_alt_44 = []byte(`<a href="/users/ips/?ip=`)
var topic_alt_45 = []byte(`" title="IP Address" class="action_button ip_item_button hide_on_big" aria-label="This user's IP" data-action="ip"></a>`)
var topic_alt_46 = []byte(`
<a href="/report/submit/`) <a href="/report/submit/`)
var topic_alt_45 = []byte(`?session=`) var topic_alt_47 = []byte(`?session=`)
var topic_alt_46 = []byte(`&type=topic" class="action_button report_item">Report</a> var topic_alt_48 = []byte(`&type=topic" class="action_button report_item" aria-label="Report this post" data-action="report"></a>
<a href="#" class="action_button button_menu"></a>
`) `)
var topic_alt_47 = []byte(` var topic_alt_49 = []byte(`
<div class="action_button_right"> <div class="action_button_right`)
var topic_alt_50 = []byte(` has_likes`)
var topic_alt_51 = []byte(`">
`) `)
var topic_alt_48 = []byte(`<a class="action_button like_count hide_on_micro">`) var topic_alt_52 = []byte(`<a class="action_button like_count hide_on_micro">`)
var topic_alt_49 = []byte(`</a>`)
var topic_alt_50 = []byte(`
<a class="action_button created_at hide_on_mobile">`)
var topic_alt_51 = []byte(`</a>
`)
var topic_alt_52 = []byte(`<a href="#" title="IP Address" class="action_button ip_item hide_on_mobile">`)
var topic_alt_53 = []byte(`</a>`) var topic_alt_53 = []byte(`</a>`)
var topic_alt_54 = []byte(` var topic_alt_54 = []byte(`
<a class="action_button created_at hide_on_mobile">`)
var topic_alt_55 = []byte(`</a>
`)
var topic_alt_56 = []byte(`<a href="/users/ips/?ip=`)
var topic_alt_57 = []byte(`" title="IP Address" class="action_button ip_item hide_on_mobile">`)
var topic_alt_58 = []byte(`</a>`)
var topic_alt_59 = []byte(`
</div> </div>
</div> </div>
</div><div style="clear:both;"></div> </div><div style="clear:both;"></div>
</article> </article>
`) `)
var topic_alt_55 = []byte(` var topic_alt_60 = []byte(`
<article itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item `) <article itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item `)
var topic_alt_56 = []byte(`action_item`) var topic_alt_61 = []byte(`action_item`)
var topic_alt_57 = []byte(`"> var topic_alt_62 = []byte(`">
<div class="userinfo" aria-label="The information on the poster"> <div class="userinfo" aria-label="The information on the poster">
<div class="avatar_item" style="background-image: url(`) <div class="avatar_item" style="background-image: url(`)
var topic_alt_58 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div> var topic_alt_63 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<a href="`) <a href="`)
var topic_alt_59 = []byte(`" class="the_name" rel="author">`) var topic_alt_64 = []byte(`" class="the_name" rel="author">`)
var topic_alt_60 = []byte(`</a> var topic_alt_65 = []byte(`</a>
`) `)
var topic_alt_61 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`) var topic_alt_66 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`)
var topic_alt_62 = []byte(`</div><div class="tag_post"></div></div>`) var topic_alt_67 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_63 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `) var topic_alt_68 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `)
var topic_alt_64 = []byte(`</div><div class="tag_post"></div></div>`) var topic_alt_69 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_65 = []byte(` var topic_alt_70 = []byte(`
</div> </div>
<div class="content_container" `) <div class="content_container" `)
var topic_alt_66 = []byte(`style="margin-left: 0px;"`) var topic_alt_71 = []byte(`style="margin-left: 0px;"`)
var topic_alt_67 = []byte(`> var topic_alt_72 = []byte(`>
`) `)
var topic_alt_68 = []byte(` var topic_alt_73 = []byte(`
<span class="action_icon" style="font-size: 18px;padding-right: 5px;">`) <span class="action_icon" style="font-size: 18px;padding-right: 5px;">`)
var topic_alt_69 = []byte(`</span> var topic_alt_74 = []byte(`</span>
<span itemprop="text">`) <span itemprop="text">`)
var topic_alt_70 = []byte(`</span> var topic_alt_75 = []byte(`</span>
`) `)
var topic_alt_71 = []byte(` var topic_alt_76 = []byte(`
<div class="editable_block user_content" itemprop="text">`) <div class="editable_block user_content" itemprop="text">`)
var topic_alt_72 = []byte(`</div> var topic_alt_77 = []byte(`</div>
<div class="button_container"> <div class="button_container">
`) `)
var topic_alt_73 = []byte(`<a href="/reply/like/submit/`) var topic_alt_78 = []byte(`<a href="/reply/like/submit/`)
var topic_alt_74 = []byte(`" class="action_button like_item add_like">+1</a>`) var topic_alt_79 = []byte(`" class="action_button like_item add_like" aria-label="Like this post" data-action="like"></a>`)
var topic_alt_75 = []byte(`<a href="/reply/edit/submit/`) var topic_alt_80 = []byte(`<a href="/reply/edit/submit/`)
var topic_alt_76 = []byte(`" class="action_button edit_item">Edit</a>`) var topic_alt_81 = []byte(`" class="action_button edit_item" aria-label="Edit this post" data-action="edit"></a>`)
var topic_alt_77 = []byte(`<a href="/reply/delete/submit/`) var topic_alt_82 = []byte(`<a href="/reply/delete/submit/`)
var topic_alt_78 = []byte(`" class="action_button delete_item">Delete</a>`) var topic_alt_83 = []byte(`" class="action_button delete_item" aria-label="Delete this post" data-action="delete"></a>`)
var topic_alt_79 = []byte(` var topic_alt_84 = []byte(`<a href="/users/ips/?ip=`)
var topic_alt_85 = []byte(`" title="IP Address" class="action_button ip_item_button hide_on_big" aria-label="This user's IP Address" data-action="ip"></a>`)
var topic_alt_86 = []byte(`
<a href="/report/submit/`) <a href="/report/submit/`)
var topic_alt_80 = []byte(`?session=`) var topic_alt_87 = []byte(`?session=`)
var topic_alt_81 = []byte(`&type=reply" class="action_button report_item">Report</a> var topic_alt_88 = []byte(`&type=reply" class="action_button report_item" aria-label="Report this post" data-action="report"></a>
<a href="#" class="action_button button_menu"></a>
`) `)
var topic_alt_82 = []byte(`
<div class="action_button_right">
`)
var topic_alt_83 = []byte(`<a class="action_button like_count hide_on_micro">`)
var topic_alt_84 = []byte(`</a>`)
var topic_alt_85 = []byte(`
<a class="action_button created_at hide_on_mobile">`)
var topic_alt_86 = []byte(`</a>
`)
var topic_alt_87 = []byte(`<a href="#" title="IP Address" class="action_button ip_item hide_on_mobile">`)
var topic_alt_88 = []byte(`</a>`)
var topic_alt_89 = []byte(` var topic_alt_89 = []byte(`
<div class="action_button_right`)
var topic_alt_90 = []byte(` has_likes`)
var topic_alt_91 = []byte(`">
`)
var topic_alt_92 = []byte(`<a class="action_button like_count hide_on_micro">`)
var topic_alt_93 = []byte(`</a>`)
var topic_alt_94 = []byte(`
<a class="action_button created_at hide_on_mobile">`)
var topic_alt_95 = []byte(`</a>
`)
var topic_alt_96 = []byte(`<a href="/users/ips/?ip=`)
var topic_alt_97 = []byte(`" title="IP Address" class="action_button ip_item hide_on_mobile">`)
var topic_alt_98 = []byte(`</a>`)
var topic_alt_99 = []byte(`
</div> </div>
</div> </div>
`) `)
var topic_alt_90 = []byte(` var topic_alt_100 = []byte(`
</div> </div>
<div style="clear:both;"></div> <div style="clear:both;"></div>
</article> </article>
`) `)
var topic_alt_91 = []byte(`</div> var topic_alt_101 = []byte(`</div>
`) `)
var topic_alt_92 = []byte(` var topic_alt_102 = []byte(`
<div class="rowblock topic_reply_container"> <div class="rowblock topic_reply_container">
<div class="userinfo" aria-label="The information on the poster"> <div class="userinfo" aria-label="The information on the poster">
<div class="avatar_item" style="background-image: url(`) <div class="avatar_item" style="background-image: url(`)
var topic_alt_93 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div> var topic_alt_103 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<a href="`) <a href="`)
var topic_alt_94 = []byte(`" class="the_name" rel="author">`) var topic_alt_104 = []byte(`" class="the_name" rel="author">`)
var topic_alt_95 = []byte(`</a> var topic_alt_105 = []byte(`</a>
`) `)
var topic_alt_96 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`) var topic_alt_106 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`)
var topic_alt_97 = []byte(`</div><div class="tag_post"></div></div>`) var topic_alt_107 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_98 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `) var topic_alt_108 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `)
var topic_alt_99 = []byte(`</div><div class="tag_post"></div></div>`) var topic_alt_109 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_100 = []byte(` var topic_alt_110 = []byte(`
</div> </div>
<div class="rowblock topic_reply_form quick_create_form"> <div class="rowblock topic_reply_form quick_create_form">
<form id="reply_form" enctype="multipart/form-data" action="/reply/create/" method="post"></form> <form id="reply_form" enctype="multipart/form-data" action="/reply/create/" method="post"></form>
<input form="reply_form" name="tid" value='`) <input form="reply_form" name="tid" value='`)
var topic_alt_101 = []byte(`' type="hidden" /> var topic_alt_111 = []byte(`' type="hidden" />
<div class="formrow real_first_child"> <div class="formrow real_first_child">
<div class="formitem"> <div class="formitem">
<textarea id="input_content" form="reply_form" name="reply-content" placeholder="What do you think?" required></textarea> <textarea id="input_content" form="reply_form" name="reply-content" placeholder="What do you think?" required></textarea>
@ -482,17 +494,17 @@ var topic_alt_101 = []byte(`' type="hidden" />
<div class="formitem"> <div class="formitem">
<button form="reply_form" name="reply-button" class="formbutton">Create Reply</button> <button form="reply_form" name="reply-button" class="formbutton">Create Reply</button>
`) `)
var topic_alt_102 = []byte(` var topic_alt_112 = []byte(`
<input name="upload_files" form="reply_form" id="upload_files" multiple type="file" style="display: none;" /> <input name="upload_files" form="reply_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label> <label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`) <div id="upload_file_dock"></div>`)
var topic_alt_103 = []byte(` var topic_alt_113 = []byte(`
</div> </div>
</div> </div>
</div> </div>
</div> </div>
`) `)
var topic_alt_104 = []byte(` var topic_alt_114 = []byte(`
</main> </main>
@ -762,7 +774,7 @@ var topics_9 = []byte(`
<div class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form"> <div class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form">
<form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> <form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<img class="little_row_avatar" src="`) <img class="little_row_avatar" src="`)
var topics_10 = []byte(`" height="64" /> var topics_10 = []byte(`" height="64" alt="Your Avatar" />
<div class="main_form"> <div class="main_form">
<div class="topic_meta"> <div class="topic_meta">
<div class="formrow topic_board_row real_first_child"> <div class="formrow topic_board_row real_first_child">
@ -814,50 +826,52 @@ var topics_24 = []byte(`">
<span class="selector"></span> <span class="selector"></span>
<a href="`) <a href="`)
var topics_25 = []byte(`"><img src="`) var topics_25 = []byte(`"><img src="`)
var topics_26 = []byte(`" height="64" /></a> var topics_26 = []byte(`" height="64" alt="`)
var topics_27 = []byte(`'s Avatar" /></a>
<span class="topic_inner_left"> <span class="topic_inner_left">
<a class="rowtopic" href="`) <a class="rowtopic" href="`)
var topics_27 = []byte(`"><span>`) var topics_28 = []byte(`"><span>`)
var topics_28 = []byte(`</span></a> `) var topics_29 = []byte(`</span></a> `)
var topics_29 = []byte(`<a class="rowsmall parent_forum" href="`) var topics_30 = []byte(`<a class="rowsmall parent_forum" href="`)
var topics_30 = []byte(`">`) var topics_31 = []byte(`">`)
var topics_31 = []byte(`</a>`) var topics_32 = []byte(`</a>`)
var topics_32 = []byte(` var topics_33 = []byte(`
<br /><a class="rowsmall starter" href="`) <br /><a class="rowsmall starter" href="`)
var topics_33 = []byte(`">`) var topics_34 = []byte(`">`)
var topics_34 = []byte(`</a> var topics_35 = []byte(`</a>
`) `)
var topics_35 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`) var topics_36 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var topics_36 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`) var topics_37 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var topics_37 = []byte(` var topics_38 = []byte(`
</span> </span>
<span class="topic_inner_right rowsmall" style="float: right;"> <span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`) <span class="replyCount">`)
var topics_38 = []byte(`</span><br /> var topics_39 = []byte(`</span><br />
<span class="likeCount">`) <span class="likeCount">`)
var topics_39 = []byte(`</span> var topics_40 = []byte(`</span>
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow `) <div class="rowitem topic_right passive datarow `)
var topics_40 = []byte(`topic_sticky`) var topics_41 = []byte(`topic_sticky`)
var topics_41 = []byte(`topic_closed`) var topics_42 = []byte(`topic_closed`)
var topics_42 = []byte(`"> var topics_43 = []byte(`">
<a href="`) <a href="`)
var topics_43 = []byte(`"><img src="`) var topics_44 = []byte(`"><img src="`)
var topics_44 = []byte(`" height="64" /></a> var topics_45 = []byte(`" height="64" alt="`)
var topics_46 = []byte(`'s Avatar" /></a>
<span> <span>
<a href="`) <a href="`)
var topics_45 = []byte(`" class="lastName" style="font-size: 14px;">`) var topics_47 = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_46 = []byte(`</a><br> var topics_48 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">`) <span class="rowsmall lastReplyAt">`)
var topics_47 = []byte(`</span> var topics_49 = []byte(`</span>
</span> </span>
</div> </div>
</div>`) </div>`)
var topics_48 = []byte(`<div class="rowitem passive">There aren't any topics yet.`) var topics_50 = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_49 = []byte(` <a href="/topics/create/">Start one?</a>`) var topics_51 = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_50 = []byte(`</div>`) var topics_52 = []byte(`</div>`)
var topics_51 = []byte(` var topics_53 = []byte(`
</div> </div>
</main> </main>
@ -919,7 +933,7 @@ var forum_19 = []byte(`
<div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form"> <div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form">
<form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> <form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<img class="little_row_avatar" src="`) <img class="little_row_avatar" src="`)
var forum_20 = []byte(`" height="64" /> var forum_20 = []byte(`" height="64" alt="Your Avatar" />
<input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="`) <input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="`)
var forum_21 = []byte(`" type="hidden"> var forum_21 = []byte(`" type="hidden">
<div class="main_form"> <div class="main_form">
@ -962,47 +976,49 @@ var forum_29 = []byte(`">
<span class="selector"></span> <span class="selector"></span>
<a href="`) <a href="`)
var forum_30 = []byte(`"><img src="`) var forum_30 = []byte(`"><img src="`)
var forum_31 = []byte(`" height="64" /></a> var forum_31 = []byte(`" height="64" alt="`)
var forum_32 = []byte(`'s Avatar" /></a>
<span class="topic_inner_left"> <span class="topic_inner_left">
<a class="rowtopic" href="`) <a class="rowtopic" href="`)
var forum_32 = []byte(`"><span>`) var forum_33 = []byte(`"><span>`)
var forum_33 = []byte(`</span></a> var forum_34 = []byte(`</span></a>
<br /><a class="rowsmall starter" href="`) <br /><a class="rowsmall starter" href="`)
var forum_34 = []byte(`">`) var forum_35 = []byte(`">`)
var forum_35 = []byte(`</a> var forum_36 = []byte(`</a>
`) `)
var forum_36 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`) var forum_37 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var forum_37 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`) var forum_38 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var forum_38 = []byte(` var forum_39 = []byte(`
</span> </span>
<span class="topic_inner_right rowsmall" style="float: right;"> <span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`) <span class="replyCount">`)
var forum_39 = []byte(`</span><br /> var forum_40 = []byte(`</span><br />
<span class="likeCount">`) <span class="likeCount">`)
var forum_40 = []byte(`</span> var forum_41 = []byte(`</span>
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow `) <div class="rowitem topic_right passive datarow `)
var forum_41 = []byte(`topic_sticky`) var forum_42 = []byte(`topic_sticky`)
var forum_42 = []byte(`topic_closed`) var forum_43 = []byte(`topic_closed`)
var forum_43 = []byte(`"> var forum_44 = []byte(`">
<a href="`) <a href="`)
var forum_44 = []byte(`"><img src="`) var forum_45 = []byte(`"><img src="`)
var forum_45 = []byte(`" height="64" /></a> var forum_46 = []byte(`" height="64" alt="`)
var forum_47 = []byte(`'s Avatar" /></a>
<span> <span>
<a href="`) <a href="`)
var forum_46 = []byte(`" class="lastName" style="font-size: 14px;">`) var forum_48 = []byte(`" class="lastName" style="font-size: 14px;">`)
var forum_47 = []byte(`</a><br> var forum_49 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">`) <span class="rowsmall lastReplyAt">`)
var forum_48 = []byte(`</span> var forum_50 = []byte(`</span>
</span> </span>
</div> </div>
</div>`) </div>`)
var forum_49 = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`) var forum_51 = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
var forum_50 = []byte(` <a href="/topics/create/`) var forum_52 = []byte(` <a href="/topics/create/`)
var forum_51 = []byte(`">Start one?</a>`) var forum_53 = []byte(`">Start one?</a>`)
var forum_52 = []byte(`</div>`) var forum_54 = []byte(`</div>`)
var forum_53 = []byte(` var forum_55 = []byte(`
</div> </div>
</main> </main>
`) `)

View File

@ -3,9 +3,9 @@
// Code generated by Gosora. More below: // Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */ /* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main package main
import "net/http"
import "./common" import "./common"
import "strconv" import "strconv"
import "net/http"
// nolint // nolint
func init() { func init() {
@ -176,132 +176,154 @@ w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_43) w.Write(topic_alt_43)
} }
} }
w.Write(topic_alt_44)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_45)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(topic_alt_46)
}
w.Write(topic_alt_47)
if tmpl_topic_alt_vars.Topic.LikeCount > 0 {
w.Write(topic_alt_48)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount)))
w.Write(topic_alt_49)
}
w.Write(topic_alt_50)
w.Write([]byte(tmpl_topic_alt_vars.Topic.RelativeCreatedAt))
w.Write(topic_alt_51)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_52) w.Write(topic_alt_44)
w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress)) w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress))
w.Write(topic_alt_45)
}
w.Write(topic_alt_46)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_47)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(topic_alt_48)
}
w.Write(topic_alt_49)
if tmpl_topic_alt_vars.Topic.LikeCount > 0 {
w.Write(topic_alt_50)
}
w.Write(topic_alt_51)
if tmpl_topic_alt_vars.Topic.LikeCount > 0 {
w.Write(topic_alt_52)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount)))
w.Write(topic_alt_53) w.Write(topic_alt_53)
} }
w.Write(topic_alt_54) w.Write(topic_alt_54)
w.Write([]byte(tmpl_topic_alt_vars.Topic.RelativeCreatedAt))
w.Write(topic_alt_55)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_56)
w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress))
w.Write(topic_alt_57)
w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress))
w.Write(topic_alt_58)
}
w.Write(topic_alt_59)
if len(tmpl_topic_alt_vars.ItemList) != 0 { if len(tmpl_topic_alt_vars.ItemList) != 0 {
for _, item := range tmpl_topic_alt_vars.ItemList { for _, item := range tmpl_topic_alt_vars.ItemList {
w.Write(topic_alt_55)
if item.ActionType != "" {
w.Write(topic_alt_56)
}
w.Write(topic_alt_57)
w.Write([]byte(item.Avatar))
w.Write(topic_alt_58)
w.Write([]byte(item.UserLink))
w.Write(topic_alt_59)
w.Write([]byte(item.CreatedByName))
w.Write(topic_alt_60) w.Write(topic_alt_60)
if item.Tag != "" { if item.ActionType != "" {
w.Write(topic_alt_61) w.Write(topic_alt_61)
w.Write([]byte(item.Tag)) }
w.Write(topic_alt_62) w.Write(topic_alt_62)
} else { w.Write([]byte(item.Avatar))
w.Write(topic_alt_63) w.Write(topic_alt_63)
w.Write([]byte(strconv.Itoa(item.Level))) w.Write([]byte(item.UserLink))
w.Write(topic_alt_64) w.Write(topic_alt_64)
} w.Write([]byte(item.CreatedByName))
w.Write(topic_alt_65) w.Write(topic_alt_65)
if item.ActionType != "" { if item.Tag != "" {
w.Write(topic_alt_66) w.Write(topic_alt_66)
} w.Write([]byte(item.Tag))
w.Write(topic_alt_67) w.Write(topic_alt_67)
if item.ActionType != "" {
w.Write(topic_alt_68)
w.Write([]byte(item.ActionIcon))
w.Write(topic_alt_69)
w.Write([]byte(item.ActionType))
w.Write(topic_alt_70)
} else { } else {
w.Write(topic_alt_68)
w.Write([]byte(strconv.Itoa(item.Level)))
w.Write(topic_alt_69)
}
w.Write(topic_alt_70)
if item.ActionType != "" {
w.Write(topic_alt_71) w.Write(topic_alt_71)
w.Write([]byte(item.ContentHtml)) }
w.Write(topic_alt_72) w.Write(topic_alt_72)
if item.ActionType != "" {
w.Write(topic_alt_73)
w.Write([]byte(item.ActionIcon))
w.Write(topic_alt_74)
w.Write([]byte(item.ActionType))
w.Write(topic_alt_75)
} else {
w.Write(topic_alt_76)
w.Write([]byte(item.ContentHtml))
w.Write(topic_alt_77)
if tmpl_topic_alt_vars.CurrentUser.Loggedin { if tmpl_topic_alt_vars.CurrentUser.Loggedin {
if tmpl_topic_alt_vars.CurrentUser.Perms.LikeItem { if tmpl_topic_alt_vars.CurrentUser.Perms.LikeItem {
w.Write(topic_alt_73) w.Write(topic_alt_78)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_74) w.Write(topic_alt_79)
} }
if tmpl_topic_alt_vars.CurrentUser.Perms.EditReply { if tmpl_topic_alt_vars.CurrentUser.Perms.EditReply {
w.Write(topic_alt_75)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_76)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteReply {
w.Write(topic_alt_77)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_78)
}
w.Write(topic_alt_79)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_80) w.Write(topic_alt_80)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session)) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_81) w.Write(topic_alt_81)
} }
if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteReply {
w.Write(topic_alt_82) w.Write(topic_alt_82)
if item.LikeCount > 0 { w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_83) w.Write(topic_alt_83)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topic_alt_84)
} }
w.Write(topic_alt_85)
w.Write([]byte(item.RelativeCreatedAt))
w.Write(topic_alt_86)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs { if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_87) w.Write(topic_alt_84)
w.Write([]byte(item.IPAddress)) w.Write([]byte(item.IPAddress))
w.Write(topic_alt_85)
}
w.Write(topic_alt_86)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_87)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(topic_alt_88) w.Write(topic_alt_88)
} }
w.Write(topic_alt_89) w.Write(topic_alt_89)
} if item.LikeCount > 0 {
w.Write(topic_alt_90) w.Write(topic_alt_90)
} }
}
w.Write(topic_alt_91) w.Write(topic_alt_91)
if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply { if item.LikeCount > 0 {
w.Write(topic_alt_92) w.Write(topic_alt_92)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Avatar)) w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topic_alt_93) w.Write(topic_alt_93)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Link)) }
w.Write(topic_alt_94) w.Write(topic_alt_94)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Name)) w.Write([]byte(item.RelativeCreatedAt))
w.Write(topic_alt_95) w.Write(topic_alt_95)
if tmpl_topic_alt_vars.CurrentUser.Tag != "" { if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_96) w.Write(topic_alt_96)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Tag)) w.Write([]byte(item.IPAddress))
w.Write(topic_alt_97) w.Write(topic_alt_97)
} else { w.Write([]byte(item.IPAddress))
w.Write(topic_alt_98) w.Write(topic_alt_98)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.CurrentUser.Level))) }
w.Write(topic_alt_99) w.Write(topic_alt_99)
} }
w.Write(topic_alt_100) w.Write(topic_alt_100)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) }
}
w.Write(topic_alt_101) w.Write(topic_alt_101)
if tmpl_topic_alt_vars.CurrentUser.Perms.UploadFiles { if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply {
w.Write(topic_alt_102) w.Write(topic_alt_102)
} w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Avatar))
w.Write(topic_alt_103) w.Write(topic_alt_103)
} w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Link))
w.Write(topic_alt_104) w.Write(topic_alt_104)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Name))
w.Write(topic_alt_105)
if tmpl_topic_alt_vars.CurrentUser.Tag != "" {
w.Write(topic_alt_106)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Tag))
w.Write(topic_alt_107)
} else {
w.Write(topic_alt_108)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.CurrentUser.Level)))
w.Write(topic_alt_109)
}
w.Write(topic_alt_110)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_111)
if tmpl_topic_alt_vars.CurrentUser.Perms.UploadFiles {
w.Write(topic_alt_112)
}
w.Write(topic_alt_113)
}
w.Write(topic_alt_114)
w.Write(footer_0) w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_topic_alt_vars.Header))) w.Write([]byte(common.BuildWidget("footer",tmpl_topic_alt_vars.Header)))
w.Write(footer_1) w.Write(footer_1)

View File

@ -133,60 +133,64 @@ w.Write([]byte(item.Creator.Link))
w.Write(topics_25) w.Write(topics_25)
w.Write([]byte(item.Creator.Avatar)) w.Write([]byte(item.Creator.Avatar))
w.Write(topics_26) w.Write(topics_26)
w.Write([]byte(item.Link))
w.Write(topics_27)
w.Write([]byte(item.Title))
w.Write(topics_28)
if item.ForumName != "" {
w.Write(topics_29)
w.Write([]byte(item.ForumLink))
w.Write(topics_30)
w.Write([]byte(item.ForumName))
w.Write(topics_31)
}
w.Write(topics_32)
w.Write([]byte(item.Creator.Link))
w.Write(topics_33)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Name))
w.Write(topics_34) w.Write(topics_27)
if item.IsClosed { w.Write([]byte(item.Link))
w.Write(topics_35) w.Write(topics_28)
w.Write([]byte(item.Title))
w.Write(topics_29)
if item.ForumName != "" {
w.Write(topics_30)
w.Write([]byte(item.ForumLink))
w.Write(topics_31)
w.Write([]byte(item.ForumName))
w.Write(topics_32)
} }
if item.Sticky { w.Write(topics_33)
w.Write([]byte(item.Creator.Link))
w.Write(topics_34)
w.Write([]byte(item.Creator.Name))
w.Write(topics_35)
if item.IsClosed {
w.Write(topics_36) w.Write(topics_36)
} }
w.Write(topics_37)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(topics_38)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topics_39)
if item.Sticky { if item.Sticky {
w.Write(topics_37)
}
w.Write(topics_38)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(topics_39)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topics_40) w.Write(topics_40)
if item.Sticky {
w.Write(topics_41)
} else { } else {
if item.IsClosed { if item.IsClosed {
w.Write(topics_41)
}
}
w.Write(topics_42) w.Write(topics_42)
w.Write([]byte(item.LastUser.Link)) }
}
w.Write(topics_43) w.Write(topics_43)
w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_44)
w.Write([]byte(item.LastUser.Link)) w.Write([]byte(item.LastUser.Link))
w.Write(topics_44)
w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_45) w.Write(topics_45)
w.Write([]byte(item.LastUser.Name)) w.Write([]byte(item.LastUser.Name))
w.Write(topics_46) w.Write(topics_46)
w.Write([]byte(item.RelativeLastReplyAt)) w.Write([]byte(item.LastUser.Link))
w.Write(topics_47) w.Write(topics_47)
} w.Write([]byte(item.LastUser.Name))
} else {
w.Write(topics_48) w.Write(topics_48)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic { w.Write([]byte(item.RelativeLastReplyAt))
w.Write(topics_49) w.Write(topics_49)
} }
} else {
w.Write(topics_50) w.Write(topics_50)
} if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_51) w.Write(topics_51)
}
w.Write(topics_52)
}
w.Write(topics_53)
w.Write(footer_0) w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_topics_vars.Header))) w.Write([]byte(common.BuildWidget("footer",tmpl_topics_vars.Header)))
w.Write(footer_1) w.Write(footer_1)

View File

@ -39,7 +39,7 @@
{{if .CurrentUser.Perms.CreateTopic}} {{if .CurrentUser.Perms.CreateTopic}}
<div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form"> <div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form">
<form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> <form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<img class="little_row_avatar" src="{{.CurrentUser.Avatar}}" height="64" /> <img class="little_row_avatar" src="{{.CurrentUser.Avatar}}" height="64" alt="Your Avatar" />
<input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="{{.Forum.ID}}" type="hidden"> <input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="{{.Forum.ID}}" type="hidden">
<div class="main_form"> <div class="main_form">
<div class="topic_meta"> <div class="topic_meta">
@ -72,7 +72,7 @@
{{range .ItemList}}<div class="topic_row" data-tid="{{.ID}}"> {{range .ItemList}}<div class="topic_row" data-tid="{{.ID}}">
<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}"> <div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
<span class="selector"></span> <span class="selector"></span>
<a href="{{.Creator.Link}}"><img src="{{.Creator.Avatar}}" height="64" /></a> <a href="{{.Creator.Link}}"><img src="{{.Creator.Avatar}}" height="64" alt="{{.Creator.Avatar}}'s Avatar" /></a>
<span class="topic_inner_left"> <span class="topic_inner_left">
<a class="rowtopic" href="{{.Link}}"><span>{{.Title}}</span></a> <a class="rowtopic" href="{{.Link}}"><span>{{.Title}}</span></a>
<br /><a class="rowsmall starter" href="{{.Creator.Link}}">{{.Creator.Name}}</a> <br /><a class="rowsmall starter" href="{{.Creator.Link}}">{{.Creator.Name}}</a>
@ -86,7 +86,7 @@
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}"> <div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.Avatar}}" height="64" /></a> <a href="{{.LastUser.Link}}"><img src="{{.LastUser.Avatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" /></a>
<span> <span>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br> <a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">{{.RelativeLastReplyAt}}</span> <span class="rowsmall lastReplyAt">{{.RelativeLastReplyAt}}</span>

View File

@ -1,5 +1,5 @@
{{template "header.html" . }} {{template "header.html" . }}
<main> <main id="ip_search_container">
<div class="rowblock rowhead"> <div class="rowblock rowhead">
<div class="rowitem"> <div class="rowitem">
<h1>IP Search</h1> <h1>IP Search</h1>
@ -8,14 +8,15 @@
<form action="/users/ips/" method="get" id="ip-search-form"></form> <form action="/users/ips/" method="get" id="ip-search-form"></form>
<div class="rowblock ip_search_block"> <div class="rowblock ip_search_block">
<div class="rowitem passive"> <div class="rowitem passive">
<input form="ip-search-form" name="ip" class="ip_search_input" type="search" placeholder="🔍︎" {{if .IP}}value="{{.IP}}"{{end}}/> <input form="ip-search-form" name="ip" class="ip_search_input" type="search" placeholder="🔍︎"{{if .IP}} value="{{.IP}}"{{end}} />
<input form="ip-search-form" class="ip_search_search" type="submit" value="Search" /> <input form="ip-search-form" class="ip_search_search" type="submit" value="Search" />
</div> </div>
</div> </div>
{{if .IP}} {{if .IP}}
<div class="rowblock bgavatars"> <div class="rowblock rowlist bgavatars">
{{range .ItemList}}<div class="rowitem" style="background-image: url('{{.Avatar}}');"> {{range .ItemList}}<div class="rowitem" style="background-image: url('{{.Avatar}}');">
<a href="{{.Link}}">{{.Name}}</a> <img src="{{.Avatar}}" class="bgsub" alt="{{.Name}}'s Avatar" />
<a class="rowTitle" href="{{.Link}}">{{.Name}}</a>
</div> </div>
{{else}}<div class="rowitem">No users found.</div>{{end}} {{else}}<div class="rowitem">No users found.</div>{{end}}
</div> </div>

View File

@ -16,7 +16,7 @@
</div> </div>
<div id="panel_adminlogs" class="colstack_item rowlist"> <div id="panel_adminlogs" class="colstack_item rowlist">
{{range .Logs}} {{range .Logs}}
<div class="rowitem panel_compactrow" style="font-weight: normal;text-transform: none;"> <div class="rowitem panel_compactrow">
<span style="float: left;"> <span style="float: left;">
<span>{{.Action}}</span><br /> <span>{{.Action}}</span><br />
<small style="margin-left: 2px;font-size: 12px;">{{.IPAddress}}</small> <small style="margin-left: 2px;font-size: 12px;">{{.IPAddress}}</small>
@ -30,11 +30,11 @@
</div> </div>
{{if gt .LastPage 1}} {{if gt .LastPage 1}}
<div class="pageset"> <div class="pageset">
{{if gt .Page 1}}<div class="pageitem"><a href="?page={{subtract .Page 1}}">Prev</a></div>{{end}} {{if gt .Page 1}}<div class="pageitem"><a href="?page={{subtract .Page 1}}" aria-label="Previous Page">Prev</a></div>{{end}}
{{range .PageList}} {{range .PageList}}
<div class="pageitem"><a href="?page={{.}}">{{.}}</a></div> <div class="pageitem"><a href="?page={{.}}" aria-label="Page {{.}}">{{.}}</a></div>
{{end}} {{end}}
{{if ne .LastPage .Page}}<div class="pageitem"><a href="?page={{add .Page 1}}">Next</a></div>{{end}} {{if ne .LastPage .Page}}<div class="pageitem"><a href="?page={{add .Page 1}}" aria-label="Next Page">Next</a></div>{{end}}
</div> </div>
{{end}} {{end}}
</main> </main>

View File

@ -17,7 +17,7 @@
</div> </div>
<div id="panel_modlogs" class="colstack_item rowlist"> <div id="panel_modlogs" class="colstack_item rowlist">
{{range .Logs}} {{range .Logs}}
<div class="rowitem panel_compactrow" style="font-weight: normal;text-transform: none;"> <div class="rowitem panel_compactrow">
<span style="float: left;"> <span style="float: left;">
<span>{{.Action}}</span><br /> <span>{{.Action}}</span><br />
<small style="margin-left: 2px;font-size: 12px;">{{.IPAddress}}</small> <small style="margin-left: 2px;font-size: 12px;">{{.IPAddress}}</small>
@ -31,11 +31,11 @@
</div> </div>
{{if gt .LastPage 1}} {{if gt .LastPage 1}}
<div class="pageset"> <div class="pageset">
{{if gt .Page 1}}<div class="pageitem"><a href="?page={{subtract .Page 1}}">Prev</a></div>{{end}} {{if gt .Page 1}}<div class="pageitem"><a href="?page={{subtract .Page 1}}" aria-label="Previous Page">Prev</a></div>{{end}}
{{range .PageList}} {{range .PageList}}
<div class="pageitem"><a href="?page={{.}}">{{.}}</a></div> <div class="pageitem"><a href="?page={{.}}" aria-label="Page {{.}}">{{.}}</a></div>
{{end}} {{end}}
{{if ne .LastPage .Page}}<div class="pageitem"><a href="?page={{add .Page 1}}">Next</a></div>{{end}} {{if ne .LastPage .Page}}<div class="pageitem"><a href="?page={{add .Page 1}}" aria-label="Next Page">Next</a></div>{{end}}
</div> </div>
{{end}} {{end}}
</main> </main>

View File

@ -26,7 +26,7 @@
</div> </div>
<div id="panel_primary_themes" class="colstack_item panel_themes complex_rowlist"> <div id="panel_primary_themes" class="colstack_item panel_themes complex_rowlist">
{{range .PrimaryThemes}} {{range .PrimaryThemes}}
<div class="theme_row rowitem editable_parent" style="{{if .FullImage}}background-image: url('/static/{{.FullImage}}');background-position: center;background-size: 50%;background-repeat: no-repeat;{{end}}"> <div class="theme_row rowitem editable_parent"{{if .FullImage}} style="background-image: url('/static/{{.FullImage}}');background-position: center;background-size: 50%;background-repeat: no-repeat;"{{end}}>
<span style="float: left;"> <span style="float: left;">
<a href="/panel/themes/{{.Name}}" class="editable_block" style="font-size: 17px;">{{.FriendlyName}}</a><br /> <a href="/panel/themes/{{.Name}}" class="editable_block" style="font-size: 17px;">{{.FriendlyName}}</a><br />
<small class="panel_theme_author" style="margin-left: 2px;">Author: <a href="//{{.URL}}">{{.Creator}}</a></small> <small class="panel_theme_author" style="margin-left: 2px;">Author: <a href="//{{.URL}}">{{.Creator}}</a></small>
@ -45,7 +45,7 @@
</div> </div>
<div id="panel_variant_themes" class="colstack_item panel_themes"> <div id="panel_variant_themes" class="colstack_item panel_themes">
{{range .VariantThemes}} {{range .VariantThemes}}
<div class="theme_row rowitem editable_parent" style="{{if .FullImage}}background-image: url('/static/{{.FullImage}}');background-position: center;background-size: 50%;background-repeat: no-repeat;{{end}}"> <div class="theme_row rowitem editable_parent"{{if .FullImage}} style="background-image: url('/static/{{.FullImage}}');background-position: center;background-size: 50%;background-repeat: no-repeat;"{{end}}>
<span style="float: left;"> <span style="float: left;">
<a href="/panel/themes/{{.Name}}" class="editable_block" style="font-size: 17px;">{{.FriendlyName}}</a><br /> <a href="/panel/themes/{{.Name}}" class="editable_block" style="font-size: 17px;">{{.FriendlyName}}</a><br />
<small class="panel_theme_author" style="margin-left: 2px;">Author: <a href="//{{.URL}}">{{.Creator}}</a></small> <small class="panel_theme_author" style="margin-left: 2px;">Author: <a href="//{{.URL}}">{{.Creator}}</a></small>

View File

@ -9,7 +9,7 @@
<div id="panel_users" class="colstack_item rowlist bgavatars"> <div id="panel_users" class="colstack_item rowlist bgavatars">
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem editable_parent" style="background-image: url('{{.Avatar}}');"> <div class="rowitem editable_parent" style="background-image: url('{{.Avatar}}');">
<img class="bgsub" src="{{.Avatar}}" /> <img class="bgsub" src="{{.Avatar}}" alt="{{.Name}}'s Avatar" />
<a class="rowTitle" {{if $.CurrentUser.Perms.EditUser}}href="/panel/users/edit/{{.ID}}?session={{$.CurrentUser.Session}}" {{end}}class="editable_block">{{.Name}}</a> <a class="rowTitle" {{if $.CurrentUser.Perms.EditUser}}href="/panel/users/edit/{{.ID}}?session={{$.CurrentUser.Session}}" {{end}}class="editable_block">{{.Name}}</a>
<a href="/user/{{.ID}}" class="tag-mini">Profile</a> <a href="/user/{{.ID}}" class="tag-mini">Profile</a>
{{if (.Tag) and (.IsSuperMod)}}<span style="float: right;"><span class="panel_tag" style="margin-left 4px;">{{.Tag}}</span></span>{{end}} {{if (.Tag) and (.IsSuperMod)}}<span style="float: right;"><span class="panel_tag" style="margin-left 4px;">{{.Tag}}</span></span>{{end}}

View File

@ -34,19 +34,21 @@
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea> <textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea>
<div class="button_container"> <div class="button_container">
{{if .CurrentUser.Loggedin}} {{if .CurrentUser.Loggedin}}
{{if .CurrentUser.Perms.LikeItem}}<a href="/topic/like/submit/{{.Topic.ID}}" class="action_button like_item add_like">+1</a>{{end}} {{if .CurrentUser.Perms.LikeItem}}<a href="/topic/like/submit/{{.Topic.ID}}" class="action_button like_item add_like" aria-label="Like this post" data-action="like"></a>{{end}}
{{if .CurrentUser.Perms.EditTopic}}<a href="/topic/edit/{{.Topic.ID}}" class="action_button open_edit">Edit</a>{{end}} {{if .CurrentUser.Perms.EditTopic}}<a href="/topic/edit/{{.Topic.ID}}" class="action_button open_edit" aria-label="Edit this post" data-action="edit"></a>{{end}}
{{if .CurrentUser.Perms.DeleteTopic}}<a href="/topic/delete/submit/{{.Topic.ID}}" class="action_button delete_item">Delete</a>{{end}} {{if .CurrentUser.Perms.DeleteTopic}}<a href="/topic/delete/submit/{{.Topic.ID}}" class="action_button delete_item" aria-label="Delete this post" data-action="delete"></a>{{end}}
{{if .CurrentUser.Perms.CloseTopic}} {{if .CurrentUser.Perms.CloseTopic}}
{{if .Topic.IsClosed}}<a href='/topic/unlock/submit/{{.Topic.ID}}' class="action_button">Unlock</a>{{else}}<a href='/topic/lock/submit/{{.Topic.ID}}' class="action_button">Lock</a>{{end}}{{end}} {{if .Topic.IsClosed}}<a href='/topic/unlock/submit/{{.Topic.ID}}' class="action_button unlock_item" data-action="unlock"></a>{{else}}<a href='/topic/lock/submit/{{.Topic.ID}}' class="action_button lock_item" data-action="lock"></a>{{end}}{{end}}
{{if .CurrentUser.Perms.PinTopic}} {{if .CurrentUser.Perms.PinTopic}}
{{if .Topic.Sticky}}<a href='/topic/unstick/submit/{{.Topic.ID}}' class="action_button">Unpin</a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="action_button">Pin</a>{{end}}{{end}} {{if .Topic.Sticky}}<a href='/topic/unstick/submit/{{.Topic.ID}}' class="action_button unpin_item" data-action="unpin"></a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="action_button pin_item" data-action="pin"></a>{{end}}{{end}}
<a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="action_button report_item">Report</a> {{if .CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.Topic.IPAddress}}" title="IP Address" class="action_button ip_item_button hide_on_big" aria-label="This user's IP" data-action="ip"></a>{{end}}
<a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="action_button report_item" aria-label="Report this post" data-action="report"></a>
<a href="#" class="action_button button_menu"></a>
{{end}} {{end}}
<div class="action_button_right"> <div class="action_button_right{{if .Topic.LikeCount}} has_likes{{end}}">
{{if .Topic.LikeCount}}<a class="action_button like_count hide_on_micro">{{.Topic.LikeCount}}</a>{{end}} {{if .Topic.LikeCount}}<a class="action_button like_count hide_on_micro">{{.Topic.LikeCount}}</a>{{end}}
<a class="action_button created_at hide_on_mobile">{{.Topic.RelativeCreatedAt}}</a> <a class="action_button created_at hide_on_mobile">{{.Topic.RelativeCreatedAt}}</a>
{{if .CurrentUser.Perms.ViewIPs}}<a href="#" title="IP Address" class="action_button ip_item hide_on_mobile">{{.Topic.IPAddress}}</a>{{end}} {{if .CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.Topic.IPAddress}}" title="IP Address" class="action_button ip_item hide_on_mobile">{{.Topic.IPAddress}}</a>{{end}}
</div> </div>
</div> </div>
</div><div style="clear:both;"></div> </div><div style="clear:both;"></div>
@ -68,15 +70,17 @@
<div class="editable_block user_content" itemprop="text">{{.ContentHtml}}</div> <div class="editable_block user_content" itemprop="text">{{.ContentHtml}}</div>
<div class="button_container"> <div class="button_container">
{{if $.CurrentUser.Loggedin}} {{if $.CurrentUser.Loggedin}}
{{if $.CurrentUser.Perms.LikeItem}}<a href="/reply/like/submit/{{.ID}}" class="action_button like_item add_like">+1</a>{{end}} {{if $.CurrentUser.Perms.LikeItem}}<a href="/reply/like/submit/{{.ID}}" class="action_button like_item add_like" aria-label="Like this post" data-action="like"></a>{{end}}
{{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="action_button edit_item">Edit</a>{{end}} {{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="action_button edit_item" aria-label="Edit this post" data-action="edit"></a>{{end}}
{{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="action_button delete_item">Delete</a>{{end}} {{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="action_button delete_item" aria-label="Delete this post" data-action="delete"></a>{{end}}
<a href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="action_button report_item">Report</a> {{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IPAddress}}" title="IP Address" class="action_button ip_item_button hide_on_big" aria-label="This user's IP Address" data-action="ip"></a>{{end}}
<a href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="action_button report_item" aria-label="Report this post" data-action="report"></a>
<a href="#" class="action_button button_menu"></a>
{{end}} {{end}}
<div class="action_button_right"> <div class="action_button_right{{if .LikeCount}} has_likes{{end}}">
{{if .LikeCount}}<a class="action_button like_count hide_on_micro">{{.LikeCount}}</a>{{end}} {{if .LikeCount}}<a class="action_button like_count hide_on_micro">{{.LikeCount}}</a>{{end}}
<a class="action_button created_at hide_on_mobile">{{.RelativeCreatedAt}}</a> <a class="action_button created_at hide_on_mobile">{{.RelativeCreatedAt}}</a>
{{if $.CurrentUser.Perms.ViewIPs}}<a href="#" title="IP Address" class="action_button ip_item hide_on_mobile">{{.IPAddress}}</a>{{end}} {{if $.CurrentUser.Perms.ViewIPs}}<a href="/users/ips/?ip={{.IPAddress}}" title="IP Address" class="action_button ip_item hide_on_mobile">{{.IPAddress}}</a>{{end}}
</div> </div>
</div> </div>
{{end}} {{end}}

View File

@ -35,7 +35,7 @@
{{if .ForumList}} {{if .ForumList}}
<div class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form"> <div class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form">
<form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> <form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<img class="little_row_avatar" src="{{.CurrentUser.Avatar}}" height="64" /> <img class="little_row_avatar" src="{{.CurrentUser.Avatar}}" height="64" alt="Your Avatar" />
<div class="main_form"> <div class="main_form">
<div class="topic_meta"> <div class="topic_meta">
<div class="formrow topic_board_row real_first_child"> <div class="formrow topic_board_row real_first_child">
@ -72,7 +72,7 @@
{{range .TopicList}}<div class="topic_row" data-tid="{{.ID}}"> {{range .TopicList}}<div class="topic_row" data-tid="{{.ID}}">
<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}"> <div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
<span class="selector"></span> <span class="selector"></span>
<a href="{{.Creator.Link}}"><img src="{{.Creator.Avatar}}" height="64" /></a> <a href="{{.Creator.Link}}"><img src="{{.Creator.Avatar}}" height="64" alt="{{.Creator.Name}}'s Avatar" /></a>
<span class="topic_inner_left"> <span class="topic_inner_left">
<a class="rowtopic" href="{{.Link}}"><span>{{.Title}}</span></a> {{if .ForumName}}<a class="rowsmall parent_forum" href="{{.ForumLink}}">{{.ForumName}}</a>{{end}} <a class="rowtopic" href="{{.Link}}"><span>{{.Title}}</span></a> {{if .ForumName}}<a class="rowsmall parent_forum" href="{{.ForumLink}}">{{.ForumName}}</a>{{end}}
<br /><a class="rowsmall starter" href="{{.Creator.Link}}">{{.Creator.Name}}</a> <br /><a class="rowsmall starter" href="{{.Creator.Link}}">{{.Creator.Name}}</a>
@ -86,7 +86,7 @@
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}"> <div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
<a href="{{.LastUser.Link}}"><img src="{{.LastUser.Avatar}}" height="64" /></a> <a href="{{.LastUser.Link}}"><img src="{{.LastUser.Avatar}}" height="64" alt="{{.LastUser.Name}}'s Avatar" /></a>
<span> <span>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br> <a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">{{.RelativeLastReplyAt}}</span> <span class="rowsmall lastReplyAt">{{.RelativeLastReplyAt}}</span>

View File

@ -12,6 +12,9 @@
--inverse-primary-text-color: white; --inverse-primary-text-color: white;
--light-text-color: hsl(0,0%,55%); --light-text-color: hsl(0,0%,55%);
--lighter-text-color: hsl(0,0%,65%); --lighter-text-color: hsl(0,0%,65%);
/*background-color: hsl(0,0%,97%);*/
--tinted-background-color: hsl(0,0%,98%);
} }
* { * {
@ -40,8 +43,7 @@ a {
} }
body, #back { body, #back {
/*background-color: hsl(0,0%,97%);*/ background-color: var(--tinted-background-color);
background-color: hsl(0,0%,98%);
} }
#back { #back {
padding: 8px; padding: 8px;
@ -195,7 +197,8 @@ ul {
border-bottom: 2px solid var(--header-border-color); border-bottom: 2px solid var(--header-border-color);
margin-left: 12px; margin-left: 12px;
} }
.rowblock:not(.topic_list):not(.forum_list), .colstack_head, .topic_row .rowitem, .forum_list .rowitem { /* TODO: Reduce the number of nots */
.rowblock:not(.topic_list):not(.forum_list):not(.post_container):not(.topic_reply_container), .colstack_head, .topic_row .rowitem, .forum_list .rowitem {
background-color: var(--element-background-color); background-color: var(--element-background-color);
} }
.rowblock { .rowblock {
@ -407,7 +410,7 @@ ul {
border-bottom: 1px solid var(--header-border-color); border-bottom: 1px solid var(--header-border-color);
outline: none; outline: none;
} }
.topic_name_row input { .topic_name_row input, .ip_search_input {
width: 100%; width: 100%;
border: none; border: none;
border-bottom: 1px solid var(--header-border-color); border-bottom: 1px solid var(--header-border-color);
@ -424,8 +427,7 @@ ul {
display: flex; display: flex;
margin-left: 2px; margin-left: 2px;
} }
.quick_button_row button, .quick_button_row label { .quick_button_row button, .quick_button_row label, .ip_search_search {
margin-left: 10px;
padding-left: 10px; padding-left: 10px;
padding-right: 10px; padding-right: 10px;
padding-top: 6px; padding-top: 6px;
@ -433,7 +435,6 @@ ul {
color: var(--inverse-primary-text-color); color: var(--inverse-primary-text-color);
font-size: 13px; font-size: 13px;
font-weight: bold; font-weight: bold;
margin-top: 8px;
border-width: initial; border-width: initial;
border-style: none; border-style: none;
border-color: initial; border-color: initial;
@ -442,6 +443,10 @@ ul {
background: hsl(209, 97%, 56%); background: hsl(209, 97%, 56%);
border-radius: 2px; border-radius: 2px;
} }
.quick_button_row button, .quick_button_row label {
margin-left: 10px;
margin-top: 8px;
}
.quick_button_row .add_file_button { .quick_button_row .add_file_button {
background: hsl(129, 57%, 56%); background: hsl(129, 57%, 56%);
} }
@ -527,14 +532,25 @@ select, input, textarea, button {
grid-template-columns: repeat(auto-fill, 150px); grid-template-columns: repeat(auto-fill, 150px);
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
grid-gap: 6px 12px; grid-gap: 6px 12px;
border: none;
background: none !important;
} }
.rowlist .rowitem { .rowlist .rowitem {
display: flex; display: flex;
} }
.rowlist.bgavatars .rowitem { .bgavatars .rowitem {
background-image: none !important; background-image: none !important;
}
.rowlist.bgavatars .rowitem {
flex-direction: column; flex-direction: column;
} }
.bgavatars .bgsub {
border-radius: 30px;
height: 48px;
width: 48px;
margin-top: 8px;
margin-left: 4px;
}
.rowlist.bgavatars .bgsub { .rowlist.bgavatars .bgsub {
height: 80px; height: 80px;
width: 80px; width: 80px;
@ -629,12 +645,10 @@ select, input, textarea, button {
content: var(--replies-lang-string); content: var(--replies-lang-string);
color: var(--lightened-primary-text-color); color: var(--lightened-primary-text-color);
} }
.topic_inner_right .topicCount:after { .topic_inner_right .topicCount:after {
content: var(--topics-lang-string); content: var(--topics-lang-string);
color: var(--lightened-primary-text-color); color: var(--lightened-primary-text-color);
} }
.topic_inner_right .likeCount:after { .topic_inner_right .likeCount:after {
content: var(--likes-lang-string); content: var(--likes-lang-string);
color: var(--lightened-primary-text-color); color: var(--lightened-primary-text-color);
@ -645,7 +659,7 @@ select, input, textarea, button {
} }
.topic_right { .topic_right {
flex: 1 1 0px; /*150px*/ flex: 1 1 0px;
border-left: none; border-left: none;
} }
@ -656,7 +670,6 @@ select, input, textarea, button {
margin-top: 8px; margin-top: 8px;
margin-left: 4px; margin-left: 4px;
} }
.topic_right img { .topic_right img {
border-radius: 30px; border-radius: 30px;
height: 42px; height: 42px;
@ -670,7 +683,6 @@ select, input, textarea, button {
margin-bottom: 14px; margin-bottom: 14px;
width: 240px; width: 240px;
} }
.topic_right > span { .topic_right > span {
margin-top: 12px; margin-top: 12px;
margin-left: 8px; margin-left: 8px;
@ -723,6 +735,7 @@ select, input, textarea, button {
margin-bottom: 16px; margin-bottom: 16px;
} }
.userinfo, .content_container { .userinfo, .content_container {
background-color: var(--element-background-color);
border: 1px solid var(--element-border-color); border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-color); border-bottom: 2px solid var(--element-border-color);
} }
@ -781,7 +794,9 @@ select, input, textarea, button {
margin-right: 6px; margin-right: 6px;
} }
.post_item .add_like:after, .created_at:before, .ip_item:before { .post_item .add_like:after,
.created_at:before,
.ip_item:before {
border-left: 1px solid var(--element-border-color); border-left: 1px solid var(--element-border-color);
content: ""; content: "";
margin-top: 1px; margin-top: 1px;
@ -794,11 +809,52 @@ select, input, textarea, button {
margin-left: 10px; margin-left: 10px;
margin-right: 5px; margin-right: 5px;
} }
.created_at { .created_at {
margin-right: 10px; margin-right: 10px;
} }
.add_like:before {
content: "+1";
}
.button_container .open_edit:after, .edit_item:after{
content: "Edit";
}
.delete_item:after {
content: "Delete";
}
.ip_item_button:after {
content: "IP";
}
.lock_item:after {
content: "Lock";
}
.unlock_item:after {
content: "Unlock";
}
.pin_item:after {
content: "Pin";
}
.unpin_item:after {
content: "Unpin";
}
.report_item:after {
content: "Report";
}
#ip_search_container .rowlist .rowitem {
padding-top: 16px;
padding-bottom: 10px;
}
.ip_search_block .rowitem {
padding: 8px;
padding-left: 12px;
padding-right: 12px;
}
.ip_search_input {
margin-right: 12px;
}
.ip_search_block .rowitem,
#profile_left_pane .topBlock { #profile_left_pane .topBlock {
display: flex; display: flex;
} }
@ -870,6 +926,9 @@ select, input, textarea, button {
#profile_right_lane .topic_reply_form { #profile_right_lane .topic_reply_form {
width: auto; width: auto;
} }
#profile_comments .rowitem {
background-image: none !important;
}
.colstack_item .formrow { .colstack_item .formrow {
display: flex; display: flex;
@ -1023,7 +1082,7 @@ select, input, textarea, button {
/* TODO: Move these to panel.css */ /* TODO: Move these to panel.css */
#dash-version:before, #dash-cpu:before, #dash-ram:before { #dash-version:before, #dash-cpu:before, #dash-ram:before {
display: inline-block; display: inline-block;
background: hsl(0,0%,98%); background: var(--tinted-background-color);
font: normal normal normal 14px/1 FontAwesome; font: normal normal normal 14px/1 FontAwesome;
font-size: 20px; font-size: 20px;
padding-left: 17px; padding-left: 17px;
@ -1041,12 +1100,88 @@ select, input, textarea, button {
content: "\f233"; content: "\f233";
} }
@media(min-width: 721px) {
.hide_on_big {
display: none;
}
}
@media(max-width: 720px) {
.menu_profile, .ip_item {
display: none;
}
.like_count {
margin-right: 1px;
}
.like_count:after {
margin-right: 6px;
}
.created_at:before, .ip_item:before {
margin-right: 6px;
}
}
@media(max-width: 670px) { @media(max-width: 670px) {
.topic_inner_right { .topic_inner_right {
display: none; display: none;
} }
} }
@media(max-width: 520px) { @media(max-width: 620px) {
.userinfo .avatar_item {
width: 72px;
height: 72px;
}
}
@media(max-width: 610px) {
.userinfo {
padding-top: 24px;
padding-left: 34px;
padding-right: 34px;
padding-bottom: 14px;
}
.userinfo .avatar_item {
height: 64px;
width: 64px;
/*background-size: 82px;*/
}
}
@media(max-width: 590px) {
#main {
padding-left: 4px;
padding-right: 4px;
}
.post_item {
margin-bottom: 12px;
}
.userinfo {
margin-right: 12px;
padding-top: 20px;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 12px;
}
.userinfo .avatar_item {
width: 52px;
height: 52px;
margin-bottom: 10px;
background-size: 72px;
margin-left: auto;
margin-right: auto;
}
.post_tag {
font-size: 15px;
}
.content_container {
padding: 15px;
}
}
@media(max-width: 550px) {
.nav {
border-bottom: 1px solid var(--header-border-color);
}
.menu_profile {
display: block;
}
#menu_overview { #menu_overview {
font-size: 18px; font-size: 18px;
background-color: hsl(0,0%,97%); background-color: hsl(0,0%,97%);
@ -1081,9 +1216,19 @@ select, input, textarea, button {
.alert_aftercounter { .alert_aftercounter {
display: none; display: none;
} }
#back {
padding-top: 0px;
}
.rowhead h1, .opthead h1, .colstack_head h1 { .rowhead h1, .opthead h1, .colstack_head h1 {
font-size: 18px; font-size: 18px;
} }
main > .rowhead, #main > .rowhead {
margin-left: 0px;
margin-right: 0px;
border: none;
border-bottom: 2px solid var(--header-border-color);
}
.topic_list { .topic_list {
display: flex; display: flex;
@ -1120,6 +1265,183 @@ select, input, textarea, button {
margin-top: 6px; margin-top: 6px;
margin-bottom: 6px; margin-bottom: 6px;
} }
.button_container {
border-top: 1px solid var(--element-border-color);
}
.action_button {
padding-bottom: 15px;
padding-left: 10px;
padding-right: 8px;
padding-top: 15px;
font-size: 12px;
}
.action_button:not(.add_like) {
font: normal normal normal 14px/1 FontAwesome;
}
.action_button_right.has_likes {
margin-left: 0px;
width: 100%;
}
.like_item {
background-color: hsl(0,0%,97%);
}
.post_item:not(.top_post) .like_item {
border-bottom: 1px solid var(--element-border-color);
}
.post_item .add_like:after {
border-left: none;
margin: inherit;
}
.content_container {
padding: 0px;
}
.user_content {
padding: 12px;
}
.button_container .open_edit:after, .edit_item:after{
content: "\f040";
}
.delete_item:after {
content: "\f014";
}
.ip_item_button:after {
content: "\f0ac";
}
.lock_item:after {
content: "\f023";
}
.unlock_item:after {
content: "\f09c";
}
.pin_item:after, .unpin_item:after {
content: "\f08d";
}
.report_item:after {
content: "\f024";
}
.unpin_item, .unlock_item {
background-color: hsl(80,50%,97%);
}
.like_count, .like_count:before {
font-family: arial;
}
.like_count:after {
content: "";
}
.like_count:before {
content: "+";
font-weight: normal;
}
.created_at {
margin-left: auto;
}
.created_at:before {
border-left: none;
margin: inherit;
}
.topic_reply_form .trumbowyg-editor {
padding: 15px;
}
.trumbowyg-editor[contenteditable=true]:empty:not(:focus)::before {
font-size: 15px;
}
.trumbowyg-button-pane .trumbowyg-button-group:first-child {
margin-left: 0px !important;
}
.trumbowyg-button-pane .trumbowyg-button-group:after,
.trumbowyg-button-pane .trumbowyg-button-group:first-child:before {
margin: inherit !important;
border: none !important;
}
}
@media(min-width: 521px) {
.button_menu {
display: none;
}
}
@media(max-width: 520px) {
main > .rowhead, #main > .rowhead {
margin-left: -3px;
margin-right: -3px;
}
.edit_item, .button_container .open_edit, .delete_item, .pin_item, .unpin_item, .lock_item, .unlock_item, .ip_item_button, .report_item {
display: none;
}
.button_menu:after {
content: "\f013";
}
.button_menu_pane {
display: flex;
flex-direction: column;
background-color: var(--element-background-color);
border: 2px solid var(--element-border-color);
position: fixed;
left: 50%;
top: 110px;
width: 300px;
transform: translateX(-50%);
z-index: 200;
}
.button_menu_pane > *:not(:last-child) {
border-bottom: 1px solid var(--element-border-color);
}
.button_menu_pane .userinfo {
display: flex;
flex-direction: row;
width: 100%;
padding-top: 12px;
}
.button_menu_pane .avatar_item {
width: 42px;
height: 42px;
background-size: 62px;
margin-left: 0px;
margin-right: 10px;
margin-bottom: 0px;
}
.button_menu_pane .userinfo .the_name {
margin-right: 0px;
}
/* TODO: Make this grid more flexible so that plugins can add new items more easily */
.button_menu_pane .buttonGrid {
display: grid;
grid-template-columns: repeat(8, 1fr);
border-left: 1px solid var(--element-border-color);
border-bottom: 1px solid var(--element-border-color);
}
.button_menu_pane .action_button {
display: flex;
margin: 0px;
padding-left: 0px;
padding-right: 0px;
background-color: var(--element-background-color);
margin-left: auto;
margin-right: auto;
width: 42px;
height: 42px;
font-size: 15px;
border-right: 1px solid var(--element-border-color);
border-bottom: 1px solid var(--element-border-color);
}
.button_menu_pane .action_button:nth-child(8n) {
border-right: none;
}
.button_menu_pane .action_button:nth-last-child(-n+8) {
border-bottom: none;
}
.button_menu_pane .action_button:after, .button_menu_pane .add_like:before {
margin-left: auto;
margin-right: auto;
}
.button_menu_pane .open_edit:after {
content: "\f040";
}
.button_menu_pane .gridFiller {
background-color: var(--tinted-background-color);
}
} }
@media(max-width: 450px) { @media(max-width: 450px) {
.topic_list .topic_row { .topic_list .topic_row {
@ -1130,4 +1452,18 @@ select, input, textarea, button {
.topic_list .topic_row:nth-child(odd) { .topic_list .topic_row:nth-child(odd) {
margin-right: 0px; margin-right: 0px;
} }
}
@media(max-width: 440px) {
#main {
padding-left: 0px;
padding-right: 0px;
}
.userinfo {
padding-left: 18px;
padding-right: 18px;
margin-right: 10px;
}
.the_name {
font-size: 17px;
}
} }

View File

@ -9,13 +9,83 @@ $(document).ready(function(){
$(".topic_create_form").addClass("selectedInput"); $(".topic_create_form").addClass("selectedInput");
}); });
//$.trumbowyg.svgPath = false; //$.trumbowyg.svgPath = false;
// TODO: Bind this to the viewport resize event
var btnlist = [];
if(document.documentElement.clientWidth > 550) {
btnlist = [['viewHTML'],['undo','redo'],['formatting'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']];
} else {
btnlist = [['viewHTML'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']];
}
$('.topic_create_form #input_content').trumbowyg({ $('.topic_create_form #input_content').trumbowyg({
btns: [['viewHTML'],['undo','redo'],['formatting'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']], btns: btnlist,
//hideButtonTexts: true
}); });
$('.topic_reply_form #input_content').trumbowyg({ $('.topic_reply_form #input_content').trumbowyg({
btns: [['viewHTML'],['undo','redo'],['formatting'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']], btns: btnlist,
autogrow: true, autogrow: true,
//hideButtonTexts: true
}); });
});
// TODO: Refactor this to use `each` less
$('.button_menu').click(function(){
console.log(".button_menu");
// The outer container
let buttonPane = newElement("div","button_menu_pane");
let postItem = $(this).parents('.post_item');
// Create the userinfo row in the pane
let userInfo = newElement("div","userinfo");
postItem.find('.avatar_item').each(function(){
userInfo.appendChild(this);
});
let userText = newElement("div","userText");
postItem.find('.userinfo:not(.avatar_item)').children().each(function(){
userText.appendChild(this);
});
userInfo.appendChild(userText);
buttonPane.appendChild(userInfo);
// Copy a short preview of the post contents into the pane
postItem.find('.user_content').each(function(){
// TODO: Truncate an excessive number of lines to 5 or so
let contents = this.innerHTML;
if(contents.length > 45) {
this.innerHTML = contents.substring(0,45) + "...";
}
buttonPane.appendChild(this);
});
// Copy the buttons from the post to the pane
let buttonGrid = newElement("div","buttonGrid");
let gridElementCount = 0;
$(this).parent().children('a:not(.button_menu)').each(function(){
buttonGrid.appendChild(this);
gridElementCount++;
});
// Fill in the placeholder grid nodes
let rowCount = 4;
console.log("rowCount: ",rowCount);
console.log("gridElementCount: ",gridElementCount);
if(gridElementCount%rowCount != 0) {
let fillerNodes = (rowCount - (gridElementCount%rowCount));
console.log("fillerNodes: ",fillerNodes);
for(let i = 0; i < fillerNodes;i++ ) {
console.log("added a gridFiller");
buttonGrid.appendChild(newElement("div","gridFiller"));
}
}
buttonPane.appendChild(buttonGrid);
document.getElementById("back").appendChild(buttonPane);
});
});
function newElement(etype, eclass) {
let element = document.createElement(etype);
element.className = eclass;
return element;
}

View File

@ -122,7 +122,7 @@ li {
content: "Alerts"; content: "Alerts";
} }
.menu_alerts .alertList, .auto_hide { .menu_alerts .alertList, .auto_hide, .hide_on_big, .show_on_mobile {
display: none; display: none;
} }
.selectedAlert .alertList { .selectedAlert .alertList {
@ -425,7 +425,6 @@ textarea.large {
.formitem a { .formitem a {
font-size: 14px; font-size: 14px;
} }
.rowmenu .rowitem, .rowlist .rowitem, .rowlist .formitem { .rowmenu .rowitem, .rowlist .rowitem, .rowlist .formitem {
margin-top: 3px; margin-top: 3px;
font-size: 13px; font-size: 13px;
@ -448,6 +447,9 @@ textarea.large {
font-size: 13px; font-size: 13px;
} }
.bgsub {
display: none;
}
.rowlist.bgavatars .rowitem { .rowlist.bgavatars .rowitem {
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 40px; background-size: 40px;

View File

@ -1,7 +1,3 @@
.bgsub {
display: none;
}
.rowlist .tag-mini { .rowlist .tag-mini {
font-size: 10px; font-size: 10px;
margin-left: 2px; margin-left: 2px;

View File

@ -216,11 +216,14 @@ li a {
} }
} }
.bgsub {
display: none;
}
.rowlist .rowitem { .rowlist .rowitem {
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
} }
.rowlist.bgavatars .rowitem { .bgavatars .rowitem {
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 40px; background-size: 40px;
padding-left: 48px; padding-left: 48px;
@ -616,7 +619,7 @@ button.username {
.mention { .mention {
font-weight: bold; font-weight: bold;
} }
.show_on_edit, .auto_hide { .show_on_edit, .auto_hide, .hide_on_big, .show_on_mobile, .button_menu {
display: none; display: none;
} }
@ -884,6 +887,31 @@ button.username {
margin-bottom: 6px !important; margin-bottom: 6px !important;
} }
.add_like:before {
content: "+1";
}
.button_container .open_edit:after, .edit_item:after {
content: "Edit";
}
.delete_item:after {
content: "Delete";
}
.lock_item:after {
content: "Lock";
}
.unlock_item:after {
content: "Unlock";
}
.pin_item:after {
content: "Pin";
}
.unpin_item:after {
content: "Unpin";
}
.report_item:after {
content: "Report";
}
#poweredByHolder { #poweredByHolder {
border: 1px solid var(--main-border-color); border: 1px solid var(--main-border-color);
margin-top: 12px; margin-top: 12px;

View File

@ -1,9 +1,5 @@
/* Control Panel */ /* Control Panel */
.bgsub {
display: none;
}
.edit_button:before { .edit_button:before {
content: "Edit"; content: "Edit";
} }

View File

@ -173,7 +173,10 @@ li a {
font-size: 12px; font-size: 12px;
} }
.rowlist.bgavatars .rowitem { .bgsub {
display: none;
}
.bgavatars .rowitem {
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 50px; background-size: 50px;
padding-left: 58px; padding-left: 58px;
@ -503,7 +506,7 @@ button.username {
.mention { .mention {
font-weight: bold; font-weight: bold;
} }
.show_on_edit, .auto_hide { .show_on_edit, .auto_hide, .hide_on_big, .show_on_mobile {
display: none; display: none;
} }

View File

@ -337,7 +337,10 @@ li a {
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
} }
.rowlist.bgavatars .rowitem { .bgsub {
display: none;
}
.bgavatars .rowitem {
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 40px; background-size: 40px;
padding-left: 46px; padding-left: 46px;
@ -679,7 +682,7 @@ button.username {
.mention { .mention {
font-weight: bold; font-weight: bold;
} }
.show_on_edit, .auto_hide { .show_on_edit, .auto_hide, .hide_on_big, .show_on_mobile {
display: none; display: none;
} }

View File

@ -1,9 +1,5 @@
/* Control Panel */ /* Control Panel */
.bgsub {
display: none;
}
.edit_button:before { .edit_button:before {
content: "Edit"; content: "Edit";
} }