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...)
}
}
// 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 (
"database/sql"
"log"
"sync"
"sync/atomic"
"../query_gen/lib"
)
var GlobalViewCounter *BufferedViewCounter
var GlobalViewCounter *ChunkedViewCounter
var RouteViewCounter *RouteViewCounterImpl
type BufferedViewCounter struct {
type ChunkedViewCounter struct {
buckets [2]int64
currentBucket int64
insert *sql.Stmt
}
func NewGlobalViewCounter() (*BufferedViewCounter, error) {
func NewChunkedViewCounter() (*ChunkedViewCounter, error) {
acc := qgen.Builder.Accumulator()
counter := &BufferedViewCounter{
counter := &ChunkedViewCounter{
currentBucket: 0,
insert: acc.SimpleInsert("viewchunks", "count, createdAt", "?,UTC_TIMESTAMP()"),
insert: acc.Insert("viewchunks").Columns("count, createdAt").Fields("?,UTC_TIMESTAMP()").Prepare(),
}
//AddScheduledFifteenMinuteTask(counter.Tick)
AddScheduledSecondTask(counter.Tick)
AddScheduledFifteenMinuteTask(counter.Tick) // This is run once every fifteen minutes to match the frequency of the RouteViewCounter
//AddScheduledSecondTask(counter.Tick)
return counter, acc.FirstError()
}
func (counter *BufferedViewCounter) Tick() (err error) {
func (counter *ChunkedViewCounter) Tick() (err error) {
var oldBucket = counter.currentBucket
var nextBucket int64
if counter.currentBucket == 1 {
@ -38,22 +41,17 @@ func (counter *BufferedViewCounter) Tick() (err error) {
atomic.AddInt64(&counter.buckets[oldBucket], counter.buckets[nextBucket])
atomic.StoreInt64(&counter.buckets[nextBucket], 0)
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]
atomic.AddInt64(&counter.buckets[oldBucket], -previousViewChunk)
return counter.insertChunk(previousViewChunk)
}
func (counter *BufferedViewCounter) Bump() {
func (counter *ChunkedViewCounter) Bump() {
atomic.AddInt64(&counter.buckets[counter.currentBucket], 1)
}
func (counter *BufferedViewCounter) insertChunk(count int64) error {
func (counter *ChunkedViewCounter) insertChunk(count int64) error {
if count == 0 {
return nil
}
@ -61,3 +59,194 @@ func (counter *BufferedViewCounter) insertChunk(count int64) error {
_, err := counter.insert.Exec(count)
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: Add support for TLS
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?
if Vhooks["email_send_intercept"] != nil {
@ -68,6 +69,6 @@ func SendEmail(email string, subject string, msg string) bool {
if err != nil {
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 {
w.WriteHeader(500)
_, _ = w.Write([]byte(`{'errmsg': '` + errmsg + `'}`))
_, _ = w.Write([]byte(`{"errmsg": "` + errmsg + `"}`))
return HandledRouteError()
}

View File

@ -284,6 +284,14 @@ func preRoute(w http.ResponseWriter, r *http.Request) (User, bool) {
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
func AdminOnly(w http.ResponseWriter, r *http.Request, user User) RouteError {
if !user.IsAdmin {

View File

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

View File

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

View File

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

View File

@ -13,7 +13,8 @@ import (
)
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,
"routeOverview": routeOverview,
"routeForums": routeForums,
@ -72,9 +73,133 @@ var RouteMap = map[string]interface{}{
"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 {
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
}
@ -82,7 +207,7 @@ type GenRouter struct {
func NewGenRouter(uploads http.Handler) *GenRouter {
return &GenRouter{
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) {
router.Lock()
defer router.Unlock()
router.extra_routes[pattern] = handle
router.extraRoutes[pattern] = handle
}
func (router *GenRouter) RemoveFunc(pattern string) error {
router.Lock()
defer router.Unlock()
_, ok := router.extra_routes[pattern]
_, ok := router.extraRoutes[pattern]
if !ok {
return ErrNoRoute
}
delete(router.extra_routes, pattern)
delete(router.extraRoutes, pattern)
return nil
}
@ -125,10 +250,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
var prefix, extra_data string
var prefix, extraData string
prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 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]
}
@ -136,12 +261,12 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("before routeStatic")
log.Print("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extra_data: ", extra_data)
log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer())
}
if prefix == "/static" {
req.URL.Path += extra_data
req.URL.Path += extraData
routeStatic(w, req)
return
}
@ -159,27 +284,32 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
if common.Dev.SuperDebug {
log.Print("after PreRoute")
log.Print("routeMapEnum: ", routeMapEnum)
}
var err common.RouteError
switch(prefix) {
case "/api":
common.RouteViewCounter.Bump(0)
err = routeAPI(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
}
case "/overview":
common.RouteViewCounter.Bump(1)
err = routeOverview(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
}
case "/forums":
common.RouteViewCounter.Bump(2)
err = routeForums(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
}
case "/forum":
err = routeForum(w,req,user,extra_data)
common.RouteViewCounter.Bump(3)
err = routeForum(w,req,user,extraData)
if err != nil {
router.handleError(err,w,req,user)
}
@ -190,6 +320,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(4)
err = routeChangeTheme(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
@ -201,7 +332,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routeShowAttachment(w,req,user,extra_data)
common.RouteViewCounter.Bump(5)
err = routeShowAttachment(w,req,user,extraData)
if err != nil {
router.handleError(err,w,req,user)
}
@ -226,7 +358,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routeReportSubmit(w,req,user,extra_data)
common.RouteViewCounter.Bump(6)
err = routeReportSubmit(w,req,user,extraData)
}
if err != nil {
router.handleError(err,w,req,user)
@ -240,8 +373,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routeTopicCreate(w,req,user,extra_data)
common.RouteViewCounter.Bump(7)
err = routeTopicCreate(w,req,user,extraData)
default:
common.RouteViewCounter.Bump(8)
err = routeTopics(w,req,user)
}
if err != nil {
@ -256,6 +391,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch(req.URL.Path) {
case "/panel/forums/":
common.RouteViewCounter.Bump(9)
err = routePanelForums(w,req,user)
case "/panel/forums/create/":
err = common.NoSessionMismatch(w,req,user)
@ -264,6 +400,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(10)
err = routePanelForumsCreateSubmit(w,req,user)
case "/panel/forums/delete/":
err = common.NoSessionMismatch(w,req,user)
@ -272,7 +409,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelForumsDelete(w,req,user,extra_data)
common.RouteViewCounter.Bump(11)
err = routePanelForumsDelete(w,req,user,extraData)
case "/panel/forums/delete/submit/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -280,9 +418,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelForumsDeleteSubmit(w,req,user,extra_data)
common.RouteViewCounter.Bump(12)
err = routePanelForumsDeleteSubmit(w,req,user,extraData)
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/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -290,7 +430,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelForumsEditSubmit(w,req,user,extra_data)
common.RouteViewCounter.Bump(14)
err = routePanelForumsEditSubmit(w,req,user,extraData)
case "/panel/forums/edit/perms/submit/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -298,11 +439,14 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelForumsEditPermsSubmit(w,req,user,extra_data)
common.RouteViewCounter.Bump(15)
err = routePanelForumsEditPermsSubmit(w,req,user,extraData)
case "/panel/settings/":
common.RouteViewCounter.Bump(16)
err = routePanelSettings(w,req,user)
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/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -310,8 +454,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelSettingEditSubmit(w,req,user,extra_data)
common.RouteViewCounter.Bump(18)
err = routePanelSettingEditSubmit(w,req,user,extraData)
case "/panel/settings/word-filters/":
common.RouteViewCounter.Bump(19)
err = routePanelWordFilters(w,req,user)
case "/panel/settings/word-filters/create/":
err = common.NoSessionMismatch(w,req,user)
@ -320,9 +466,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(20)
err = routePanelWordFiltersCreate(w,req,user)
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/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -330,7 +478,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
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/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -338,8 +487,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelWordFiltersDeleteSubmit(w,req,user,extra_data)
common.RouteViewCounter.Bump(23)
err = routePanelWordFiltersDeleteSubmit(w,req,user,extraData)
case "/panel/themes/":
common.RouteViewCounter.Bump(24)
err = routePanelThemes(w,req,user)
case "/panel/themes/default/":
err = common.NoSessionMismatch(w,req,user)
@ -348,8 +499,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelThemesSetDefault(w,req,user,extra_data)
common.RouteViewCounter.Bump(25)
err = routePanelThemesSetDefault(w,req,user,extraData)
case "/panel/plugins/":
common.RouteViewCounter.Bump(26)
err = routePanelPlugins(w,req,user)
case "/panel/plugins/activate/":
err = common.NoSessionMismatch(w,req,user)
@ -358,7 +511,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelPluginsActivate(w,req,user,extra_data)
common.RouteViewCounter.Bump(27)
err = routePanelPluginsActivate(w,req,user,extraData)
case "/panel/plugins/deactivate/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -366,7 +520,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelPluginsDeactivate(w,req,user,extra_data)
common.RouteViewCounter.Bump(28)
err = routePanelPluginsDeactivate(w,req,user,extraData)
case "/panel/plugins/install/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -374,11 +529,14 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelPluginsInstall(w,req,user,extra_data)
common.RouteViewCounter.Bump(29)
err = routePanelPluginsInstall(w,req,user,extraData)
case "/panel/users/":
common.RouteViewCounter.Bump(30)
err = routePanelUsers(w,req,user)
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/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -386,13 +544,17 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelUsersEditSubmit(w,req,user,extra_data)
common.RouteViewCounter.Bump(32)
err = routePanelUsersEditSubmit(w,req,user,extraData)
case "/panel/groups/":
common.RouteViewCounter.Bump(33)
err = routePanelGroups(w,req,user)
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/":
err = routePanelGroupsEditPerms(w,req,user,extra_data)
common.RouteViewCounter.Bump(35)
err = routePanelGroupsEditPerms(w,req,user,extraData)
case "/panel/groups/edit/submit/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -400,7 +562,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelGroupsEditSubmit(w,req,user,extra_data)
common.RouteViewCounter.Bump(36)
err = routePanelGroupsEditSubmit(w,req,user,extraData)
case "/panel/groups/edit/perms/submit/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -408,7 +571,8 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routePanelGroupsEditPermsSubmit(w,req,user,extra_data)
common.RouteViewCounter.Bump(37)
err = routePanelGroupsEditPermsSubmit(w,req,user,extraData)
case "/panel/groups/create/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
@ -416,10 +580,19 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(38)
err = routePanelGroupsCreateSubmit(w,req,user)
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/":
common.RouteViewCounter.Bump(40)
err = routePanelLogsMod(w,req,user)
case "/panel/debug/":
err = common.AdminOnly(w,req,user)
@ -428,8 +601,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(41)
err = routePanelDebug(w,req,user)
default:
common.RouteViewCounter.Bump(42)
err = routePanel(w,req,user)
}
if err != nil {
@ -444,6 +619,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(43)
err = routeAccountEditCritical(w,req,user)
case "/user/edit/critical/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -458,6 +634,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(44)
err = routeAccountEditCriticalSubmit(w,req,user)
case "/user/edit/avatar/":
err = common.MemberOnly(w,req,user)
@ -466,6 +643,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(45)
err = routeAccountEditAvatar(w,req,user)
case "/user/edit/avatar/submit/":
err = common.MemberOnly(w,req,user)
@ -474,6 +652,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(46)
err = routeAccountEditAvatarSubmit(w,req,user)
case "/user/edit/username/":
err = common.MemberOnly(w,req,user)
@ -482,6 +661,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(47)
err = routeAccountEditUsername(w,req,user)
case "/user/edit/username/submit/":
err = common.NoSessionMismatch(w,req,user)
@ -496,6 +676,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(48)
err = routeAccountEditUsernameSubmit(w,req,user)
case "/user/edit/email/":
err = common.MemberOnly(w,req,user)
@ -504,6 +685,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(49)
err = routeAccountEditEmail(w,req,user)
case "/user/edit/token/":
err = common.NoSessionMismatch(w,req,user)
@ -518,9 +700,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
err = routeAccountEditEmailTokenSubmit(w,req,user,extra_data)
common.RouteViewCounter.Bump(50)
err = routeAccountEditEmailTokenSubmit(w,req,user,extraData)
default:
req.URL.Path += extra_data
req.URL.Path += extraData
common.RouteViewCounter.Bump(51)
err = routeProfile(w,req,user)
}
if err != nil {
@ -541,6 +725,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(52)
err = routeBanSubmit(w,req,user)
case "/users/unban/":
err = common.NoSessionMismatch(w,req,user)
@ -555,6 +740,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(53)
err = routeUnban(w,req,user)
case "/users/activate/":
err = common.NoSessionMismatch(w,req,user)
@ -569,6 +755,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(54)
err = routeActivate(w,req,user)
case "/users/ips/":
err = common.MemberOnly(w,req,user)
@ -577,50 +764,51 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
common.RouteViewCounter.Bump(55)
err = routeIps(w,req,user)
}
if err != nil {
router.handleError(err,w,req,user)
}
case "/uploads":
if extra_data == "" {
if extraData == "" {
common.NotFound(w,req)
return
}
req.URL.Path += extra_data
req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this?
router.UploadHandler(w,req)
router.UploadHandler(w,req) // TODO: Count these views
case "":
// Stop the favicons, robots.txt file, etc. resolving to the topics list
// TODO: Add support for favicons and robots.txt files
switch(extra_data) {
switch(extraData) {
case "robots.txt":
err = routeRobotsTxt(w,req)
err = routeRobotsTxt(w,req) // TODO: Count these views
if err != nil {
router.handleError(err,w,req,user)
}
return
}
if extra_data != "" {
if extraData != "" {
common.NotFound(w,req)
return
}
common.Config.DefaultRoute(w,req,user)
common.Config.DefaultRoute(w,req,user) // TODO: Count these views
default:
// A fallback for the routes which haven't been converted to the new router yet or plugins
router.RLock()
handle, ok := router.extra_routes[req.URL.Path]
handle, ok := router.extraRoutes[req.URL.Path]
router.RUnlock()
if ok {
req.URL.Path += extra_data
err = handle(w,req,user)
req.URL.Path += extraData
err = handle(w,req,user) // TODO: Count these views
if err != nil {
router.handleError(err,w,req,user)
}
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 {
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 {
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")
// 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) {

View File

@ -1569,9 +1569,6 @@ func routePanelBackups(w http.ResponseWriter, r *http.Request, user common.User,
if ferr != nil {
return ferr
}
if !user.IsSuperAdmin {
return common.NoPermissions(w, r, user)
}
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

View File

@ -159,6 +159,7 @@ func createTables(adapter qgen.Adapter) error {
qgen.DBTableColumn{"postCount", "int", 0, false, false, "1"},
qgen.DBTableColumn{"likeCount", "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{"data", "varchar", 200, false, false, "''"},
},
@ -358,10 +359,22 @@ func createTables(adapter qgen.Adapter) error {
[]qgen.DBTableColumn{
qgen.DBTableColumn{"count", "int", 0, false, false, "0"},
qgen.DBTableColumn{"createdAt", "datetime", 0, false, false, ""},
qgen.DBTableColumn{"route", "varchar", 200, false, false, ""},
},
[]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.DBTableColumn{
qgen.DBTableColumn{"last_update", "datetime", 0, false, false, ""},

View File

@ -2,25 +2,40 @@
package main
import (
"bytes"
"log"
"os"
"strconv"
"text/template"
)
//import "strings"
var routeList []*RouteImpl
var routeGroups []*RouteGroup
type TmplVars struct {
RouteList []*RouteImpl
RouteGroups []*RouteGroup
AllRouteNames []string
AllRouteMap map[string]int
}
func main() {
log.Println("Generating the router...")
// Load all the 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) {
routeMap += "\t\"" + name + "\": " + name + ",\n"
//reverseRouteMap += "\t" + name + ": \"" + name + "\",\n"
allRouteNames = append(allRouteNames, name)
allRouteMap[name] = len(allRouteNames) - 1
}
var countToIndents = func(indent int) (indentor string) {
for i := 0; i < indent; i++ {
@ -47,9 +62,11 @@ func main() {
}
for _, route := range routeList {
mapIt(route.Name)
var end = len(route.Path) - 1
out += "\n\t\tcase \"" + route.Path[0:end] + "\":"
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"
for _, item := range route.Vars {
out += "," + item
@ -58,7 +75,6 @@ func main() {
if err != nil {
router.handleError(err,w,req,user)
}`
mapIt(route.Name)
}
for _, group := range routeGroups {
@ -73,6 +89,7 @@ func main() {
defaultRoute = route
continue
}
mapIt(route.Name)
out += "\n\t\t\t\tcase \"" + route.Path + "\":"
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"
for _, item := range route.Vars {
out += "," + item
}
out += ")"
mapIt(route.Name)
}
if defaultRoute.Name != "" {
mapIt(defaultRoute.Name)
out += "\n\t\t\t\tdefault:"
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"
for _, item := range defaultRoute.Vars {
out += ", " + item
}
out += ")"
mapIt(defaultRoute.Name)
}
out += `
}
@ -132,6 +150,9 @@ func main() {
}`
}
tmplVars.AllRouteNames = allRouteNames
tmplVars.AllRouteMap = allRouteMap
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. */
package main
@ -147,12 +168,28 @@ import (
)
var ErrNoRoute = errors.New("That route doesn't exist.")
var RouteMap = map[string]interface{}{
` + routeMap + `}
// TODO: What about the /uploads/ route? x.x
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 {
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
}
@ -160,7 +197,7 @@ type GenRouter struct {
func NewGenRouter(uploads http.Handler) *GenRouter {
return &GenRouter{
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) {
router.Lock()
defer router.Unlock()
router.extra_routes[pattern] = handle
router.extraRoutes[pattern] = handle
}
func (router *GenRouter) RemoveFunc(pattern string) error {
router.Lock()
defer router.Unlock()
_, ok := router.extra_routes[pattern]
_, ok := router.extraRoutes[pattern]
if !ok {
return ErrNoRoute
}
delete(router.extra_routes, pattern)
delete(router.extraRoutes, pattern)
return nil
}
@ -203,10 +240,10 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
var prefix, extra_data string
var prefix, extraData string
prefix = req.URL.Path[0:strings.IndexByte(req.URL.Path[1:],'/') + 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]
}
@ -214,12 +251,12 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Print("before routeStatic")
log.Print("prefix: ", prefix)
log.Print("req.URL.Path: ", req.URL.Path)
log.Print("extra_data: ", extra_data)
log.Print("extraData: ", extraData)
log.Print("req.Referer(): ", req.Referer())
}
if prefix == "/static" {
req.URL.Path += extra_data
req.URL.Path += extraData
routeStatic(w, req)
return
}
@ -237,54 +274,62 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
if common.Dev.SuperDebug {
log.Print("after PreRoute")
log.Print("routeMapEnum: ", routeMapEnum)
}
var err common.RouteError
switch(prefix) {` + out + `
case "/uploads":
if extra_data == "" {
if extraData == "" {
common.NotFound(w,req)
return
}
req.URL.Path += extra_data
req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this?
router.UploadHandler(w,req)
router.UploadHandler(w,req) // TODO: Count these views
case "":
// Stop the favicons, robots.txt file, etc. resolving to the topics list
// TODO: Add support for favicons and robots.txt files
switch(extra_data) {
switch(extraData) {
case "robots.txt":
err = routeRobotsTxt(w,req)
err = routeRobotsTxt(w,req) // TODO: Count these views
if err != nil {
router.handleError(err,w,req,user)
}
return
}
if extra_data != "" {
if extraData != "" {
common.NotFound(w,req)
return
}
common.Config.DefaultRoute(w,req,user)
common.Config.DefaultRoute(w,req,user) // TODO: Count these views
default:
// A fallback for the routes which haven't been converted to the new router yet or plugins
router.RLock()
handle, ok := router.extra_routes[req.URL.Path]
handle, ok := router.extraRoutes[req.URL.Path]
router.RUnlock()
if ok {
req.URL.Path += extra_data
err = handle(w,req,user)
req.URL.Path += extraData
err = handle(w,req,user) // TODO: Count these views
if err != nil {
router.handleError(err,w,req,user)
}
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")
}

View File

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

View File

@ -1,4 +1,5 @@
CREATE TABLE [viewchunks] (
[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` (
`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` (
`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:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "strconv"
import "net/http"
import "./common"
import "strconv"
// nolint
func init() {
@ -143,54 +143,58 @@ w.Write([]byte(item.Creator.Link))
w.Write(forum_30)
w.Write([]byte(item.Creator.Avatar))
w.Write(forum_31)
w.Write([]byte(item.Link))
w.Write([]byte(item.Creator.Avatar))
w.Write(forum_32)
w.Write([]byte(item.Title))
w.Write([]byte(item.Link))
w.Write(forum_33)
w.Write([]byte(item.Creator.Link))
w.Write([]byte(item.Title))
w.Write(forum_34)
w.Write([]byte(item.Creator.Name))
w.Write([]byte(item.Creator.Link))
w.Write(forum_35)
if item.IsClosed {
w.Write([]byte(item.Creator.Name))
w.Write(forum_36)
}
if item.Sticky {
if item.IsClosed {
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 {
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)
if item.Sticky {
w.Write(forum_42)
} else {
if item.IsClosed {
w.Write(forum_42)
}
}
w.Write(forum_43)
w.Write([]byte(item.LastUser.Link))
}
}
w.Write(forum_44)
w.Write([]byte(item.LastUser.Avatar))
w.Write(forum_45)
w.Write([]byte(item.LastUser.Link))
w.Write(forum_45)
w.Write([]byte(item.LastUser.Avatar))
w.Write(forum_46)
w.Write([]byte(item.LastUser.Name))
w.Write(forum_47)
w.Write([]byte(item.RelativeLastReplyAt))
w.Write([]byte(item.LastUser.Link))
w.Write(forum_48)
w.Write([]byte(item.LastUser.Name))
w.Write(forum_49)
w.Write([]byte(item.RelativeLastReplyAt))
w.Write(forum_50)
}
} 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)
}
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_52)
}
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_53)
}
w.Write(forum_54)
}
w.Write(forum_55)
w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_forum_vars.Header)))
w.Write(footer_1)

View File

@ -352,127 +352,139 @@ var topic_alt_29 = []byte(`</textarea>
<div class="button_container">
`)
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_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_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_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_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_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_43 = []byte(`' class="action_button">Pin</a>`)
var topic_alt_44 = []byte(`
var topic_alt_43 = []byte(`' class="action_button pin_item" data-action="pin"></a>`)
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/`)
var topic_alt_45 = []byte(`?session=`)
var topic_alt_46 = []byte(`&type=topic" class="action_button report_item">Report</a>
var topic_alt_47 = []byte(`?session=`)
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(`
<div class="action_button_right">
var topic_alt_49 = []byte(`
<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_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_52 = []byte(`<a class="action_button like_count hide_on_micro">`)
var topic_alt_53 = []byte(`</a>`)
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 style="clear:both;"></div>
</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 `)
var topic_alt_56 = []byte(`action_item`)
var topic_alt_57 = []byte(`">
var topic_alt_61 = []byte(`action_item`)
var topic_alt_62 = []byte(`">
<div class="userinfo" aria-label="The information on the poster">
<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="`)
var topic_alt_59 = []byte(`" class="the_name" rel="author">`)
var topic_alt_60 = []byte(`</a>
var topic_alt_64 = []byte(`" class="the_name" rel="author">`)
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_62 = []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_64 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_65 = []byte(`
var topic_alt_66 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`)
var topic_alt_67 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_68 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `)
var topic_alt_69 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_70 = []byte(`
</div>
<div class="content_container" `)
var topic_alt_66 = []byte(`style="margin-left: 0px;"`)
var topic_alt_67 = []byte(`>
var topic_alt_71 = []byte(`style="margin-left: 0px;"`)
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;">`)
var topic_alt_69 = []byte(`</span>
var topic_alt_74 = []byte(`</span>
<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">`)
var topic_alt_72 = []byte(`</div>
var topic_alt_77 = []byte(`</div>
<div class="button_container">
`)
var topic_alt_73 = []byte(`<a href="/reply/like/submit/`)
var topic_alt_74 = []byte(`" class="action_button like_item add_like">+1</a>`)
var topic_alt_75 = []byte(`<a href="/reply/edit/submit/`)
var topic_alt_76 = []byte(`" class="action_button edit_item">Edit</a>`)
var topic_alt_77 = []byte(`<a href="/reply/delete/submit/`)
var topic_alt_78 = []byte(`" class="action_button delete_item">Delete</a>`)
var topic_alt_79 = []byte(`
var topic_alt_78 = []byte(`<a href="/reply/like/submit/`)
var topic_alt_79 = []byte(`" class="action_button like_item add_like" aria-label="Like this post" data-action="like"></a>`)
var topic_alt_80 = []byte(`<a href="/reply/edit/submit/`)
var topic_alt_81 = []byte(`" class="action_button edit_item" aria-label="Edit this post" data-action="edit"></a>`)
var topic_alt_82 = []byte(`<a href="/reply/delete/submit/`)
var topic_alt_83 = []byte(`" class="action_button delete_item" aria-label="Delete this post" data-action="delete"></a>`)
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/`)
var topic_alt_80 = []byte(`?session=`)
var topic_alt_81 = []byte(`&type=reply" class="action_button report_item">Report</a>
var topic_alt_87 = []byte(`?session=`)
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(`
<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>
`)
var topic_alt_90 = []byte(`
var topic_alt_100 = []byte(`
</div>
<div style="clear:both;"></div>
</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="userinfo" aria-label="The information on the poster">
<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="`)
var topic_alt_94 = []byte(`" class="the_name" rel="author">`)
var topic_alt_95 = []byte(`</a>
var topic_alt_104 = []byte(`" class="the_name" rel="author">`)
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_97 = []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_99 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_100 = []byte(`
var topic_alt_106 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`)
var topic_alt_107 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_108 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `)
var topic_alt_109 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_110 = []byte(`
</div>
<div class="rowblock topic_reply_form quick_create_form">
<form id="reply_form" enctype="multipart/form-data" action="/reply/create/" method="post"></form>
<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="formitem">
<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">
<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;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`)
var topic_alt_103 = []byte(`
var topic_alt_113 = []byte(`
</div>
</div>
</div>
</div>
`)
var topic_alt_104 = []byte(`
var topic_alt_114 = []byte(`
</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">
<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="`)
var topics_10 = []byte(`" height="64" />
var topics_10 = []byte(`" height="64" alt="Your Avatar" />
<div class="main_form">
<div class="topic_meta">
<div class="formrow topic_board_row real_first_child">
@ -814,50 +826,52 @@ var topics_24 = []byte(`">
<span class="selector"></span>
<a href="`)
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">
<a class="rowtopic" href="`)
var topics_27 = []byte(`"><span>`)
var topics_28 = []byte(`</span></a> `)
var topics_29 = []byte(`<a class="rowsmall parent_forum" href="`)
var topics_30 = []byte(`">`)
var topics_31 = []byte(`</a>`)
var topics_32 = []byte(`
var topics_28 = []byte(`"><span>`)
var topics_29 = []byte(`</span></a> `)
var topics_30 = []byte(`<a class="rowsmall parent_forum" href="`)
var topics_31 = []byte(`">`)
var topics_32 = []byte(`</a>`)
var topics_33 = []byte(`
<br /><a class="rowsmall starter" href="`)
var topics_33 = []byte(`">`)
var topics_34 = []byte(`</a>
var topics_34 = []byte(`">`)
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_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var topics_37 = []byte(`
var topics_36 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var topics_37 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var topics_38 = []byte(`
</span>
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`)
var topics_38 = []byte(`</span><br />
var topics_39 = []byte(`</span><br />
<span class="likeCount">`)
var topics_39 = []byte(`</span>
var topics_40 = []byte(`</span>
</span>
</div>
<div class="rowitem topic_right passive datarow `)
var topics_40 = []byte(`topic_sticky`)
var topics_41 = []byte(`topic_closed`)
var topics_42 = []byte(`">
var topics_41 = []byte(`topic_sticky`)
var topics_42 = []byte(`topic_closed`)
var topics_43 = []byte(`">
<a href="`)
var topics_43 = []byte(`"><img src="`)
var topics_44 = []byte(`" height="64" /></a>
var topics_44 = []byte(`"><img src="`)
var topics_45 = []byte(`" height="64" alt="`)
var topics_46 = []byte(`'s Avatar" /></a>
<span>
<a href="`)
var topics_45 = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_46 = []byte(`</a><br>
var topics_47 = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_48 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">`)
var topics_47 = []byte(`</span>
var topics_49 = []byte(`</span>
</span>
</div>
</div>`)
var topics_48 = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_49 = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_50 = []byte(`</div>`)
var topics_51 = []byte(`
var topics_50 = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_51 = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_52 = []byte(`</div>`)
var topics_53 = []byte(`
</div>
</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">
<form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<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="`)
var forum_21 = []byte(`" type="hidden">
<div class="main_form">
@ -962,47 +976,49 @@ var forum_29 = []byte(`">
<span class="selector"></span>
<a href="`)
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">
<a class="rowtopic" href="`)
var forum_32 = []byte(`"><span>`)
var forum_33 = []byte(`</span></a>
var forum_33 = []byte(`"><span>`)
var forum_34 = []byte(`</span></a>
<br /><a class="rowsmall starter" href="`)
var forum_34 = []byte(`">`)
var forum_35 = []byte(`</a>
var forum_35 = []byte(`">`)
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_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var forum_38 = []byte(`
var forum_37 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var forum_38 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var forum_39 = []byte(`
</span>
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`)
var forum_39 = []byte(`</span><br />
var forum_40 = []byte(`</span><br />
<span class="likeCount">`)
var forum_40 = []byte(`</span>
var forum_41 = []byte(`</span>
</span>
</div>
<div class="rowitem topic_right passive datarow `)
var forum_41 = []byte(`topic_sticky`)
var forum_42 = []byte(`topic_closed`)
var forum_43 = []byte(`">
var forum_42 = []byte(`topic_sticky`)
var forum_43 = []byte(`topic_closed`)
var forum_44 = []byte(`">
<a href="`)
var forum_44 = []byte(`"><img src="`)
var forum_45 = []byte(`" height="64" /></a>
var forum_45 = []byte(`"><img src="`)
var forum_46 = []byte(`" height="64" alt="`)
var forum_47 = []byte(`'s Avatar" /></a>
<span>
<a href="`)
var forum_46 = []byte(`" class="lastName" style="font-size: 14px;">`)
var forum_47 = []byte(`</a><br>
var forum_48 = []byte(`" class="lastName" style="font-size: 14px;">`)
var forum_49 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">`)
var forum_48 = []byte(`</span>
var forum_50 = []byte(`</span>
</span>
</div>
</div>`)
var forum_49 = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
var forum_50 = []byte(` <a href="/topics/create/`)
var forum_51 = []byte(`">Start one?</a>`)
var forum_52 = []byte(`</div>`)
var forum_53 = []byte(`
var forum_51 = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
var forum_52 = []byte(` <a href="/topics/create/`)
var forum_53 = []byte(`">Start one?</a>`)
var forum_54 = []byte(`</div>`)
var forum_55 = []byte(`
</div>
</main>
`)

View File

@ -3,9 +3,9 @@
// 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. */
package main
import "net/http"
import "./common"
import "strconv"
import "net/http"
// nolint
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_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 {
w.Write(topic_alt_52)
w.Write(topic_alt_44)
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_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 {
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)
if item.Tag != "" {
if item.ActionType != "" {
w.Write(topic_alt_61)
w.Write([]byte(item.Tag))
}
w.Write(topic_alt_62)
} else {
w.Write([]byte(item.Avatar))
w.Write(topic_alt_63)
w.Write([]byte(strconv.Itoa(item.Level)))
w.Write([]byte(item.UserLink))
w.Write(topic_alt_64)
}
w.Write([]byte(item.CreatedByName))
w.Write(topic_alt_65)
if item.ActionType != "" {
if item.Tag != "" {
w.Write(topic_alt_66)
}
w.Write([]byte(item.Tag))
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 {
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([]byte(item.ContentHtml))
}
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.Perms.LikeItem {
w.Write(topic_alt_73)
w.Write(topic_alt_78)
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 {
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([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_81)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteReply {
w.Write(topic_alt_82)
if item.LikeCount > 0 {
w.Write([]byte(strconv.Itoa(item.ID)))
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 {
w.Write(topic_alt_87)
w.Write(topic_alt_84)
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_89)
}
if item.LikeCount > 0 {
w.Write(topic_alt_90)
}
}
w.Write(topic_alt_91)
if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply {
if item.LikeCount > 0 {
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([]byte(tmpl_topic_alt_vars.CurrentUser.Link))
}
w.Write(topic_alt_94)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Name))
w.Write([]byte(item.RelativeCreatedAt))
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([]byte(tmpl_topic_alt_vars.CurrentUser.Tag))
w.Write([]byte(item.IPAddress))
w.Write(topic_alt_97)
} else {
w.Write([]byte(item.IPAddress))
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_100)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
}
}
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([]byte(tmpl_topic_alt_vars.CurrentUser.Avatar))
w.Write(topic_alt_103)
}
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Link))
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([]byte(common.BuildWidget("footer",tmpl_topic_alt_vars.Header)))
w.Write(footer_1)

View File

@ -133,60 +133,64 @@ w.Write([]byte(item.Creator.Link))
w.Write(topics_25)
w.Write([]byte(item.Creator.Avatar))
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(topics_34)
if item.IsClosed {
w.Write(topics_35)
w.Write(topics_27)
w.Write([]byte(item.Link))
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_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 {
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)
if item.Sticky {
w.Write(topics_41)
} else {
if item.IsClosed {
w.Write(topics_41)
}
}
w.Write(topics_42)
w.Write([]byte(item.LastUser.Link))
}
}
w.Write(topics_43)
w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_44)
w.Write([]byte(item.LastUser.Link))
w.Write(topics_44)
w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_45)
w.Write([]byte(item.LastUser.Name))
w.Write(topics_46)
w.Write([]byte(item.RelativeLastReplyAt))
w.Write([]byte(item.LastUser.Link))
w.Write(topics_47)
}
} else {
w.Write([]byte(item.LastUser.Name))
w.Write(topics_48)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write([]byte(item.RelativeLastReplyAt))
w.Write(topics_49)
}
} else {
w.Write(topics_50)
}
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_51)
}
w.Write(topics_52)
}
w.Write(topics_53)
w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_topics_vars.Header)))
w.Write(footer_1)

View File

@ -39,7 +39,7 @@
{{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">
<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">
<div class="main_form">
<div class="topic_meta">
@ -72,7 +72,7 @@
{{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}}">
<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">
<a class="rowtopic" href="{{.Link}}"><span>{{.Title}}</span></a>
<br /><a class="rowsmall starter" href="{{.Creator.Link}}">{{.Creator.Name}}</a>
@ -86,7 +86,7 @@
</span>
</div>
<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>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">{{.RelativeLastReplyAt}}</span>

View File

@ -1,5 +1,5 @@
{{template "header.html" . }}
<main>
<main id="ip_search_container">
<div class="rowblock rowhead">
<div class="rowitem">
<h1>IP Search</h1>
@ -8,14 +8,15 @@
<form action="/users/ips/" method="get" id="ip-search-form"></form>
<div class="rowblock ip_search_block">
<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" />
</div>
</div>
{{if .IP}}
<div class="rowblock bgavatars">
<div class="rowblock rowlist bgavatars">
{{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>
{{else}}<div class="rowitem">No users found.</div>{{end}}
</div>

View File

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

View File

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

View File

@ -26,7 +26,7 @@
</div>
<div id="panel_primary_themes" class="colstack_item panel_themes complex_rowlist">
{{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;">
<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>
@ -45,7 +45,7 @@
</div>
<div id="panel_variant_themes" class="colstack_item panel_themes">
{{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;">
<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>

View File

@ -9,7 +9,7 @@
<div id="panel_users" class="colstack_item rowlist bgavatars">
{{range .ItemList}}
<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 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}}

View File

@ -34,19 +34,21 @@
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea>
<div class="button_container">
{{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.EditTopic}}<a href="/topic/edit/{{.Topic.ID}}" class="action_button open_edit">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.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" 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" aria-label="Delete this post" data-action="delete"></a>{{end}}
{{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 .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}}
<a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="action_button report_item">Report</a>
{{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}}
{{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}}
<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}}
<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 style="clear:both;"></div>
@ -68,15 +70,17 @@
<div class="editable_block user_content" itemprop="text">{{.ContentHtml}}</div>
<div class="button_container">
{{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.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="action_button edit_item">Edit</a>{{end}}
{{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="action_button delete_item">Delete</a>{{end}}
<a href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="action_button report_item">Report</a>
{{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" 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" aria-label="Delete this post" data-action="delete"></a>{{end}}
{{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}}
<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}}
<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>
{{end}}

View File

@ -35,7 +35,7 @@
{{if .ForumList}}
<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>
<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="topic_meta">
<div class="formrow topic_board_row real_first_child">
@ -72,7 +72,7 @@
{{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}}">
<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">
<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>
@ -86,7 +86,7 @@
</span>
</div>
<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>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">{{.RelativeLastReplyAt}}</span>

View File

@ -12,6 +12,9 @@
--inverse-primary-text-color: white;
--light-text-color: hsl(0,0%,55%);
--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 {
/*background-color: hsl(0,0%,97%);*/
background-color: hsl(0,0%,98%);
background-color: var(--tinted-background-color);
}
#back {
padding: 8px;
@ -195,7 +197,8 @@ ul {
border-bottom: 2px solid var(--header-border-color);
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);
}
.rowblock {
@ -407,7 +410,7 @@ ul {
border-bottom: 1px solid var(--header-border-color);
outline: none;
}
.topic_name_row input {
.topic_name_row input, .ip_search_input {
width: 100%;
border: none;
border-bottom: 1px solid var(--header-border-color);
@ -424,8 +427,7 @@ ul {
display: flex;
margin-left: 2px;
}
.quick_button_row button, .quick_button_row label {
margin-left: 10px;
.quick_button_row button, .quick_button_row label, .ip_search_search {
padding-left: 10px;
padding-right: 10px;
padding-top: 6px;
@ -433,7 +435,6 @@ ul {
color: var(--inverse-primary-text-color);
font-size: 13px;
font-weight: bold;
margin-top: 8px;
border-width: initial;
border-style: none;
border-color: initial;
@ -442,6 +443,10 @@ ul {
background: hsl(209, 97%, 56%);
border-radius: 2px;
}
.quick_button_row button, .quick_button_row label {
margin-left: 10px;
margin-top: 8px;
}
.quick_button_row .add_file_button {
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, minmax(150px, 1fr));
grid-gap: 6px 12px;
border: none;
background: none !important;
}
.rowlist .rowitem {
display: flex;
}
.rowlist.bgavatars .rowitem {
.bgavatars .rowitem {
background-image: none !important;
}
.rowlist.bgavatars .rowitem {
flex-direction: column;
}
.bgavatars .bgsub {
border-radius: 30px;
height: 48px;
width: 48px;
margin-top: 8px;
margin-left: 4px;
}
.rowlist.bgavatars .bgsub {
height: 80px;
width: 80px;
@ -629,12 +645,10 @@ select, input, textarea, button {
content: var(--replies-lang-string);
color: var(--lightened-primary-text-color);
}
.topic_inner_right .topicCount:after {
content: var(--topics-lang-string);
color: var(--lightened-primary-text-color);
}
.topic_inner_right .likeCount:after {
content: var(--likes-lang-string);
color: var(--lightened-primary-text-color);
@ -645,7 +659,7 @@ select, input, textarea, button {
}
.topic_right {
flex: 1 1 0px; /*150px*/
flex: 1 1 0px;
border-left: none;
}
@ -656,7 +670,6 @@ select, input, textarea, button {
margin-top: 8px;
margin-left: 4px;
}
.topic_right img {
border-radius: 30px;
height: 42px;
@ -670,7 +683,6 @@ select, input, textarea, button {
margin-bottom: 14px;
width: 240px;
}
.topic_right > span {
margin-top: 12px;
margin-left: 8px;
@ -723,6 +735,7 @@ select, input, textarea, button {
margin-bottom: 16px;
}
.userinfo, .content_container {
background-color: var(--element-background-color);
border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-color);
}
@ -781,7 +794,9 @@ select, input, textarea, button {
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);
content: "";
margin-top: 1px;
@ -794,11 +809,52 @@ select, input, textarea, button {
margin-left: 10px;
margin-right: 5px;
}
.created_at {
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 {
display: flex;
}
@ -870,6 +926,9 @@ select, input, textarea, button {
#profile_right_lane .topic_reply_form {
width: auto;
}
#profile_comments .rowitem {
background-image: none !important;
}
.colstack_item .formrow {
display: flex;
@ -1023,7 +1082,7 @@ select, input, textarea, button {
/* TODO: Move these to panel.css */
#dash-version:before, #dash-cpu:before, #dash-ram:before {
display: inline-block;
background: hsl(0,0%,98%);
background: var(--tinted-background-color);
font: normal normal normal 14px/1 FontAwesome;
font-size: 20px;
padding-left: 17px;
@ -1041,12 +1100,88 @@ select, input, textarea, button {
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) {
.topic_inner_right {
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 {
font-size: 18px;
background-color: hsl(0,0%,97%);
@ -1081,9 +1216,19 @@ select, input, textarea, button {
.alert_aftercounter {
display: none;
}
#back {
padding-top: 0px;
}
.rowhead h1, .opthead h1, .colstack_head h1 {
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 {
display: flex;
@ -1120,6 +1265,183 @@ select, input, textarea, button {
margin-top: 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) {
.topic_list .topic_row {
@ -1130,4 +1452,18 @@ select, input, textarea, button {
.topic_list .topic_row:nth-child(odd) {
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");
});
//$.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({
btns: [['viewHTML'],['undo','redo'],['formatting'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']],
//hideButtonTexts: true
btns: btnlist,
});
$('.topic_reply_form #input_content').trumbowyg({
btns: [['viewHTML'],['undo','redo'],['formatting'],['strong','em','del'],['link'],['insertImage'],['unorderedList','orderedList'],['removeformat']],
btns: btnlist,
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";
}
.menu_alerts .alertList, .auto_hide {
.menu_alerts .alertList, .auto_hide, .hide_on_big, .show_on_mobile {
display: none;
}
.selectedAlert .alertList {
@ -425,7 +425,6 @@ textarea.large {
.formitem a {
font-size: 14px;
}
.rowmenu .rowitem, .rowlist .rowitem, .rowlist .formitem {
margin-top: 3px;
font-size: 13px;
@ -448,6 +447,9 @@ textarea.large {
font-size: 13px;
}
.bgsub {
display: none;
}
.rowlist.bgavatars .rowitem {
background-repeat: no-repeat;
background-size: 40px;

View File

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

View File

@ -216,11 +216,14 @@ li a {
}
}
.bgsub {
display: none;
}
.rowlist .rowitem {
padding-top: 10px;
padding-bottom: 10px;
}
.rowlist.bgavatars .rowitem {
.bgavatars .rowitem {
background-repeat: no-repeat;
background-size: 40px;
padding-left: 48px;
@ -616,7 +619,7 @@ button.username {
.mention {
font-weight: bold;
}
.show_on_edit, .auto_hide {
.show_on_edit, .auto_hide, .hide_on_big, .show_on_mobile, .button_menu {
display: none;
}
@ -884,6 +887,31 @@ button.username {
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 {
border: 1px solid var(--main-border-color);
margin-top: 12px;

View File

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

View File

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

View File

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

View File

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