The time it took for the route to be processed is shown in the footer in the Nox Theme for admins now
Don't waste queries on groups without any users for the topic list. Bypass the GzipResponseWriter in RunThemeTemplate, so we get indirected through fewer layers of Write calls. Added an experimental flag for disabling fsnotify. Refactored the topic benchmarks to make them a little more flexible. Refactored a couple of benchmarks to reduce the amount of boilerplate. Fixed a bug in the forum list for Nox where topic titles broke onto multiple lines. Refactored the template transpiler to reduce the amount of boilerplate for generating template functions.
This commit is contained in:
parent
8e997048e3
commit
17f85ceccf
|
@ -174,11 +174,8 @@ func PermmapToQuery(permmap map[string]*ForumPerms, fid int) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = FPStore.Reload(fid)
|
return FPStore.Reload(fid)
|
||||||
if err != nil {
|
//return TopicList.RebuildPermTree()
|
||||||
return err
|
|
||||||
}
|
|
||||||
return TopicList.RebuildPermTree()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: FPStore.Reload?
|
// TODO: FPStore.Reload?
|
||||||
|
@ -193,11 +190,8 @@ func ReplaceForumPermsForGroup(gid int, presetSet map[int]string, permSets map[i
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = tx.Commit()
|
return tx.Commit()
|
||||||
if err != nil {
|
//return TopicList.RebuildPermTree()
|
||||||
return err
|
|
||||||
}
|
|
||||||
return TopicList.RebuildPermTree()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReplaceForumPermsForGroupTx(tx *sql.Tx, gid int, presetSets map[int]string, permSets map[int]*ForumPerms) error {
|
func ReplaceForumPermsForGroupTx(tx *sql.Tx, gid int, presetSets map[int]string, permSets map[int]*ForumPerms) error {
|
||||||
|
|
|
@ -280,16 +280,8 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod
|
||||||
mgs.groupCount++
|
mgs.groupCount++
|
||||||
mgs.Unlock()
|
mgs.Unlock()
|
||||||
|
|
||||||
err = FPStore.ReloadAll()
|
return gid, FPStore.ReloadAll()
|
||||||
if err != nil {
|
//return gid, TopicList.RebuildPermTree()
|
||||||
return gid, err
|
|
||||||
}
|
|
||||||
err = TopicList.RebuildPermTree()
|
|
||||||
if err != nil {
|
|
||||||
return gid, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gid, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mgs *MemoryGroupStore) GetAll() (results []*Group, err error) {
|
func (mgs *MemoryGroupStore) GetAll() (results []*Group, err error) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ type Header struct {
|
||||||
Zone string
|
Zone string
|
||||||
Path string
|
Path string
|
||||||
MetaDesc string
|
MetaDesc string
|
||||||
|
StartedAt time.Time
|
||||||
Writer http.ResponseWriter
|
Writer http.ResponseWriter
|
||||||
ExtData ExtData
|
ExtData ExtData
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nolint
|
// nolint
|
||||||
|
@ -115,6 +116,10 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
|
||||||
Writer: w,
|
Writer: w,
|
||||||
}
|
}
|
||||||
// TODO: We should probably initialise header.ExtData
|
// TODO: We should probably initialise header.ExtData
|
||||||
|
// ? - Should we only show this in debug mode? It might be useful for detecting issues in production, if we show it there as-well
|
||||||
|
if user.IsAdmin {
|
||||||
|
header.StartedAt = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
header.AddSheet(theme.Name + "/panel.css")
|
header.AddSheet(theme.Name + "/panel.css")
|
||||||
if len(theme.Resources) > 0 {
|
if len(theme.Resources) > 0 {
|
||||||
|
@ -193,6 +198,11 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Head
|
||||||
if user.Loggedin && !user.Active {
|
if user.Loggedin && !user.Active {
|
||||||
header.AddNotice("account_inactive")
|
header.AddNotice("account_inactive")
|
||||||
}
|
}
|
||||||
|
// An optimisation so we don't populate StartedAt for users who shouldn't see the stat anyway
|
||||||
|
// ? - Should we only show this in debug mode? It might be useful for detecting issues in production, if we show it there as-well
|
||||||
|
if user.IsAdmin {
|
||||||
|
header.StartedAt = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
if len(theme.Resources) > 0 {
|
if len(theme.Resources) > 0 {
|
||||||
rlist := theme.Resources
|
rlist := theme.Resources
|
||||||
|
|
|
@ -96,6 +96,7 @@ type devConfig struct {
|
||||||
TemplateDebug bool
|
TemplateDebug bool
|
||||||
Profiling bool
|
Profiling bool
|
||||||
TestDB bool
|
TestDB bool
|
||||||
|
NoFsnotify bool // Super Experimental!
|
||||||
}
|
}
|
||||||
|
|
||||||
// configHolder is purely for having a big struct to unmarshal data into
|
// configHolder is purely for having a big struct to unmarshal data into
|
||||||
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Azareal/Gosora/common/alerts"
|
"github.com/Azareal/Gosora/common/alerts"
|
||||||
"github.com/Azareal/Gosora/common/templates"
|
|
||||||
"github.com/Azareal/Gosora/common/phrases"
|
"github.com/Azareal/Gosora/common/phrases"
|
||||||
|
"github.com/Azareal/Gosora/common/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Ctemplates []string
|
var Ctemplates []string
|
||||||
|
@ -162,15 +162,14 @@ func tmplInitHeaders(user User, user2 User, user3 User) (*Header, *Header, *Head
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var header2 = &Header{Site: Site}
|
buildHeader := func(user User) *Header {
|
||||||
*header2 = *header
|
var head = &Header{Site: Site}
|
||||||
header2.CurrentUser = user2
|
*head = *header
|
||||||
|
head.CurrentUser = user
|
||||||
|
return head
|
||||||
|
}
|
||||||
|
|
||||||
var header3 = &Header{Site: Site}
|
return header, buildHeader(user2), buildHeader(user3)
|
||||||
*header3 = *header
|
|
||||||
header3.CurrentUser = user3
|
|
||||||
|
|
||||||
return header, header2, header3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ? - Add template hooks?
|
// ? - Add template hooks?
|
||||||
|
@ -527,6 +526,10 @@ func InitTemplates() error {
|
||||||
return template.HTML(BuildWidget(dock.(string), headerInt.(*Header)))
|
return template.HTML(BuildWidget(dock.(string), headerInt.(*Header)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmap["elapsed"] = func(startedAtInt interface{}) interface{} {
|
||||||
|
return time.Since(startedAtInt.(time.Time)).String()
|
||||||
|
}
|
||||||
|
|
||||||
fmap["lang"] = func(phraseNameInt interface{}) interface{} {
|
fmap["lang"] = func(phraseNameInt interface{}) interface{} {
|
||||||
phraseName, ok := phraseNameInt.(string)
|
phraseName, ok := phraseNameInt.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
// TODO: Turn this file into a library
|
// TODO: Turn this file into a library
|
||||||
var textOverlapList = make(map[string]int)
|
var textOverlapList = make(map[string]int)
|
||||||
|
|
||||||
// TODO: Stop hard-coding this here
|
// TODO: Stop hard-coding this here
|
||||||
var langPkg = "github.com/Azareal/Gosora/common/phrases"
|
var langPkg = "github.com/Azareal/Gosora/common/phrases"
|
||||||
|
|
||||||
|
@ -86,6 +87,7 @@ func NewCTemplateSet() *CTemplateSet {
|
||||||
"multiply": true,
|
"multiply": true,
|
||||||
"divide": true,
|
"divide": true,
|
||||||
"dock": true,
|
"dock": true,
|
||||||
|
"elapsed": true,
|
||||||
"lang": true,
|
"lang": true,
|
||||||
"level": true,
|
"level": true,
|
||||||
"scope": true,
|
"scope": true,
|
||||||
|
@ -626,6 +628,24 @@ func (c *CTemplateSet) compareJoin(varholder string, holdreflect reflect.Value,
|
||||||
|
|
||||||
func (c *CTemplateSet) compileIdentSwitch(varholder string, holdreflect reflect.Value, templateName string, node *parse.CommandNode) (out string, val reflect.Value, literal bool) {
|
func (c *CTemplateSet) compileIdentSwitch(varholder string, holdreflect reflect.Value, templateName string, node *parse.CommandNode) (out string, val reflect.Value, literal bool) {
|
||||||
c.detail("in compileIdentSwitch")
|
c.detail("in compileIdentSwitch")
|
||||||
|
var litString = func(inner string) {
|
||||||
|
out = "w.Write([]byte(" + inner + "))\n"
|
||||||
|
literal = true
|
||||||
|
}
|
||||||
|
var compOpMappings = map[string]string{
|
||||||
|
"le": "<=",
|
||||||
|
"lt": "<",
|
||||||
|
"gt": ">",
|
||||||
|
"ge": ">=",
|
||||||
|
"eq": "==",
|
||||||
|
"ne": "!=",
|
||||||
|
}
|
||||||
|
var mathOpMappings = map[string]string{
|
||||||
|
"add": "+",
|
||||||
|
"subtract": "-",
|
||||||
|
"divide": "/",
|
||||||
|
"multiply": "*",
|
||||||
|
}
|
||||||
ArgLoop:
|
ArgLoop:
|
||||||
for pos := 0; pos < len(node.Args); pos++ {
|
for pos := 0; pos < len(node.Args); pos++ {
|
||||||
id := node.Args[pos]
|
id := node.Args[pos]
|
||||||
|
@ -638,43 +658,21 @@ ArgLoop:
|
||||||
var rout string
|
var rout string
|
||||||
pos, rout = c.compareJoin(varholder, holdreflect, templateName, pos, node, c.funcMap[id.String()].(string)) // TODO: Test this
|
pos, rout = c.compareJoin(varholder, holdreflect, templateName, pos, node, c.funcMap[id.String()].(string)) // TODO: Test this
|
||||||
out += rout
|
out += rout
|
||||||
case "le": // TODO: Can we condense these comparison cases down into one?
|
case "le", "lt", "gt", "ge", "eq", "ne":
|
||||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "<=")
|
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, compOpMappings[id.String()])
|
||||||
break ArgLoop
|
break ArgLoop
|
||||||
case "lt":
|
case "add", "subtract", "divide", "multiply":
|
||||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "<")
|
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, mathOpMappings[id.String()])
|
||||||
break ArgLoop
|
|
||||||
case "gt":
|
|
||||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, ">")
|
|
||||||
break ArgLoop
|
|
||||||
case "ge":
|
|
||||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, ">=")
|
|
||||||
break ArgLoop
|
|
||||||
case "eq":
|
|
||||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "==")
|
|
||||||
break ArgLoop
|
|
||||||
case "ne":
|
|
||||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "!=")
|
|
||||||
break ArgLoop
|
|
||||||
case "add":
|
|
||||||
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "+")
|
|
||||||
out += rout
|
out += rout
|
||||||
val = rval
|
val = rval
|
||||||
break ArgLoop
|
break ArgLoop
|
||||||
case "subtract":
|
case "elapsed":
|
||||||
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "-")
|
leftOperand := node.Args[pos+1].String()
|
||||||
out += rout
|
leftParam, _ := c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect)
|
||||||
val = rval
|
// TODO: Refactor this
|
||||||
break ArgLoop
|
// TODO: Validate that this is actually a time.Time
|
||||||
case "divide":
|
litString("time.Since(" + leftParam + ").String()")
|
||||||
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "/")
|
c.importMap["time"] = "time"
|
||||||
out += rout
|
|
||||||
val = rval
|
|
||||||
break ArgLoop
|
|
||||||
case "multiply":
|
|
||||||
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "*")
|
|
||||||
out += rout
|
|
||||||
val = rval
|
|
||||||
break ArgLoop
|
break ArgLoop
|
||||||
case "dock":
|
case "dock":
|
||||||
var leftParam, rightParam string
|
var leftParam, rightParam string
|
||||||
|
@ -701,40 +699,33 @@ ArgLoop:
|
||||||
val = val3
|
val = val3
|
||||||
|
|
||||||
// TODO: Refactor this
|
// TODO: Refactor this
|
||||||
out = "w.Write([]byte(common.BuildWidget(" + leftParam + "," + rightParam + ")))\n"
|
litString("common.BuildWidget(" + leftParam + "," + rightParam + ")")
|
||||||
literal = true
|
|
||||||
break ArgLoop
|
break ArgLoop
|
||||||
case "lang":
|
case "lang":
|
||||||
var leftParam string
|
|
||||||
// TODO: Implement string literals properly
|
// TODO: Implement string literals properly
|
||||||
leftOperand := node.Args[pos+1].String()
|
leftOperand := node.Args[pos+1].String()
|
||||||
if len(leftOperand) == 0 {
|
if len(leftOperand) == 0 {
|
||||||
panic("The left operand for the language string cannot be left blank")
|
panic("The left operand for the language string cannot be left blank")
|
||||||
}
|
}
|
||||||
|
if leftOperand[0] != '"' {
|
||||||
if leftOperand[0] == '"' {
|
|
||||||
// ! Slightly crude but it does the job
|
|
||||||
leftParam = strings.Replace(leftOperand, "\"", "", -1)
|
|
||||||
} else {
|
|
||||||
panic("Phrase names cannot be dynamic")
|
panic("Phrase names cannot be dynamic")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ! Slightly crude but it does the job
|
||||||
|
leftParam := strings.Replace(leftOperand, "\"", "", -1)
|
||||||
c.langIndexToName = append(c.langIndexToName, leftParam)
|
c.langIndexToName = append(c.langIndexToName, leftParam)
|
||||||
out = "w.Write(plist[" + strconv.Itoa(len(c.langIndexToName)-1) + "])\n"
|
litString("plist[" + strconv.Itoa(len(c.langIndexToName)-1) + "]")
|
||||||
literal = true
|
|
||||||
break ArgLoop
|
break ArgLoop
|
||||||
case "level":
|
case "level":
|
||||||
var leftParam string
|
|
||||||
// TODO: Implement level literals
|
// TODO: Implement level literals
|
||||||
leftOperand := node.Args[pos+1].String()
|
leftOperand := node.Args[pos+1].String()
|
||||||
if len(leftOperand) == 0 {
|
if len(leftOperand) == 0 {
|
||||||
panic("The leftoperand for function level cannot be left blank")
|
panic("The leftoperand for function level cannot be left blank")
|
||||||
}
|
}
|
||||||
|
|
||||||
leftParam, _ = c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect)
|
leftParam, _ := c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect)
|
||||||
// TODO: Refactor this
|
// TODO: Refactor this
|
||||||
out = "w.Write([]byte(phrases.GetLevelPhrase(" + leftParam + ")))\n"
|
litString("phrases.GetLevelPhrase(" + leftParam + ")")
|
||||||
literal = true
|
|
||||||
c.importMap[langPkg] = langPkg
|
c.importMap[langPkg] = langPkg
|
||||||
break ArgLoop
|
break ArgLoop
|
||||||
case "scope":
|
case "scope":
|
||||||
|
|
|
@ -289,10 +289,25 @@ func ResetTemplateOverrides() {
|
||||||
log.Print("All of the template overrides have been reset")
|
log.Print("All of the template overrides have been reset")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GzipResponseWriter struct {
|
||||||
|
io.Writer
|
||||||
|
http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w GzipResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
return w.Writer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
// NEW method of doing theme templates to allow one user to have a different theme to another. Under construction.
|
// NEW method of doing theme templates to allow one user to have a different theme to another. Under construction.
|
||||||
// TODO: Generate the type switch instead of writing it by hand
|
// TODO: Generate the type switch instead of writing it by hand
|
||||||
// TODO: Cut the number of types in half
|
// TODO: Cut the number of types in half
|
||||||
func RunThemeTemplate(theme string, template string, pi interface{}, w io.Writer) error {
|
func RunThemeTemplate(theme string, template string, pi interface{}, w io.Writer) error {
|
||||||
|
// Unpack this to avoid an indirect call
|
||||||
|
gzw, ok := w.(GzipResponseWriter)
|
||||||
|
if ok {
|
||||||
|
w = gzw.Writer
|
||||||
|
}
|
||||||
|
|
||||||
var getTmpl = GetThemeTemplate(theme, template)
|
var getTmpl = GetThemeTemplate(theme, template)
|
||||||
switch tmplO := getTmpl.(type) {
|
switch tmplO := getTmpl.(type) {
|
||||||
case *func(CustomPagePage, io.Writer) error:
|
case *func(CustomPagePage, io.Writer) error:
|
||||||
|
|
|
@ -3,7 +3,6 @@ package common
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/Azareal/Gosora/query_gen"
|
"github.com/Azareal/Gosora/query_gen"
|
||||||
)
|
)
|
||||||
|
@ -20,8 +19,6 @@ type TopicListInt interface {
|
||||||
GetListByCanSee(canSee []int, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
|
GetListByCanSee(canSee []int, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
|
||||||
GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
|
GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
|
||||||
GetList(page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
|
GetList(page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
|
||||||
|
|
||||||
RebuildPermTree() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultTopicList struct {
|
type DefaultTopicList struct {
|
||||||
|
@ -31,7 +28,7 @@ type DefaultTopicList struct {
|
||||||
oddLock sync.RWMutex
|
oddLock sync.RWMutex
|
||||||
evenLock sync.RWMutex
|
evenLock sync.RWMutex
|
||||||
|
|
||||||
permTree atomic.Value // [string(canSee)]canSee
|
//permTree atomic.Value // [string(canSee)]canSee
|
||||||
//permTree map[string][]int // [string(canSee)]canSee
|
//permTree map[string][]int // [string(canSee)]canSee
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,11 +41,7 @@ func NewDefaultTopicList() (*DefaultTopicList, error) {
|
||||||
evenGroups: make(map[int]*TopicListHolder),
|
evenGroups: make(map[int]*TopicListHolder),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := tList.RebuildPermTree()
|
err := tList.Tick()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tList.Tick()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -70,19 +63,13 @@ func (tList *DefaultTopicList) Tick() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var canSeeHolders = make(map[string]*TopicListHolder)
|
|
||||||
for name, canSee := range tList.permTree.Load().(map[string][]int) {
|
|
||||||
topicList, forumList, paginator, err := tList.GetListByCanSee(canSee, 1, "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
canSeeHolders[name] = &TopicListHolder{topicList, forumList, paginator}
|
|
||||||
}
|
|
||||||
|
|
||||||
allGroups, err := Groups.GetAll()
|
allGroups, err := Groups.GetAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gidToCanSee = make(map[int]string)
|
||||||
|
var permTree = make(map[string][]int) // [string(canSee)]canSee
|
||||||
for _, group := range allGroups {
|
for _, group := range allGroups {
|
||||||
// ? - Move the user count check to instance initialisation? Might require more book-keeping, particularly when a user moves into a zero user group
|
// ? - Move the user count check to instance initialisation? Might require more book-keeping, particularly when a user moves into a zero user group
|
||||||
if group.UserCount == 0 && group.ID != GuestUser.Group {
|
if group.UserCount == 0 && group.ID != GuestUser.Group {
|
||||||
|
@ -92,7 +79,23 @@ func (tList *DefaultTopicList) Tick() error {
|
||||||
for i, item := range group.CanSee {
|
for i, item := range group.CanSee {
|
||||||
canSee[i] = byte(item)
|
canSee[i] = byte(item)
|
||||||
}
|
}
|
||||||
addList(group.ID, canSeeHolders[string(canSee)])
|
var canSeeInt = make([]int, len(canSee))
|
||||||
|
copy(canSeeInt, group.CanSee)
|
||||||
|
sCanSee := string(canSee)
|
||||||
|
permTree[sCanSee] = canSeeInt
|
||||||
|
gidToCanSee[group.ID] = sCanSee
|
||||||
|
}
|
||||||
|
|
||||||
|
var canSeeHolders = make(map[string]*TopicListHolder)
|
||||||
|
for name, canSee := range permTree {
|
||||||
|
topicList, forumList, paginator, err := tList.GetListByCanSee(canSee, 1, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
canSeeHolders[name] = &TopicListHolder{topicList, forumList, paginator}
|
||||||
|
}
|
||||||
|
for gid, canSee := range gidToCanSee {
|
||||||
|
addList(gid, canSeeHolders[canSee])
|
||||||
}
|
}
|
||||||
|
|
||||||
tList.oddLock.Lock()
|
tList.oddLock.Lock()
|
||||||
|
@ -106,28 +109,6 @@ func (tList *DefaultTopicList) Tick() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tList *DefaultTopicList) RebuildPermTree() error {
|
|
||||||
// TODO: Do something more efficient than this
|
|
||||||
allGroups, err := Groups.GetAll()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var permTree = make(map[string][]int) // [string(canSee)]canSee
|
|
||||||
for _, group := range allGroups {
|
|
||||||
var canSee = make([]byte, len(group.CanSee))
|
|
||||||
for i, item := range group.CanSee {
|
|
||||||
canSee[i] = byte(item)
|
|
||||||
}
|
|
||||||
var canSeeInt = make([]int, len(canSee))
|
|
||||||
copy(canSeeInt, group.CanSee)
|
|
||||||
permTree[string(canSee)] = canSeeInt
|
|
||||||
}
|
|
||||||
tList.permTree.Store(permTree)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tList *DefaultTopicList) GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) {
|
func (tList *DefaultTopicList) GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) {
|
||||||
if page == 0 {
|
if page == 0 {
|
||||||
page = 1
|
page = 1
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
@ -536,15 +535,6 @@ func init() {
|
||||||
counters.SetReverseOSMapEnum(reverseOSMapEnum)
|
counters.SetReverseOSMapEnum(reverseOSMapEnum)
|
||||||
}
|
}
|
||||||
|
|
||||||
type gzipResponseWriter struct {
|
|
||||||
io.Writer
|
|
||||||
http.ResponseWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
|
||||||
return w.Writer.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type WriterIntercept struct {
|
type WriterIntercept struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
}
|
}
|
||||||
|
@ -889,7 +879,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
gz.Close()
|
gz.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
w = gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||||
}
|
}
|
||||||
|
|
||||||
ferr := r.routeSwitch(w, req, user, prefix, extraData)
|
ferr := r.routeSwitch(w, req, user, prefix, extraData)
|
||||||
|
@ -944,7 +934,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gzw, ok := w.(gzipResponseWriter)
|
gzw, ok := w.(common.GzipResponseWriter)
|
||||||
if ok {
|
if ok {
|
||||||
w = gzw.ResponseWriter
|
w = gzw.ResponseWriter
|
||||||
w.Header().Del("Content-Type")
|
w.Header().Del("Content-Type")
|
||||||
|
@ -1406,7 +1396,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
gzw, ok := w.(gzipResponseWriter)
|
gzw, ok := w.(common.GzipResponseWriter)
|
||||||
if ok {
|
if ok {
|
||||||
w = gzw.ResponseWriter
|
w = gzw.ResponseWriter
|
||||||
w.Header().Del("Content-Type")
|
w.Header().Del("Content-Type")
|
||||||
|
@ -1982,7 +1972,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
if extraData == "" {
|
if extraData == "" {
|
||||||
return common.NotFound(w,req,nil)
|
return common.NotFound(w,req,nil)
|
||||||
}
|
}
|
||||||
gzw, ok := w.(gzipResponseWriter)
|
gzw, ok := w.(common.GzipResponseWriter)
|
||||||
if ok {
|
if ok {
|
||||||
w = gzw.ResponseWriter
|
w = gzw.ResponseWriter
|
||||||
w.Header().Del("Content-Type")
|
w.Header().Del("Content-Type")
|
||||||
|
|
|
@ -108,6 +108,8 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const benchTid = "1"
|
||||||
|
|
||||||
// TODO: Swap out LocalError for a panic for this?
|
// TODO: Swap out LocalError for a panic for this?
|
||||||
func BenchmarkTopicAdminRouteParallel(b *testing.B) {
|
func BenchmarkTopicAdminRouteParallel(b *testing.B) {
|
||||||
binit(b)
|
binit(b)
|
||||||
|
@ -128,7 +130,7 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
reqAdmin := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
|
reqAdmin := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil))
|
||||||
reqAdmin.AddCookie(&adminUIDCookie)
|
reqAdmin.AddCookie(&adminUIDCookie)
|
||||||
reqAdmin.AddCookie(&adminSessionCookie)
|
reqAdmin.AddCookie(&adminSessionCookie)
|
||||||
|
|
||||||
|
@ -176,7 +178,7 @@ func BenchmarkTopicAdminRouteParallelWithRouter(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
reqAdmin := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
|
reqAdmin := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil))
|
||||||
reqAdmin.AddCookie(&uidCookie)
|
reqAdmin.AddCookie(&uidCookie)
|
||||||
reqAdmin.AddCookie(&sessionCookie)
|
reqAdmin.AddCookie(&sessionCookie)
|
||||||
reqAdmin.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
reqAdmin.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
||||||
|
@ -215,7 +217,7 @@ func BenchmarkTopicGuestRouteParallel(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
|
req := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil))
|
||||||
user := common.GuestUser
|
user := common.GuestUser
|
||||||
|
|
||||||
head, err := common.UserCheck(w, req, &user)
|
head, err := common.UserCheck(w, req, &user)
|
||||||
|
@ -242,7 +244,7 @@ func BenchmarkTopicGuestRouteParallelDebugMode(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
|
req := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil))
|
||||||
user := common.GuestUser
|
user := common.GuestUser
|
||||||
|
|
||||||
head, err := common.UserCheck(w, req, &user)
|
head, err := common.UserCheck(w, req, &user)
|
||||||
|
@ -260,65 +262,6 @@ func BenchmarkTopicGuestRouteParallelDebugMode(b *testing.B) {
|
||||||
cfg.Restore()
|
cfg.Restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTopicGuestRouteParallelWithRouter(b *testing.B) {
|
|
||||||
binit(b)
|
|
||||||
router, err := NewGenRouter(http.FileServer(http.Dir("./uploads")))
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
cfg := NewStashConfig()
|
|
||||||
common.Dev.DebugMode = false
|
|
||||||
common.Dev.SuperDebug = false
|
|
||||||
|
|
||||||
/*f, err := os.Create("BenchmarkTopicGuestRouteParallelWithRouter.prof")
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
pprof.StartCPUProfile(f)*/
|
|
||||||
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
|
||||||
for pb.Next() {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
req := httptest.NewRequest("GET", "/topic/hm.1", bytes.NewReader(nil))
|
|
||||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
|
||||||
req.Header.Set("Host", "localhost")
|
|
||||||
req.Host = "localhost"
|
|
||||||
//w.Body.Reset()
|
|
||||||
router.ServeHTTP(w, req)
|
|
||||||
if w.Code != 200 {
|
|
||||||
b.Log(w.Body)
|
|
||||||
b.Fatal("HTTP Error!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
//defer pprof.StopCPUProfile()
|
|
||||||
cfg.Restore()
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) {
|
|
||||||
binit(b)
|
|
||||||
router, err := NewGenRouter(http.FileServer(http.Dir("./uploads")))
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
cfg := NewStashConfig()
|
|
||||||
common.Dev.DebugMode = false
|
|
||||||
common.Dev.SuperDebug = false
|
|
||||||
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
|
||||||
for pb.Next() {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
req := httptest.NewRequest("GET", "/garble/haa", bytes.NewReader(nil))
|
|
||||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
|
||||||
req.Header.Set("Host", "localhost")
|
|
||||||
req.Host = "localhost"
|
|
||||||
router.ServeHTTP(w, req)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
cfg.Restore()
|
|
||||||
}
|
|
||||||
|
|
||||||
func obRoute(b *testing.B, path string) {
|
func obRoute(b *testing.B, path string) {
|
||||||
binit(b)
|
binit(b)
|
||||||
cfg := NewStashConfig()
|
cfg := NewStashConfig()
|
||||||
|
@ -340,6 +283,14 @@ func BenchmarkForumGuestRouteParallelWithRouter(b *testing.B) {
|
||||||
obRoute(b, "/forum/general.2")
|
obRoute(b, "/forum/general.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkTopicGuestRouteParallelWithRouter(b *testing.B) {
|
||||||
|
obRoute(b, "/topic/hm."+benchTid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) {
|
||||||
|
obRoute(b, "/garble/haa")
|
||||||
|
}
|
||||||
|
|
||||||
func binit(b *testing.B) {
|
func binit(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
err := gloinit()
|
err := gloinit()
|
||||||
|
|
16
main.go
16
main.go
|
@ -16,6 +16,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -24,8 +25,8 @@ import (
|
||||||
"github.com/Azareal/Gosora/common"
|
"github.com/Azareal/Gosora/common"
|
||||||
"github.com/Azareal/Gosora/common/counters"
|
"github.com/Azareal/Gosora/common/counters"
|
||||||
"github.com/Azareal/Gosora/common/phrases"
|
"github.com/Azareal/Gosora/common/phrases"
|
||||||
"github.com/Azareal/Gosora/routes"
|
|
||||||
"github.com/Azareal/Gosora/query_gen"
|
"github.com/Azareal/Gosora/query_gen"
|
||||||
|
"github.com/Azareal/Gosora/routes"
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -289,6 +290,7 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !common.Dev.NoFsnotify {
|
||||||
log.Print("Initialising the file watcher")
|
log.Print("Initialising the file watcher")
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -356,6 +358,7 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Print("Initialising the task system")
|
log.Print("Initialising the task system")
|
||||||
|
|
||||||
|
@ -437,6 +440,17 @@ func main() {
|
||||||
args := <-common.StopServerChan
|
args := <-common.StopServerChan
|
||||||
if false {
|
if false {
|
||||||
pprof.StopCPUProfile()
|
pprof.StopCPUProfile()
|
||||||
|
f, err := os.Create("./logs/mem.prof")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
runtime.GC()
|
||||||
|
err = pprof.WriteHeapProfile(f)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Why did the server stop?
|
// Why did the server stop?
|
||||||
log.Fatal(args...)
|
log.Fatal(args...)
|
||||||
|
|
|
@ -239,7 +239,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
@ -318,15 +317,6 @@ func init() {
|
||||||
counters.SetReverseOSMapEnum(reverseOSMapEnum)
|
counters.SetReverseOSMapEnum(reverseOSMapEnum)
|
||||||
}
|
}
|
||||||
|
|
||||||
type gzipResponseWriter struct {
|
|
||||||
io.Writer
|
|
||||||
http.ResponseWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
|
||||||
return w.Writer.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type WriterIntercept struct {
|
type WriterIntercept struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
}
|
}
|
||||||
|
@ -671,7 +661,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
gz.Close()
|
gz.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
w = gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||||
}
|
}
|
||||||
|
|
||||||
ferr := r.routeSwitch(w, req, user, prefix, extraData)
|
ferr := r.routeSwitch(w, req, user, prefix, extraData)
|
||||||
|
@ -691,7 +681,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||||
if extraData == "" {
|
if extraData == "" {
|
||||||
return common.NotFound(w,req,nil)
|
return common.NotFound(w,req,nil)
|
||||||
}
|
}
|
||||||
gzw, ok := w.(gzipResponseWriter)
|
gzw, ok := w.(common.GzipResponseWriter)
|
||||||
if ok {
|
if ok {
|
||||||
w = gzw.ResponseWriter
|
w = gzw.ResponseWriter
|
||||||
w.Header().Del("Content-Type")
|
w.Header().Del("Content-Type")
|
||||||
|
|
|
@ -60,7 +60,7 @@ func (route *RouteImpl) hasBeforeItem(item string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (route *RouteImpl) NoGzip() *RouteImpl {
|
func (route *RouteImpl) NoGzip() *RouteImpl {
|
||||||
return route.LitBeforeMultiline(`gzw, ok := w.(gzipResponseWriter)
|
return route.LitBeforeMultiline(`gzw, ok := w.(common.GzipResponseWriter)
|
||||||
if ok {
|
if ok {
|
||||||
w = gzw.ResponseWriter
|
w = gzw.ResponseWriter
|
||||||
w.Header().Del("Content-Type")
|
w.Header().Del("Content-Type")
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<div id="poweredBy">
|
<div id="poweredBy">
|
||||||
<a id="poweredByName" href="https://github.com/Azareal/Gosora">{{lang "footer_powered_by"}}</a><span id="poweredByDash"> - </span><span id="poweredByMaker">{{lang "footer_made_with_love"}}</span>
|
<a id="poweredByName" href="https://github.com/Azareal/Gosora">{{lang "footer_powered_by"}}</a><span id="poweredByDash"> - </span><span id="poweredByMaker">{{lang "footer_made_with_love"}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{{if .CurrentUser.IsAdmin}}<div class="elapsed">{{elapsed .Header.StartedAt}}</div>{{end}}
|
||||||
<form action="/theme/" method="post">
|
<form action="/theme/" method="post">
|
||||||
<div id="themeSelector" style="float: right;">
|
<div id="themeSelector" style="float: right;">
|
||||||
<select id="themeSelectorSelect" name="themeSelector" aria-label="{{lang "footer_theme_selector_aria"}}">
|
<select id="themeSelectorSelect" name="themeSelector" aria-label="{{lang "footer_theme_selector_aria"}}">
|
||||||
|
|
|
@ -1413,6 +1413,9 @@ textarea {
|
||||||
background-color: var(--element-background-color);
|
background-color: var(--element-background-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
.elapsed {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
#poweredByHolder {
|
#poweredByHolder {
|
||||||
border-bottom: 2px solid var(--element-border-color);
|
border-bottom: 2px solid var(--element-border-color);
|
||||||
}
|
}
|
||||||
|
|
|
@ -588,6 +588,9 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) {
|
||||||
.forum_list .forum_right span {
|
.forum_list .forum_right span {
|
||||||
line-height: 19px;
|
line-height: 19px;
|
||||||
}
|
}
|
||||||
|
.forum_list .forum_right span a {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
.extra_little_row_avatar {
|
.extra_little_row_avatar {
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
|
@ -837,10 +840,21 @@ input[type=checkbox]:checked + label .sel {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer .widget {
|
.footer .widget, .elapsed {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-bottom: 1px solid #555555;
|
border-bottom: 1px solid #555555;
|
||||||
}
|
}
|
||||||
|
.elapsed {
|
||||||
|
padding: 6px;
|
||||||
|
background: rgb(82,82,82);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 13.5px;
|
||||||
|
color: rgb(200,200,200);
|
||||||
|
margin-top: 1px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
padding-top: 3px;
|
||||||
|
}
|
||||||
#poweredByHolder {
|
#poweredByHolder {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
|
|
|
@ -573,6 +573,9 @@ input, select, textarea {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
.elapsed {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
#poweredByHolder {
|
#poweredByHolder {
|
||||||
background-color: var(--main-block-color);
|
background-color: var(--main-block-color);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
|
@ -856,6 +856,9 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
|
||||||
top: -2px;
|
top: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.elapsed {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
#poweredByHolder {
|
#poweredByHolder {
|
||||||
border: 1px solid hsl(0, 0%, 80%);
|
border: 1px solid hsl(0, 0%, 80%);
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
|
Loading…
Reference in New Issue