/topics/ (the default index) is now over 25 times faster than before.

Change the length of the ticks for referrer tracking.
Fixed a bug in Shadow and Tempra Simple where the polls would appear when they shouldn't.
Added styling to Shadow for Control Panel Sub-menus.
Fixed the ridiculously wide attachment images on Cosora.
The analytics pages should no longer be treated as the dashboard style-wise.
The installer now works again.
Tests now run again, as do the benchmarks.
Refactored the topic list logic, and moved the route portion into the routes package.
Fixed an inverted comparison in the referrer logic.
Added friendly text for Mac operating systems to the english language pack.
Removed two obsolete prepared statements.
Fixed three race conditions in the database adapters.
Potentially sped up the topic page by 20%
Added several new benchmarks and refactored the others.
Removed a redundant prepared statement in user.go

Added reviseID as a primary key to the revisions table.
The user_count column was added to the users_groups table, but it's unknown if it will stay.
This commit is contained in:
Azareal 2018-02-10 15:07:21 +00:00
parent 1ac47bfdfd
commit fa065bc584
49 changed files with 1038 additions and 512 deletions

View File

@ -24,8 +24,6 @@ Always use strict mode.
Don't worry about ES5, we're targetting modern browsers. If we decide to backport code to older browsers, then we'll transpile the files. Don't worry about ES5, we're targetting modern browsers. If we decide to backport code to older browsers, then we'll transpile the files.
Don't use await until Edge gets around to implementing it.
To keep consistency with Go code, variables must be camelCase. To keep consistency with Go code, variables must be camelCase.
# JSON # JSON

View File

@ -1,9 +1,11 @@
package common package common
import "log" import (
import "sync" "log"
import "net/http" "net/http"
import "runtime/debug" "runtime/debug"
"sync"
)
// TODO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page? // TODO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page?
// ? - Should we pass HeaderVars / HeaderLite rather than forcing the errors to pull the global HeaderVars instance? // ? - Should we pass HeaderVars / HeaderLite rather than forcing the errors to pull the global HeaderVars instance?
@ -120,6 +122,7 @@ func SilentInternalErrorXML(err error, w http.ResponseWriter, r *http.Request) R
} }
func PreError(errmsg string, w http.ResponseWriter, r *http.Request) RouteError { func PreError(errmsg string, w http.ResponseWriter, r *http.Request) RouteError {
//LogError(errors.New(errmsg))
w.WriteHeader(500) w.WriteHeader(500)
pi := Page{"Error", GuestUser, DefaultHeaderVar(), tList, errmsg} pi := Page{"Error", GuestUser, DefaultHeaderVar(), tList, errmsg}
handleErrorTemplate(w, r, pi) handleErrorTemplate(w, r, pi)
@ -141,6 +144,7 @@ func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs boo
// LocalError is an error shown to the end-user when something goes wrong and it's not the software's fault // LocalError is an error shown to the end-user when something goes wrong and it's not the software's fault
func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) RouteError { func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) RouteError {
//LogError(errors.New(errmsg))
w.WriteHeader(500) w.WriteHeader(500)
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, errmsg} pi := Page{"Local Error", user, DefaultHeaderVar(), tList, errmsg}
handleErrorTemplate(w, r, pi) handleErrorTemplate(w, r, pi)
@ -277,6 +281,7 @@ func CustomErrorJS(errmsg string, errcode int, w http.ResponseWriter, r *http.Re
} }
func handleErrorTemplate(w http.ResponseWriter, r *http.Request, pi Page) { func handleErrorTemplate(w http.ResponseWriter, r *http.Request, pi Page) {
//LogError(errors.New("error happened"))
// TODO: What to do about this hook? // TODO: What to do about this hook?
if PreRenderHooks["pre_render_error"] != nil { if PreRenderHooks["pre_render_error"] != nil {
if RunPreRenderHook("pre_render_error", w, r, &pi.CurrentUser, &pi) { if RunPreRenderHook("pre_render_error", w, r, &pi.CurrentUser, &pi) {

View File

@ -28,6 +28,7 @@ type Group struct {
PluginPermsText []byte PluginPermsText []byte
Forums []*ForumPerms Forums []*ForumPerms
CanSee []int // The IDs of the forums this group can see CanSee []int // The IDs of the forums this group can see
UserCount int // ! Might be temporary as I might want to lean on the database instead for this
} }
type GroupStmts struct { type GroupStmts struct {

View File

@ -39,6 +39,7 @@ type MemoryGroupStore struct {
getAll *sql.Stmt getAll *sql.Stmt
get *sql.Stmt get *sql.Stmt
count *sql.Stmt count *sql.Stmt
userCount *sql.Stmt
sync.RWMutex sync.RWMutex
} }
@ -51,6 +52,7 @@ func NewMemoryGroupStore() (*MemoryGroupStore, error) {
getAll: acc.Select("users_groups").Columns("gid, name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag").Prepare(), getAll: acc.Select("users_groups").Columns("gid, name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag").Prepare(),
get: acc.Select("users_groups").Columns("name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag").Where("gid = ?").Prepare(), get: acc.Select("users_groups").Columns("name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag").Where("gid = ?").Prepare(),
count: acc.Count("users_groups").Prepare(), count: acc.Count("users_groups").Prepare(),
userCount: acc.Count("users").Where("group = ?").Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
@ -177,6 +179,11 @@ func (mgs *MemoryGroupStore) initGroup(group *Group) error {
if group.IsAdmin || group.IsMod { if group.IsAdmin || group.IsMod {
group.IsBanned = false group.IsBanned = false
} }
err = mgs.userCount.QueryRow(group.ID).Scan(&group.UserCount)
if err != sql.ErrNoRows {
return err
}
return nil return nil
} }
@ -274,7 +281,7 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod
} }
mgs.Lock() mgs.Lock()
mgs.groups[gid] = &Group{gid, name, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList} mgs.groups[gid] = &Group{gid, name, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList, 0}
mgs.groupCount++ mgs.groupCount++
mgs.Unlock() mgs.Unlock()

View File

@ -35,8 +35,8 @@ func NewDefaultReferrerTracker() (*DefaultReferrerTracker, error) {
even: make(map[string]*ReferrerItem), even: make(map[string]*ReferrerItem),
insert: acc.Insert("viewchunks_referrers").Columns("count, createdAt, domain").Fields("?,UTC_TIMESTAMP(),?").Prepare(), // TODO: Do something more efficient than doing a query for each referrer insert: acc.Insert("viewchunks_referrers").Columns("count, createdAt, domain").Fields("?,UTC_TIMESTAMP(),?").Prepare(), // TODO: Do something more efficient than doing a query for each referrer
} }
//AddScheduledFifteenMinuteTask(refTracker.Tick) AddScheduledFifteenMinuteTask(refTracker.Tick)
AddScheduledSecondTask(refTracker.Tick) //AddScheduledSecondTask(refTracker.Tick)
AddShutdownTask(refTracker.Tick) AddShutdownTask(refTracker.Tick)
return refTracker, acc.FirstError() return refTracker, acc.FirstError()
} }

View File

@ -1,7 +1,299 @@
package common package common
import "strconv" import (
import "../query_gen/lib" "strconv"
"sync"
"../query_gen/lib"
)
var TopicList TopicListInt
type TopicListHolder struct {
List []*TopicsRow
ForumList []Forum
PageList []int
Page int
LastPage int
}
type TopicListInt interface {
GetListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error)
GetList(page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error)
}
type DefaultTopicList struct {
oddGroups map[int]*TopicListHolder
evenGroups map[int]*TopicListHolder
oddLock sync.RWMutex
evenLock sync.RWMutex
groupList []int // TODO: Use an atomic.Value instead to allow this to be updated on long ticks
}
func NewDefaultTopicList() (*DefaultTopicList, error) {
tList := &DefaultTopicList{
oddGroups: make(map[int]*TopicListHolder),
evenGroups: make(map[int]*TopicListHolder),
}
var slots = make([]int, 8) // Only cache the topic list for eight groups
// TODO: Do something more efficient than this
allGroups, err := Groups.GetAll()
if err != nil {
return nil, err
}
if len(allGroups) > 0 {
var stopHere int
if len(allGroups) <= 8 {
stopHere = len(allGroups)
} else {
stopHere = 8
}
var lowest = allGroups[0].UserCount
for i := 0; i < stopHere; i++ {
slots[i] = i
if allGroups[i].UserCount < lowest {
lowest = allGroups[i].UserCount
}
}
var findNewLowest = func() {
for _, slot := range slots {
if allGroups[slot].UserCount < lowest {
lowest = allGroups[slot].UserCount
}
}
}
for i := 8; i < len(allGroups); i++ {
if allGroups[i].UserCount > lowest {
for ii, slot := range slots {
if allGroups[i].UserCount > slot {
slots[ii] = i
lowest = allGroups[i].UserCount
findNewLowest()
break
}
}
}
}
tList.groupList = slots
}
err = tList.Tick()
if err != nil {
return nil, err
}
AddScheduledHalfSecondTask(tList.Tick)
//AddScheduledSecondTask(tList.GroupCountTick) // TODO: Dynamically change the groups in the short list to be optimised every second
return tList, nil
}
// TODO: Add support for groups other than the guest group
func (tList *DefaultTopicList) Tick() error {
var oddLists = make(map[int]*TopicListHolder)
var evenLists = make(map[int]*TopicListHolder)
var addList = func(gid int, topicList []*TopicsRow, forumList []Forum, pageList []int, page int, lastPage int) {
if gid%2 == 0 {
evenLists[gid] = &TopicListHolder{topicList, forumList, pageList, page, lastPage}
} else {
oddLists[gid] = &TopicListHolder{topicList, forumList, pageList, page, lastPage}
}
}
guestGroup, err := Groups.Get(GuestUser.Group)
if err != nil {
return err
}
topicList, forumList, pageList, page, lastPage, err := tList.getListByGroup(guestGroup, 1)
if err != nil {
return err
}
addList(guestGroup.ID, topicList, forumList, pageList, page, lastPage)
for _, gid := range tList.groupList {
group, err := Groups.Get(gid) // TODO: Bulk load the groups?
if err != nil {
return err
}
topicList, forumList, pageList, page, lastPage, err := tList.getListByGroup(group, 1)
if err != nil {
return err
}
addList(group.ID, topicList, forumList, pageList, page, lastPage)
}
tList.oddLock.Lock()
tList.oddGroups = oddLists
tList.oddLock.Unlock()
tList.evenLock.Lock()
tList.evenGroups = evenLists
tList.evenLock.Unlock()
return nil
}
func (tList *DefaultTopicList) GetListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) {
// TODO: Cache the first three pages not just the first along with all the topics on this beaten track
if page == 1 {
var holder *TopicListHolder
var ok bool
if group.ID%2 == 0 {
tList.evenLock.RLock()
holder, ok = tList.evenGroups[group.ID]
tList.evenLock.RUnlock()
} else {
tList.oddLock.RLock()
holder, ok = tList.oddGroups[group.ID]
tList.oddLock.RUnlock()
}
if ok {
return holder.List, holder.ForumList, holder.PageList, holder.Page, holder.LastPage, nil
}
}
return tList.getListByGroup(group, page)
}
func (tList *DefaultTopicList) getListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) {
// TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins?
canSee := group.CanSee
// We need a list of the visible forums for Quick Topic
// ? - Would it be useful, if we could post in social groups from /topics/?
for _, fid := range canSee {
forum := Forums.DirtyGet(fid)
if forum.Name != "" && forum.Active && (forum.ParentType == "" || forum.ParentType == "forum") {
fcopy := forum.Copy()
// TODO: Add a hook here for plugin_guilds
forumList = append(forumList, fcopy)
}
}
// ? - Should we be showing plugin_guilds posts on /topics/?
argList, qlist := ForumListToArgQ(forumList)
if qlist == "" {
// We don't want to kill the page, so pass an empty slice and nil error
return topicList, forumList, pageList, 1, 1, nil
}
topicList, pageList, page, lastPage, err = tList.getList(page, argList, qlist)
return topicList, forumList, pageList, page, lastPage, err
}
// TODO: Reduce the number of returns
func (tList *DefaultTopicList) GetList(page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) {
// TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins?
canSee, err := Forums.GetAllVisibleIDs()
if err != nil {
return nil, nil, nil, 1, 1, err
}
// We need a list of the visible forums for Quick Topic
// ? - Would it be useful, if we could post in social groups from /topics/?
for _, fid := range canSee {
forum := Forums.DirtyGet(fid)
if forum.Name != "" && forum.Active && (forum.ParentType == "" || forum.ParentType == "forum") {
fcopy := forum.Copy()
// TODO: Add a hook here for plugin_guilds
forumList = append(forumList, fcopy)
}
}
// ? - Should we be showing plugin_guilds posts on /topics/?
argList, qlist := ForumListToArgQ(forumList)
if qlist == "" {
// If the super admin can't see anything, then things have gone terribly wrong
return topicList, forumList, pageList, 1, 1, err
}
topicList, pageList, outPage, lastPage, err = tList.getList(page, argList, qlist)
return topicList, forumList, pageList, outPage, lastPage, err
}
// TODO: Rename this to TopicListStore and pass back a TopicList instance holding the pagination data and topic list rather than passing them back one argument at a time
func (tList *DefaultTopicList) getList(page int, argList []interface{}, qlist string) (topicList []*TopicsRow, pageList []int, outPage int, lastPage int, err error) {
topicCount, err := ArgQToTopicCount(argList, qlist)
if err != nil {
return nil, nil, 1, 1, err
}
offset, page, lastPage := PageOffset(topicCount, page, Config.ItemsPerPage)
stmt, err := qgen.Builder.SimpleSelect("topics", "tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount", "parentID IN("+qlist+")", "sticky DESC, lastReplyAt DESC, createdBy DESC", "?,?")
if err != nil {
return nil, nil, 1, 1, err
}
defer stmt.Close()
argList = append(argList, offset)
argList = append(argList, Config.ItemsPerPage)
rows, err := stmt.Query(argList...)
if err != nil {
return nil, nil, 1, 1, err
}
defer rows.Close()
var reqUserList = make(map[int]bool)
for rows.Next() {
topicItem := TopicsRow{ID: 0}
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount)
if err != nil {
return nil, nil, 1, 1, err
}
topicItem.Link = BuildTopicURL(NameToSlug(topicItem.Title), topicItem.ID)
forum := Forums.DirtyGet(topicItem.ParentID)
topicItem.ForumName = forum.Name
topicItem.ForumLink = forum.Link
//topicItem.CreatedAt = RelativeTime(topicItem.CreatedAt)
topicItem.RelativeLastReplyAt = RelativeTime(topicItem.LastReplyAt)
// TODO: Rename this Vhook to better reflect moving the topic list from /routes/ to /common/
if Vhooks["topics_topic_row_assign"] != nil {
RunVhook("topics_topic_row_assign", &topicItem, &forum)
}
topicList = append(topicList, &topicItem)
reqUserList[topicItem.CreatedBy] = true
reqUserList[topicItem.LastReplyBy] = true
}
err = rows.Err()
if err != nil {
return nil, nil, 1, 1, err
}
// Convert the user ID map to a slice, then bulk load the users
var idSlice = make([]int, len(reqUserList))
var i int
for userID := range reqUserList {
idSlice[i] = userID
i++
}
// TODO: What if a user is deleted via the Control Panel?
userList, err := Users.BulkGetMap(idSlice)
if err != nil {
return nil, nil, 1, 1, err
}
// Second pass to the add the user data
// TODO: Use a pointer to TopicsRow instead of TopicsRow itself?
for _, topicItem := range topicList {
topicItem.Creator = userList[topicItem.CreatedBy]
topicItem.LastUser = userList[topicItem.LastReplyBy]
}
pageList = Paginate(topicCount, Config.ItemsPerPage, 5)
return topicList, pageList, page, lastPage, nil
}
// Internal. Don't rely on it. // Internal. Don't rely on it.
func ForumListToArgQ(forums []Forum) (argList []interface{}, qlist string) { func ForumListToArgQ(forums []Forum) (argList []interface{}, qlist string) {

View File

@ -82,11 +82,10 @@ func init() {
DbInits.Add(func(acc *qgen.Accumulator) error { DbInits.Add(func(acc *qgen.Accumulator) error {
userStmts = UserStmts{ userStmts = UserStmts{
activate: acc.SimpleUpdate("users", "active = 1", "uid = ?"), activate: acc.SimpleUpdate("users", "active = 1", "uid = ?"),
changeGroup: acc.SimpleUpdate("users", "group = ?", "uid = ?"), changeGroup: acc.SimpleUpdate("users", "group = ?", "uid = ?"), // TODO: Implement user_count for users_groups here
delete: acc.SimpleDelete("users", "uid = ?"), delete: acc.SimpleDelete("users", "uid = ?"),
setAvatar: acc.SimpleUpdate("users", "avatar = ?", "uid = ?"), setAvatar: acc.SimpleUpdate("users", "avatar = ?", "uid = ?"),
setUsername: acc.SimpleUpdate("users", "name = ?", "uid = ?"), setUsername: acc.SimpleUpdate("users", "name = ?", "uid = ?"),
updateGroup: acc.SimpleUpdate("users", "group = ?", "uid = ?"),
incrementTopics: acc.SimpleUpdate("users", "topics = topics + ?", "uid = ?"), incrementTopics: acc.SimpleUpdate("users", "topics = topics + ?", "uid = ?"),
updateLevel: acc.SimpleUpdate("users", "level = ?", "uid = ?"), updateLevel: acc.SimpleUpdate("users", "level = ?", "uid = ?"),
incrementScore: acc.SimpleUpdate("users", "score = score + ?", "uid = ?"), incrementScore: acc.SimpleUpdate("users", "score = score + ?", "uid = ?"),

View File

@ -51,7 +51,7 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
cache: cache, cache: cache,
get: acc.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", ""), get: acc.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", ""),
exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""), exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""),
register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"), register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"), // TODO: Implement user_count on users_groups here
usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""), usernameExists: acc.SimpleSelect("users", "name", "name = ?", "", ""),
userCount: acc.SimpleCount("users", "", ""), userCount: acc.SimpleCount("users", "", ""),
}, acc.FirstError() }, acc.FirstError()

View File

@ -45,7 +45,7 @@ func init() {
common.Config.SMTPPort = "25" common.Config.SMTPPort = "25"
// Misc // Misc
common.Config.DefaultRoute = "routeTopics" common.Config.DefaultRoute = "routes.TopicList"
common.Config.DefaultGroup = 3 // Should be a setting in the database common.Config.DefaultGroup = 3 // Should be a setting in the database
common.Config.ActivationGroup = 5 // Should be a setting in the database common.Config.ActivationGroup = 5 // Should be a setting in the database
common.Config.StaffCSS = "staff_post" common.Config.StaffCSS = "staff_post"

View File

@ -25,8 +25,6 @@ type Stmts struct {
groupEntryExists *sql.Stmt groupEntryExists *sql.Stmt
getForumTopicsOffset *sql.Stmt getForumTopicsOffset *sql.Stmt
getAttachment *sql.Stmt getAttachment *sql.Stmt
getTopicList *sql.Stmt
getTopicReplies *sql.Stmt
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
getProfileReplies *sql.Stmt getProfileReplies *sql.Stmt
getWatchers *sql.Stmt getWatchers *sql.Stmt
@ -182,20 +180,6 @@ func _gen_mssql() (err error) {
return err return err
} }
log.Print("Preparing getTopicList statement.")
stmts.getTopicList, err = db.Prepare("SELECT [topics].[tid],[topics].[title],[topics].[content],[topics].[createdBy],[topics].[is_closed],[topics].[sticky],[topics].[createdAt],[topics].[parentID],[users].[name],[users].[avatar] FROM [topics] LEFT JOIN [users] ON [topics].[createdBy] = [users].[uid] ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC")
if err != nil {
log.Print("Bad Query: ","SELECT [topics].[tid],[topics].[title],[topics].[content],[topics].[createdBy],[topics].[is_closed],[topics].[sticky],[topics].[createdAt],[topics].[parentID],[users].[name],[users].[avatar] FROM [topics] LEFT JOIN [users] ON [topics].[createdBy] = [users].[uid] ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC")
return err
}
log.Print("Preparing getTopicReplies statement.")
stmts.getTopicReplies, err = db.Prepare("SELECT [replies].[rid],[replies].[content],[replies].[createdBy],[replies].[createdAt],[replies].[lastEdit],[replies].[lastEditBy],[users].[avatar],[users].[name],[users].[group],[users].[url_prefix],[users].[url_name],[users].[level],[replies].[ipaddress] FROM [replies] LEFT JOIN [users] ON [replies].[createdBy] = [users].[uid] WHERE [tid] = ?1")
if err != nil {
log.Print("Bad Query: ","SELECT [replies].[rid],[replies].[content],[replies].[createdBy],[replies].[createdAt],[replies].[lastEdit],[replies].[lastEditBy],[users].[avatar],[users].[name],[users].[group],[users].[url_prefix],[users].[url_name],[users].[level],[replies].[ipaddress] FROM [replies] LEFT JOIN [users] ON [replies].[createdBy] = [users].[uid] WHERE [tid] = ?1")
return err
}
log.Print("Preparing getForumTopics statement.") log.Print("Preparing getForumTopics statement.")
stmts.getForumTopics, err = db.Prepare("SELECT [topics].[tid],[topics].[title],[topics].[content],[topics].[createdBy],[topics].[is_closed],[topics].[sticky],[topics].[createdAt],[topics].[lastReplyAt],[topics].[parentID],[users].[name],[users].[avatar] FROM [topics] LEFT JOIN [users] ON [topics].[createdBy] = [users].[uid] WHERE [topics].[parentID] = ?1 ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC") stmts.getForumTopics, err = db.Prepare("SELECT [topics].[tid],[topics].[title],[topics].[content],[topics].[createdBy],[topics].[is_closed],[topics].[sticky],[topics].[createdAt],[topics].[lastReplyAt],[topics].[parentID],[users].[name],[users].[avatar] FROM [topics] LEFT JOIN [users] ON [topics].[createdBy] = [users].[uid] WHERE [topics].[parentID] = ?1 ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC")
if err != nil { if err != nil {

View File

@ -27,8 +27,6 @@ type Stmts struct {
groupEntryExists *sql.Stmt groupEntryExists *sql.Stmt
getForumTopicsOffset *sql.Stmt getForumTopicsOffset *sql.Stmt
getAttachment *sql.Stmt getAttachment *sql.Stmt
getTopicList *sql.Stmt
getTopicReplies *sql.Stmt
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
getProfileReplies *sql.Stmt getProfileReplies *sql.Stmt
getWatchers *sql.Stmt getWatchers *sql.Stmt
@ -168,18 +166,6 @@ func _gen_mysql() (err error) {
return err return err
} }
log.Print("Preparing getTopicList statement.")
stmts.getTopicList, err = db.Prepare("SELECT `topics`.`tid`, `topics`.`title`, `topics`.`content`, `topics`.`createdBy`, `topics`.`is_closed`, `topics`.`sticky`, `topics`.`createdAt`, `topics`.`parentID`, `users`.`name`, `users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy` = `users`.`uid` ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC")
if err != nil {
return err
}
log.Print("Preparing getTopicReplies statement.")
stmts.getTopicReplies, err = db.Prepare("SELECT `replies`.`rid`, `replies`.`content`, `replies`.`createdBy`, `replies`.`createdAt`, `replies`.`lastEdit`, `replies`.`lastEditBy`, `users`.`avatar`, `users`.`name`, `users`.`group`, `users`.`url_prefix`, `users`.`url_name`, `users`.`level`, `replies`.`ipaddress` FROM `replies` LEFT JOIN `users` ON `replies`.`createdBy` = `users`.`uid` WHERE `tid` = ?")
if err != nil {
return err
}
log.Print("Preparing getForumTopics statement.") log.Print("Preparing getForumTopics statement.")
stmts.getForumTopics, err = db.Prepare("SELECT `topics`.`tid`, `topics`.`title`, `topics`.`content`, `topics`.`createdBy`, `topics`.`is_closed`, `topics`.`sticky`, `topics`.`createdAt`, `topics`.`lastReplyAt`, `topics`.`parentID`, `users`.`name`, `users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy` = `users`.`uid` WHERE `topics`.`parentID` = ? ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC") stmts.getForumTopics, err = db.Prepare("SELECT `topics`.`tid`, `topics`.`title`, `topics`.`content`, `topics`.`createdBy`, `topics`.`is_closed`, `topics`.`sticky`, `topics`.`createdAt`, `topics`.`lastReplyAt`, `topics`.`parentID`, `users`.`name`, `users`.`avatar` FROM `topics` LEFT JOIN `users` ON `topics`.`createdBy` = `users`.`uid` WHERE `topics`.`parentID` = ? ORDER BY topics.sticky DESC,topics.lastReplyAt DESC,topics.createdBy DESC")
if err != nil { if err != nil {

View File

@ -26,7 +26,7 @@ var RouteMap = map[string]interface{}{
"routeWebsockets": routeWebsockets, "routeWebsockets": routeWebsockets,
"routeReportSubmit": routeReportSubmit, "routeReportSubmit": routeReportSubmit,
"routes.CreateTopic": routes.CreateTopic, "routes.CreateTopic": routes.CreateTopic,
"routeTopics": routeTopics, "routes.TopicList": routes.TopicList,
"routePanelForums": routePanelForums, "routePanelForums": routePanelForums,
"routePanelForumsCreateSubmit": routePanelForumsCreateSubmit, "routePanelForumsCreateSubmit": routePanelForumsCreateSubmit,
"routePanelForumsDelete": routePanelForumsDelete, "routePanelForumsDelete": routePanelForumsDelete,
@ -128,7 +128,7 @@ var routeMapEnum = map[string]int{
"routeWebsockets": 7, "routeWebsockets": 7,
"routeReportSubmit": 8, "routeReportSubmit": 8,
"routes.CreateTopic": 9, "routes.CreateTopic": 9,
"routeTopics": 10, "routes.TopicList": 10,
"routePanelForums": 11, "routePanelForums": 11,
"routePanelForumsCreateSubmit": 12, "routePanelForumsCreateSubmit": 12,
"routePanelForumsDelete": 13, "routePanelForumsDelete": 13,
@ -228,7 +228,7 @@ var reverseRouteMapEnum = map[int]string{
7: "routeWebsockets", 7: "routeWebsockets",
8: "routeReportSubmit", 8: "routeReportSubmit",
9: "routes.CreateTopic", 9: "routes.CreateTopic",
10: "routeTopics", 10: "routes.TopicList",
11: "routePanelForums", 11: "routePanelForums",
12: "routePanelForumsCreateSubmit", 12: "routePanelForumsCreateSubmit",
13: "routePanelForumsDelete", 13: "routePanelForumsDelete",
@ -524,6 +524,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Deflect malformed requests // Deflect malformed requests
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host {
//log.Print("len(req.URL.Path): ",len(req.URL.Path))
//log.Print("req.URL.Path[0]: ",req.URL.Path[0])
//log.Print("req.Host: ",req.Host)
//log.Print("common.Site.Host: ",common.Site.Host)
w.WriteHeader(200) // 400 w.WriteHeader(200) // 400
w.Write([]byte("")) w.Write([]byte(""))
log.Print("Malformed Request") log.Print("Malformed Request")
@ -687,13 +692,13 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.OSViewCounter.Bump(osMapEnum[os]) common.OSViewCounter.Bump(osMapEnum[os])
} }
referrer := req.Header.Get("Referer") // Check the referrer header too? :P referrer := req.Header.Get("Referer") // Check the 'referrer' header too? :P
if referrer != "" { if referrer != "" {
// ? Optimise this a little? // ? Optimise this a little?
referrer = strings.TrimPrefix(strings.TrimPrefix(referrer,"http://"),"https://") referrer = strings.TrimPrefix(strings.TrimPrefix(referrer,"http://"),"https://")
referrer = strings.Split(referrer,"/")[0] referrer = strings.Split(referrer,"/")[0]
portless := strings.Split(referrer,":")[0] portless := strings.Split(referrer,":")[0]
if portless != "localhost" && portless != "127.0.0.1" && portless == common.Site.Host { if portless != "localhost" && portless != "127.0.0.1" && portless != common.Site.Host {
common.ReferrerTracker.Bump(referrer) common.ReferrerTracker.Bump(referrer)
} }
} }
@ -811,7 +816,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
err = routes.CreateTopic(w,req,user,extraData) err = routes.CreateTopic(w,req,user,extraData)
default: default:
common.RouteViewCounter.Bump(10) common.RouteViewCounter.Bump(10)
err = routeTopics(w,req,user) err = routes.TopicList(w,req,user)
} }
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)

View File

@ -2,15 +2,16 @@
package main package main
var dbTablePrimaryKeys = map[string]string{ var dbTablePrimaryKeys = map[string]string{
"users_groups":"gid",
"users_groups_scheduler":"uid",
"replies":"rid", "replies":"rid",
"word_filters":"wfid", "revisions":"reviseID",
"users_replies":"rid", "users_replies":"rid",
"activity_stream":"asid", "activity_stream":"asid",
"users":"uid", "word_filters":"wfid",
"forums":"fid", "users_groups":"gid",
"topics":"tid", "topics":"tid",
"forums":"fid",
"attachments":"attachID", "attachments":"attachID",
"polls":"pollID", "polls":"pollID",
"users":"uid",
"users_groups_scheduler":"uid",
} }

View File

@ -15,6 +15,7 @@ import (
"./common" "./common"
"./install/install" "./install/install"
"./query_gen/lib" "./query_gen/lib"
"./routes"
//"github.com/husobee/vestigo" //"github.com/husobee/vestigo"
) )
@ -99,35 +100,102 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
b.Fatal(err) b.Fatal(err)
} }
} }
prev := common.Dev.DebugMode
prev2 := common.Dev.SuperDebug
common.Dev.DebugMode = false
common.Dev.SuperDebug = false
admin, err := common.Users.Get(1)
if err != nil {
b.Fatal(err)
}
if !admin.IsAdmin {
b.Fatal("UID1 is not an admin")
}
adminUIDCookie := http.Cookie{Name: "uid", Value: "1", Path: "/", MaxAge: common.Year}
adminSessionCookie := http.Cookie{Name: "session", Value: admin.Session, Path: "/", MaxAge: common.Year}
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
admin, err := common.Users.Get(1) for pb.Next() {
topicW := httptest.NewRecorder()
topicReqAdmin := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
topicReqAdmin.AddCookie(&adminUIDCookie)
topicReqAdmin.AddCookie(&adminSessionCookie)
// Deal with the session stuff, etc.
user, ok := common.PreRoute(topicW, topicReqAdmin)
if !ok {
b.Fatal("Mysterious error!")
}
//topicW.Body.Reset()
routes.ViewTopic(topicW, topicReqAdmin, user, "1")
if topicW.Code != 200 {
b.Log(topicW.Body)
b.Fatal("HTTP Error!")
}
}
})
common.Dev.DebugMode = prev
common.Dev.SuperDebug = prev2
}
func BenchmarkTopicAdminRouteParallelWithRouter(b *testing.B) {
b.ReportAllocs()
if !gloinited {
err := gloinit()
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
if !admin.IsAdmin { }
b.Fatal("UID1 is not an admin") router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
} prev := common.Dev.DebugMode
adminUIDCookie := http.Cookie{Name: "uid", Value: "1", Path: "/", MaxAge: common.Year} prev2 := common.Dev.SuperDebug
adminSessionCookie := http.Cookie{Name: "session", Value: admin.Session, Path: "/", MaxAge: common.Year} common.Dev.DebugMode = false
common.Dev.SuperDebug = false
topicW := httptest.NewRecorder() admin, err := common.Users.Get(1)
topicReq := httptest.NewRequest("get", "/topic/1", bytes.NewReader(nil)) if err != nil {
topicReqAdmin := topicReq b.Fatal(err)
topicReqAdmin.AddCookie(&adminUIDCookie) }
topicReqAdmin.AddCookie(&adminSessionCookie) if !admin.IsAdmin {
b.Fatal("UID1 is not an admin")
// Deal with the session stuff, etc. }
user, ok := common.PreRoute(topicW, topicReqAdmin) adminUIDCookie := http.Cookie{Name: "uid", Value: "1", Path: "/", MaxAge: common.Year}
if !ok { adminSessionCookie := http.Cookie{Name: "session", Value: admin.Session, Path: "/", MaxAge: common.Year}
b.Fatal("Mysterious error!")
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
topicW.Body.Reset() topicW := httptest.NewRecorder()
routeTopicID(topicW, topicReqAdmin, user) topicReqAdmin := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
topicReqAdmin.AddCookie(&adminUIDCookie)
topicReqAdmin.AddCookie(&adminSessionCookie)
topicReqAdmin.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")
topicReqAdmin.Header.Set("Host", "localhost")
topicReqAdmin.Host = "localhost"
//topicW.Body.Reset()
router.ServeHTTP(topicW, topicReqAdmin)
if topicW.Code != 200 {
b.Log(topicW.Body)
b.Fatal("HTTP Error!")
}
} }
}) })
common.Dev.DebugMode = prev
common.Dev.SuperDebug = prev2
}
func BenchmarkTopicAdminRouteParallelAlt(b *testing.B) {
BenchmarkTopicAdminRouteParallel(b)
}
func BenchmarkTopicAdminRouteParallelWithRouterAlt(b *testing.B) {
BenchmarkTopicAdminRouteParallelWithRouter(b)
}
func BenchmarkTopicAdminRouteParallelAltAlt(b *testing.B) {
BenchmarkTopicAdminRouteParallel(b)
} }
func BenchmarkTopicGuestRouteParallel(b *testing.B) { func BenchmarkTopicGuestRouteParallel(b *testing.B) {
@ -138,15 +206,225 @@ func BenchmarkTopicGuestRouteParallel(b *testing.B) {
b.Fatal(err) b.Fatal(err)
} }
} }
prev := common.Dev.DebugMode
prev2 := common.Dev.SuperDebug
common.Dev.DebugMode = false
common.Dev.SuperDebug = false
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
topicW := httptest.NewRecorder()
topicReq := httptest.NewRequest("get", "/topic/1", bytes.NewReader(nil))
for pb.Next() { for pb.Next() {
topicW.Body.Reset() topicW := httptest.NewRecorder()
routeTopicID(topicW, topicReq, common.GuestUser) topicReq := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
//topicW.Body.Reset()
routes.ViewTopic(topicW, topicReq, common.GuestUser, "1")
if topicW.Code != 200 {
b.Log(topicW.Body)
b.Fatal("HTTP Error!")
}
} }
}) })
common.Dev.DebugMode = prev
common.Dev.SuperDebug = prev2
}
func BenchmarkTopicGuestRouteParallelDebugMode(b *testing.B) {
b.ReportAllocs()
if !gloinited {
err := gloinit()
if err != nil {
b.Fatal(err)
}
}
prev := common.Dev.DebugMode
prev2 := common.Dev.SuperDebug
common.Dev.DebugMode = true
common.Dev.SuperDebug = false
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
topicW := httptest.NewRecorder()
topicReq := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
//topicW.Body.Reset()
routes.ViewTopic(topicW, topicReq, common.GuestUser, "1")
if topicW.Code != 200 {
b.Log(topicW.Body)
b.Fatal("HTTP Error!")
}
}
})
common.Dev.DebugMode = prev
common.Dev.SuperDebug = prev2
}
func BenchmarkTopicGuestRouteParallelWithRouter(b *testing.B) {
b.ReportAllocs()
if !gloinited {
err := gloinit()
if err != nil {
b.Fatal(err)
}
}
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
prev := common.Dev.DebugMode
prev2 := common.Dev.SuperDebug
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() {
topicW := httptest.NewRecorder()
topicReq := httptest.NewRequest("GET", "/topic/hm.1", bytes.NewReader(nil))
topicReq.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")
topicReq.Header.Set("Host", "localhost")
topicReq.Host = "localhost"
//topicW.Body.Reset()
router.ServeHTTP(topicW, topicReq)
if topicW.Code != 200 {
b.Log(topicW.Body)
b.Fatal("HTTP Error!")
}
}
})
//defer pprof.StopCPUProfile()
common.Dev.DebugMode = prev
common.Dev.SuperDebug = prev2
}
func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) {
b.ReportAllocs()
if !gloinited {
err := gloinit()
if err != nil {
b.Fatal(err)
}
}
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
prev := common.Dev.DebugMode
prev2 := common.Dev.SuperDebug
common.Dev.DebugMode = false
common.Dev.SuperDebug = false
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
badW := httptest.NewRecorder()
badReq := httptest.NewRequest("GET", "/garble/haa", bytes.NewReader(nil))
badReq.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")
badReq.Header.Set("Host", "localhost")
badReq.Host = "localhost"
router.ServeHTTP(badW, badReq)
}
})
common.Dev.DebugMode = prev
common.Dev.SuperDebug = prev2
}
func BenchmarkTopicsGuestRouteParallelWithRouter(b *testing.B) {
b.ReportAllocs()
if !gloinited {
err := gloinit()
if err != nil {
b.Fatal(err)
}
}
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
prev := common.Dev.DebugMode
prev2 := common.Dev.SuperDebug
common.Dev.DebugMode = false
common.Dev.SuperDebug = false
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
listW := httptest.NewRecorder()
listReq := httptest.NewRequest("GET", "/topics/", bytes.NewReader(nil))
listReq.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")
listReq.Header.Set("Host", "localhost")
listReq.Host = "localhost"
router.ServeHTTP(listW, listReq)
if listW.Code != 200 {
b.Log(listW.Body)
b.Fatal("HTTP Error!")
}
}
})
common.Dev.DebugMode = prev
common.Dev.SuperDebug = prev2
}
func BenchmarkForumsGuestRouteParallelWithRouter(b *testing.B) {
b.ReportAllocs()
if !gloinited {
err := gloinit()
if err != nil {
b.Fatal(err)
}
}
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
prev := common.Dev.DebugMode
prev2 := common.Dev.SuperDebug
common.Dev.DebugMode = false
common.Dev.SuperDebug = false
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
listW := httptest.NewRecorder()
listReq := httptest.NewRequest("GET", "/forums/", bytes.NewReader(nil))
listReq.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")
listReq.Header.Set("Host", "localhost")
listReq.Host = "localhost"
router.ServeHTTP(listW, listReq)
if listW.Code != 200 {
b.Log(listW.Body)
b.Fatal("HTTP Error!")
}
}
})
common.Dev.DebugMode = prev
common.Dev.SuperDebug = prev2
}
func BenchmarkForumGuestRouteParallelWithRouter(b *testing.B) {
b.ReportAllocs()
if !gloinited {
err := gloinit()
if err != nil {
b.Fatal(err)
}
}
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
prev := common.Dev.DebugMode
prev2 := common.Dev.SuperDebug
common.Dev.DebugMode = false
common.Dev.SuperDebug = false
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
listW := httptest.NewRecorder()
listReq := httptest.NewRequest("GET", "/forum/general.2", bytes.NewReader(nil))
listReq.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")
listReq.Header.Set("Host", "localhost")
listReq.Host = "localhost"
router.ServeHTTP(listW, listReq)
if listW.Code != 200 {
b.Log(listW.Body)
b.Fatal("HTTP Error!")
}
}
})
common.Dev.DebugMode = prev
common.Dev.SuperDebug = prev2
} }
// TODO: Make these routes compatible with the changes to the router // TODO: Make these routes compatible with the changes to the router
@ -165,19 +443,19 @@ func BenchmarkForumsAdminRouteParallel(b *testing.B) {
if !admin.Is_Admin { if !admin.Is_Admin {
panic("UID1 is not an admin") panic("UID1 is not an admin")
} }
admin_uid_cookie := http.Cookie{Name:"uid",Value:"1",Path:"/",MaxAge: year} adminUidCookie := http.Cookie{Name:"uid",Value:"1",Path:"/",MaxAge: year}
admin_session_cookie := http.Cookie{Name:"session",Value: admin.Session,Path:"/",MaxAge: year} adminSessionCookie := http.Cookie{Name:"session",Value: admin.Session,Path:"/",MaxAge: year}
forums_w := httptest.NewRecorder() forumsW := httptest.NewRecorder()
forums_req := httptest.NewRequest("get","/forums/",bytes.NewReader(nil)) forumsReq := httptest.NewRequest("get","/forums/",bytes.NewReader(nil))
forums_req_admin := forums_req forumsReq_admin := forums_req
forums_req_admin.AddCookie(&admin_uid_cookie) forumsReq_admin.AddCookie(&adminUidCookie)
forums_req_admin.AddCookie(&admin_session_cookie) forumsReq_admin.AddCookie(&adminSessionCookie)
forums_handler := http.HandlerFunc(route_forums) forumsHandler := http.HandlerFunc(route_forums)
for pb.Next() { for pb.Next() {
forums_w.Body.Reset() forumsW.Body.Reset()
forums_handler.ServeHTTP(forums_w,forums_req_admin) forumsHandler.ServeHTTP(forumsW,forumsReqAdmin)
} }
}) })
} }
@ -196,47 +474,29 @@ func BenchmarkForumsAdminRouteParallelProf(b *testing.B) {
if !admin.Is_Admin { if !admin.Is_Admin {
panic("UID1 is not an admin") panic("UID1 is not an admin")
} }
admin_uid_cookie := http.Cookie{Name:"uid",Value:"1",Path:"/",MaxAge: year} adminUidCookie := http.Cookie{Name:"uid",Value:"1",Path:"/",MaxAge: year}
admin_session_cookie := http.Cookie{Name:"session",Value: admin.Session,Path: "/",MaxAge: year} adminSessionCookie := http.Cookie{Name:"session",Value: admin.Session,Path: "/",MaxAge: year}
forums_w := httptest.NewRecorder() forumsW := httptest.NewRecorder()
forums_req := httptest.NewRequest("get","/forums/",bytes.NewReader(nil)) forumsReq := httptest.NewRequest("get","/forums/",bytes.NewReader(nil))
forums_req_admin := forums_req forumsReqAdmin := forumsReq
forums_req_admin.AddCookie(&admin_uid_cookie) forumsReqAdmin.AddCookie(&admin_uid_cookie)
forums_req_admin.AddCookie(&admin_session_cookie) forumsReqAdmin.AddCookie(&admin_session_cookie)
forums_handler := http.HandlerFunc(route_forums) forumsHandler := http.HandlerFunc(route_forums)
f, err := os.Create("cpu_forums_admin_parallel.prof") f, err := os.Create("cpu_forums_admin_parallel.prof")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
pprof.StartCPUProfile(f) pprof.StartCPUProfile(f)
for pb.Next() { for pb.Next() {
forums_w.Body.Reset() forumsW.Body.Reset()
forums_handler.ServeHTTP(forums_w,forums_req_admin) forumsHandler.ServeHTTP(forumsW,forumsReqAdmin)
} }
pprof.StopCPUProfile() pprof.StopCPUProfile()
}) })
} }
func BenchmarkForumsGuestRouteParallel(b *testing.B) { func BenchmarkRoutesSerial(b *testing.B) {
b.ReportAllocs()
if !gloinited {
gloinit()
}
b.RunParallel(func(pb *testing.PB) {
forums_w := httptest.NewRecorder()
forums_req := httptest.NewRequest("get","/forums/",bytes.NewReader(nil))
forums_handler := http.HandlerFunc(route_forums)
for pb.Next() {
forums_w.Body.Reset()
forums_handler.ServeHTTP(forums_w,forums_req)
}
})
}
*/
/*func BenchmarkRoutesSerial(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
admin, err := users.Get(1) admin, err := users.Get(1)
if err != nil { if err != nil {

View File

@ -142,7 +142,7 @@ func init() {
common.Config.SMTPPort = "25" common.Config.SMTPPort = "25"
// Misc // Misc
common.Config.DefaultRoute = "routeTopics" common.Config.DefaultRoute = "routes.TopicList"
common.Config.DefaultGroup = 3 // Should be a setting in the database common.Config.DefaultGroup = 3 // Should be a setting in the database
common.Config.ActivationGroup = 5 // Should be a setting in the database common.Config.ActivationGroup = 5 // Should be a setting in the database
common.Config.StaffCSS = "staff_post" common.Config.StaffCSS = "staff_post"

View File

@ -126,6 +126,7 @@
"OperatingSystems": { "OperatingSystems": {
"windows": "Microsoft Windows", "windows": "Microsoft Windows",
"linux":"Linux", "linux":"Linux",
"mac":"Apple Mac",
"android": "Android", "android": "Android",
"iphone":"iPhone", "iphone":"iPhone",
"unknown":"Unknown" "unknown":"Unknown"

View File

@ -96,6 +96,10 @@ func afterDBInit() (err error) {
if err != nil { if err != nil {
return err return err
} }
common.TopicList, err = common.NewDefaultTopicList()
if err != nil {
return err
}
common.GlobalViewCounter, err = common.NewGlobalViewCounter() common.GlobalViewCounter, err = common.NewGlobalViewCounter()
if err != nil { if err != nil {

View File

@ -1135,6 +1135,9 @@ func _gen_mssql() (err error) {
// Internal methods, not exposed in the interface // Internal methods, not exposed in the interface
func (adapter *MssqlAdapter) pushStatement(name string, stype string, querystr string) { func (adapter *MssqlAdapter) pushStatement(name string, stype string, querystr string) {
if name[0] == '_' {
return
}
adapter.Buffer[name] = DBStmt{querystr, stype} adapter.Buffer[name] = DBStmt{querystr, stype}
adapter.BufferOrder = append(adapter.BufferOrder, name) adapter.BufferOrder = append(adapter.BufferOrder, name)
} }

View File

@ -696,6 +696,9 @@ func _gen_mysql() (err error) {
// Internal methods, not exposed in the interface // Internal methods, not exposed in the interface
func (adapter *MysqlAdapter) pushStatement(name string, stype string, querystr string) { func (adapter *MysqlAdapter) pushStatement(name string, stype string, querystr string) {
if name[0] == '_' {
return
}
adapter.Buffer[name] = DBStmt{querystr, stype} adapter.Buffer[name] = DBStmt{querystr, stype}
adapter.BufferOrder = append(adapter.BufferOrder, name) adapter.BufferOrder = append(adapter.BufferOrder, name)
} }

View File

@ -393,6 +393,9 @@ func _gen_pgsql() (err error) {
// Internal methods, not exposed in the interface // Internal methods, not exposed in the interface
func (adapter *PgsqlAdapter) pushStatement(name string, stype string, querystr string) { func (adapter *PgsqlAdapter) pushStatement(name string, stype string, querystr string) {
if name[0] == '_' {
return
}
adapter.Buffer[name] = DBStmt{querystr, stype} adapter.Buffer[name] = DBStmt{querystr, stype}
adapter.BufferOrder = append(adapter.BufferOrder, name) adapter.BufferOrder = append(adapter.BufferOrder, name)
} }

View File

@ -255,10 +255,6 @@ func writeSelects(adapter qgen.Adapter) error {
} }
func writeLeftJoins(adapter qgen.Adapter) error { func writeLeftJoins(adapter qgen.Adapter) error {
adapter.SimpleLeftJoin("getTopicList", "topics", "users", "topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.parentID, users.name, users.avatar", "topics.createdBy = users.uid", "", "topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC", "")
adapter.SimpleLeftJoin("getTopicReplies", "replies", "users", "replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress", "replies.createdBy = users.uid", "tid = ?", "", "")
adapter.SimpleLeftJoin("getForumTopics", "topics", "users", "topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.lastReplyAt, topics.parentID, users.name, users.avatar", "topics.createdBy = users.uid", "topics.parentID = ?", "topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy desc", "") adapter.SimpleLeftJoin("getForumTopics", "topics", "users", "topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.lastReplyAt, topics.parentID, users.name, users.avatar", "topics.createdBy = users.uid", "topics.parentID = ?", "topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy desc", "")
adapter.SimpleLeftJoin("getProfileReplies", "users_replies", "users", "users_replies.rid, users_replies.content, users_replies.createdBy, users_replies.createdAt, users_replies.lastEdit, users_replies.lastEditBy, users.avatar, users.name, users.group", "users_replies.createdBy = users.uid", "users_replies.uid = ?", "", "") adapter.SimpleLeftJoin("getProfileReplies", "users_replies", "users", "users_replies.rid, users_replies.content, users_replies.createdBy, users_replies.createdAt, users_replies.lastEdit, users_replies.lastEditBy, users.avatar, users.name, users.group", "users_replies.createdBy = users.uid", "users_replies.uid = ?", "", "")
@ -306,7 +302,7 @@ func writeUpdates(adapter qgen.Adapter) error {
build.Update("updateTheme").Table("themes").Set("default = ?").Where("uname = ?").Parse() build.Update("updateTheme").Table("themes").Set("default = ?").Where("uname = ?").Parse()
build.Update("updateUser").Table("users").Set("name = ?, email = ?, group = ?").Where("uid = ?").Parse() build.Update("updateUser").Table("users").Set("name = ?, email = ?, group = ?").Where("uid = ?").Parse() // TODO: Implement user_count for users_groups on things which use this
build.Update("updateGroupPerms").Table("users_groups").Set("permissions = ?").Where("gid = ?").Parse() build.Update("updateGroupPerms").Table("users_groups").Set("permissions = ?").Where("gid = ?").Parse()

View File

@ -48,6 +48,7 @@ func createTables(adapter qgen.Adapter) error {
qgen.DBTableColumn{"is_mod", "boolean", 0, false, false, "0"}, qgen.DBTableColumn{"is_mod", "boolean", 0, false, false, "0"},
qgen.DBTableColumn{"is_admin", "boolean", 0, false, false, "0"}, qgen.DBTableColumn{"is_admin", "boolean", 0, false, false, "0"},
qgen.DBTableColumn{"is_banned", "boolean", 0, false, false, "0"}, qgen.DBTableColumn{"is_banned", "boolean", 0, false, false, "0"},
qgen.DBTableColumn{"user_count", "int", 0, false, false, "0"}, // TODO: Implement this
qgen.DBTableColumn{"tag", "varchar", 50, false, false, "''"}, qgen.DBTableColumn{"tag", "varchar", 50, false, false, "''"},
}, },
@ -215,7 +216,9 @@ func createTables(adapter qgen.Adapter) error {
qgen.DBTableColumn{"contentType", "varchar", 100, false, false, "replies"}, qgen.DBTableColumn{"contentType", "varchar", 100, false, false, "replies"},
qgen.DBTableColumn{"createdAt", "createdAt", 0, false, false, ""}, qgen.DBTableColumn{"createdAt", "createdAt", 0, false, false, ""},
}, },
[]qgen.DBTableKey{}, []qgen.DBTableKey{
qgen.DBTableKey{"reviseID", "primary"},
},
) )
qgen.Install.CreateTable("polls", "utf8mb4", "utf8mb4_general_ci", qgen.Install.CreateTable("polls", "utf8mb4", "utf8mb4_general_ci",

View File

@ -383,6 +383,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Deflect malformed requests // Deflect malformed requests
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host { if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || req.Host != common.Site.Host {
//log.Print("len(req.URL.Path): ",len(req.URL.Path))
//log.Print("req.URL.Path[0]: ",req.URL.Path[0])
//log.Print("req.Host: ",req.Host)
//log.Print("common.Site.Host: ",common.Site.Host)
w.WriteHeader(200) // 400 w.WriteHeader(200) // 400
w.Write([]byte("")) w.Write([]byte(""))
log.Print("Malformed Request") log.Print("Malformed Request")

View File

@ -19,7 +19,7 @@ func routes() {
addRouteGroup(reportGroup) addRouteGroup(reportGroup)
topicGroup := newRouteGroup("/topics/", topicGroup := newRouteGroup("/topics/",
View("routeTopics", "/topics/"), View("routes.TopicList", "/topics/"),
MemberView("routes.CreateTopic", "/topics/create/", "extraData"), MemberView("routes.CreateTopic", "/topics/create/", "extraData"),
) )
addRouteGroup(topicGroup) addRouteGroup(topicGroup)

135
routes.go
View File

@ -15,7 +15,6 @@ import (
"time" "time"
"./common" "./common"
"./query_gen/lib"
) )
// A blank list to fill out that parameter in Page for routes which don't use it // A blank list to fill out that parameter in Page for routes which don't use it
@ -45,140 +44,6 @@ func routeUploads() {
func BadRoute() { func BadRoute() {
} }
func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, ferr := common.UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
headerVars.Zone = "topics"
headerVars.MetaDesc = headerVars.Settings["meta_desc"].(string)
group, err := common.Groups.Get(user.Group)
if err != nil {
log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID)
return common.LocalError("Something weird happened", w, r, user)
}
// TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins?
var canSee []int
if user.IsSuperAdmin {
canSee, err = common.Forums.GetAllVisibleIDs()
if err != nil {
return common.InternalError(err, w, r)
}
} else {
canSee = group.CanSee
}
// We need a list of the visible forums for Quick Topic
// ? - Would it be useful, if we could post in social groups from /topics/?
var forumList []common.Forum
for _, fid := range canSee {
forum := common.Forums.DirtyGet(fid)
if forum.Name != "" && forum.Active && (forum.ParentType == "" || forum.ParentType == "forum") {
fcopy := forum.Copy()
// TODO: Add a hook here for plugin_guilds
forumList = append(forumList, fcopy)
}
}
// ? - Should we be showing plugin_guilds posts on /topics/?
argList, qlist := common.ForumListToArgQ(forumList)
// ! Need an inline error not a page level error
if qlist == "" {
return common.NotFound(w, r)
}
topicCount, err := common.ArgQToTopicCount(argList, qlist)
if err != nil {
return common.InternalError(err, w, r)
}
// Get the current page
page, _ := strconv.Atoi(r.FormValue("page"))
offset, page, lastPage := common.PageOffset(topicCount, page, common.Config.ItemsPerPage)
var topicList []*common.TopicsRow
stmt, err := qgen.Builder.SimpleSelect("topics", "tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount", "parentID IN("+qlist+")", "sticky DESC, lastReplyAt DESC, createdBy DESC", "?,?")
if err != nil {
return common.InternalError(err, w, r)
}
defer stmt.Close()
argList = append(argList, offset)
argList = append(argList, common.Config.ItemsPerPage)
rows, err := stmt.Query(argList...)
if err != nil {
return common.InternalError(err, w, r)
}
defer rows.Close()
var reqUserList = make(map[int]bool)
for rows.Next() {
topicItem := common.TopicsRow{ID: 0}
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount)
if err != nil {
return common.InternalError(err, w, r)
}
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
forum := common.Forums.DirtyGet(topicItem.ParentID)
topicItem.ForumName = forum.Name
topicItem.ForumLink = forum.Link
//topicItem.CreatedAt = common.RelativeTime(topicItem.CreatedAt)
topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt)
if common.Vhooks["topics_topic_row_assign"] != nil {
common.RunVhook("topics_topic_row_assign", &topicItem, &forum)
}
topicList = append(topicList, &topicItem)
reqUserList[topicItem.CreatedBy] = true
reqUserList[topicItem.LastReplyBy] = true
}
err = rows.Err()
if err != nil {
return common.InternalError(err, w, r)
}
// Convert the user ID map to a slice, then bulk load the users
var idSlice = make([]int, len(reqUserList))
var i int
for userID := range reqUserList {
idSlice[i] = userID
i++
}
// TODO: What if a user is deleted via the Control Panel?
userList, err := common.Users.BulkGetMap(idSlice)
if err != nil {
return common.InternalError(err, w, r)
}
// Second pass to the add the user data
// TODO: Use a pointer to TopicsRow instead of TopicsRow itself?
for _, topicItem := range topicList {
topicItem.Creator = userList[topicItem.CreatedBy]
topicItem.LastUser = userList[topicItem.LastReplyBy]
}
pageList := common.Paginate(topicCount, common.Config.ItemsPerPage, 5)
pi := common.TopicsPage{common.GetTitlePhrase("topics"), user, headerVars, topicList, forumList, common.Config.DefaultForum, pageList, page, lastPage}
if common.PreRenderHooks["pre_render_topic_list"] != nil {
if common.RunPreRenderHook("pre_render_topic_list", w, r, &user, &pi) {
return nil
}
}
err = common.RunThemeTemplate(headerVars.Theme.Name, "topics", pi, w)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
}
func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError { func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))

View File

@ -18,6 +18,22 @@ import (
"../query_gen/lib" "../query_gen/lib"
) )
type TopicStmts struct {
getReplies *sql.Stmt
}
var topicStmts TopicStmts
// TODO: Move these DbInits into a TopicList abstraction
func init() {
common.DbInits.Add(func(acc *qgen.Accumulator) error {
topicStmts = TopicStmts{
getReplies: acc.SimpleLeftJoin("replies", "users", "replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress, replies.likeCount, replies.actionType", "replies.createdBy = users.uid", "replies.tid = ?", "replies.rid ASC", "?,?"),
}
return acc.FirstError()
})
}
var successJSONBytes = []byte(`{"success":"1"}`) var successJSONBytes = []byte(`{"success":"1"}`)
func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit string) common.RouteError { func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit string) common.RouteError {
@ -97,12 +113,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
tpage := common.TopicPage{topic.Title, user, headerVars, replyList, topic, poll, page, lastPage} tpage := common.TopicPage{topic.Title, user, headerVars, replyList, topic, poll, page, lastPage}
// Get the replies.. // Get the replies..
// TODO: Reuse this statement rather than preparing it on the spot, maybe via a TopicList abstraction rows, err := topicStmts.getReplies.Query(topic.ID, offset, common.Config.ItemsPerPage)
stmt, err := qgen.Builder.SimpleLeftJoin("replies", "users", "replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress, replies.likeCount, replies.actionType", "replies.createdBy = users.uid", "replies.tid = ?", "replies.rid ASC", "?,?")
if err != nil {
return common.InternalError(err, w, r)
}
rows, err := stmt.Query(topic.ID, offset, common.Config.ItemsPerPage)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return common.LocalError("Bad Page. Some of the posts may have been deleted or you got here by directly typing in the page number.", w, r, user) return common.LocalError("Bad Page. Some of the posts may have been deleted or you got here by directly typing in the page number.", w, r, user)
} else if err != nil { } else if err != nil {
@ -165,14 +176,16 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
if common.Vhooks["topic_reply_row_assign"] != nil { if common.Vhooks["topic_reply_row_assign"] != nil {
common.RunVhook("topic_reply_row_assign", &tpage, &replyItem) common.RunVhook("topic_reply_row_assign", &tpage, &replyItem)
} }
replyList = append(replyList, replyItem) //replyList = append(replyList, replyItem)
// TODO: Use a pointer instead to make it easier to abstract this loop? What impact would this have on escape analysis?
tpage.ItemList = append(tpage.ItemList, replyItem)
} }
err = rows.Err() err = rows.Err()
if err != nil { if err != nil {
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
tpage.ItemList = replyList //tpage.ItemList = replyList
if common.PreRenderHooks["pre_render_view_topic"] != nil { if common.PreRenderHooks["pre_render_view_topic"] != nil {
if common.RunPreRenderHook("pre_render_view_topic", w, r, &user, &tpage) { if common.RunPreRenderHook("pre_render_view_topic", w, r, &user, &tpage) {
return nil return nil

60
routes/topic_list.go Normal file
View File

@ -0,0 +1,60 @@
package routes
import (
"log"
"net/http"
"strconv"
"../common"
)
func TopicList(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, ferr := common.UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
headerVars.Zone = "topics"
headerVars.MetaDesc = headerVars.Settings["meta_desc"].(string)
group, err := common.Groups.Get(user.Group)
if err != nil {
log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID)
return common.LocalError("Something weird happened", w, r, user)
}
// Get the current page
page, _ := strconv.Atoi(r.FormValue("page"))
// TODO: Pass a struct back rather than passing back so many variables
var topicList []*common.TopicsRow
var forumList []common.Forum
var pageList []int
var lastPage int
if user.IsSuperAdmin {
topicList, forumList, pageList, page, lastPage, err = common.TopicList.GetList(page)
} else {
topicList, forumList, pageList, page, lastPage, err = common.TopicList.GetListByGroup(group, page)
}
if err != nil {
return common.InternalError(err, w, r)
}
// ! Need an inline error not a page level error
//log.Printf("topicList: %+v\n", topicList)
//log.Printf("forumList: %+v\n", forumList)
if len(topicList) == 0 {
return common.NotFound(w, r)
}
pi := common.TopicsPage{common.GetTitlePhrase("topics"), user, headerVars, topicList, forumList, common.Config.DefaultForum, pageList, page, lastPage}
if common.PreRenderHooks["pre_render_topic_list"] != nil {
if common.RunPreRenderHook("pre_render_topic_list", w, r, &user, &pi) {
return nil
}
}
err = common.RunThemeTemplate(headerVars.Theme.Name, "topics", pi, w)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
}

View File

@ -3,5 +3,6 @@ CREATE TABLE [revisions] (
[content] nvarchar (MAX) not null, [content] nvarchar (MAX) not null,
[contentID] int not null, [contentID] int not null,
[contentType] nvarchar (100) DEFAULT 'replies' not null, [contentType] nvarchar (100) DEFAULT 'replies' not null,
[createdAt] datetime not null [createdAt] datetime not null,
primary key([reviseID])
); );

View File

@ -6,6 +6,7 @@ CREATE TABLE [users_groups] (
[is_mod] bit DEFAULT 0 not null, [is_mod] bit DEFAULT 0 not null,
[is_admin] bit DEFAULT 0 not null, [is_admin] bit DEFAULT 0 not null,
[is_banned] bit DEFAULT 0 not null, [is_banned] bit DEFAULT 0 not null,
[user_count] int DEFAULT 0 not null,
[tag] nvarchar (50) DEFAULT '' not null, [tag] nvarchar (50) DEFAULT '' not null,
primary key([gid]) primary key([gid])
); );

View File

@ -3,5 +3,6 @@ CREATE TABLE `revisions` (
`content` text not null, `content` text not null,
`contentID` int not null, `contentID` int not null,
`contentType` varchar(100) DEFAULT 'replies' not null, `contentType` varchar(100) DEFAULT 'replies' not null,
`createdAt` datetime not null `createdAt` datetime not null,
primary key(`reviseID`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -6,6 +6,7 @@ CREATE TABLE `users_groups` (
`is_mod` boolean DEFAULT 0 not null, `is_mod` boolean DEFAULT 0 not null,
`is_admin` boolean DEFAULT 0 not null, `is_admin` boolean DEFAULT 0 not null,
`is_banned` boolean DEFAULT 0 not null, `is_banned` boolean DEFAULT 0 not null,
`user_count` int DEFAULT 0 not null,
`tag` varchar(50) DEFAULT '' not null, `tag` varchar(50) DEFAULT '' not null,
primary key(`gid`) primary key(`gid`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -3,5 +3,6 @@ CREATE TABLE `revisions` (
`content` text not null, `content` text not null,
`contentID` int not null, `contentID` int not null,
`contentType` varchar (100) DEFAULT 'replies' not null, `contentType` varchar (100) DEFAULT 'replies' not null,
`createdAt` timestamp not null `createdAt` timestamp not null,
primary key(`reviseID`)
); );

View File

@ -6,6 +6,7 @@ CREATE TABLE `users_groups` (
`is_mod` boolean DEFAULT 0 not null, `is_mod` boolean DEFAULT 0 not null,
`is_admin` boolean DEFAULT 0 not null, `is_admin` boolean DEFAULT 0 not null,
`is_banned` boolean DEFAULT 0 not null, `is_banned` boolean DEFAULT 0 not null,
`user_count` int DEFAULT 0 not null,
`tag` varchar (50) DEFAULT '' not null, `tag` varchar (50) DEFAULT '' not null,
primary key(`gid`) primary key(`gid`)
); );

View File

@ -126,177 +126,181 @@ var topic_20 = []byte(`' type="text" />
var topic_21 = []byte(` var topic_21 = []byte(`
</div> </div>
</div> </div>
`)
var topic_22 = []byte(`
<article class="rowblock post_container poll"> <article class="rowblock post_container poll">
<div class="rowitem passive editable_parent post_item poll_item `) <div class="rowitem passive editable_parent post_item poll_item `)
var topic_22 = []byte(`" style="background-image: url(`) var topic_23 = []byte(`" style="background-image: url(`)
var topic_23 = []byte(`), url(/static/`) var topic_24 = []byte(`), url(/static/`)
var topic_24 = []byte(`/post-avatar-bg.jpg);background-position: 0px `) var topic_25 = []byte(`/post-avatar-bg.jpg);background-position: 0px `)
var topic_25 = []byte(`-1`) var topic_26 = []byte(`-1`)
var topic_26 = []byte(`0px;background-repeat:no-repeat, repeat-y;"> var topic_27 = []byte(`0px;background-repeat:no-repeat, repeat-y;">
<div class="topic_content user_content" style="margin:0;padding:0;"> <div class="topic_content user_content" style="margin:0;padding:0;">
`) `)
var topic_27 = []byte(` var topic_28 = []byte(`
<div class="poll_option"> <div class="poll_option">
<input form="poll_`) <input form="poll_`)
var topic_28 = []byte(`_form" id="poll_option_`) var topic_29 = []byte(`_form" id="poll_option_`)
var topic_29 = []byte(`" name="poll_option_input" type="checkbox" value="`) var topic_30 = []byte(`" name="poll_option_input" type="checkbox" value="`)
var topic_30 = []byte(`" /> var topic_31 = []byte(`" />
<label class="poll_option_label" for="poll_option_`) <label class="poll_option_label" for="poll_option_`)
var topic_31 = []byte(`"> var topic_32 = []byte(`">
<div class="sel"></div> <div class="sel"></div>
</label> </label>
<span id="poll_option_text_`) <span id="poll_option_text_`)
var topic_32 = []byte(`" class="poll_option_text">`) var topic_33 = []byte(`" class="poll_option_text">`)
var topic_33 = []byte(`</span> var topic_34 = []byte(`</span>
</div> </div>
`) `)
var topic_34 = []byte(` var topic_35 = []byte(`
<div class="poll_buttons"> <div class="poll_buttons">
<button form="poll_`) <button form="poll_`)
var topic_35 = []byte(`_form" class="poll_vote_button">Vote</button> var topic_36 = []byte(`_form" class="poll_vote_button">Vote</button>
<button class="poll_results_button" data-poll-id="`) <button class="poll_results_button" data-poll-id="`)
var topic_36 = []byte(`">Results</button> var topic_37 = []byte(`">Results</button>
<a href="#"><button class="poll_cancel_button">Cancel</button></a> <a href="#"><button class="poll_cancel_button">Cancel</button></a>
</div> </div>
</div> </div>
<div id="poll_results_`) <div id="poll_results_`)
var topic_37 = []byte(`" class="poll_results auto_hide"> var topic_38 = []byte(`" class="poll_results auto_hide">
<div class="topic_content user_content"></div> <div class="topic_content user_content"></div>
</div> </div>
</div> </div>
</article> </article>
`)
var topic_39 = []byte(`
<article itemscope itemtype="http://schema.org/CreativeWork" class="rowblock post_container top_post" aria-label="The opening post for this topic"> <article itemscope itemtype="http://schema.org/CreativeWork" class="rowblock post_container top_post" aria-label="The opening post for this topic">
<div class="rowitem passive editable_parent post_item `) <div class="rowitem passive editable_parent post_item `)
var topic_38 = []byte(`" style="background-image: url(`) var topic_40 = []byte(`" style="background-image: url(`)
var topic_39 = []byte(`), url(/static/`) var topic_41 = []byte(`), url(/static/`)
var topic_40 = []byte(`/post-avatar-bg.jpg);background-position: 0px `) var topic_42 = []byte(`/post-avatar-bg.jpg);background-position: 0px `)
var topic_41 = []byte(`-1`) var topic_43 = []byte(`-1`)
var topic_42 = []byte(`0px;background-repeat:no-repeat, repeat-y;"> var topic_44 = []byte(`0px;background-repeat:no-repeat, repeat-y;">
<p class="hide_on_edit topic_content user_content" itemprop="text" style="margin:0;padding:0;">`) <p class="hide_on_edit topic_content user_content" itemprop="text" style="margin:0;padding:0;">`)
var topic_43 = []byte(`</p> var topic_45 = []byte(`</p>
<textarea name="topic_content" class="show_on_edit topic_content_input">`) <textarea name="topic_content" class="show_on_edit topic_content_input">`)
var topic_44 = []byte(`</textarea> var topic_46 = []byte(`</textarea>
<span class="controls" aria-label="Controls and Author Information"> <span class="controls" aria-label="Controls and Author Information">
<a href="`) <a href="`)
var topic_45 = []byte(`" class="username real_username" rel="author">`) var topic_47 = []byte(`" class="username real_username" rel="author">`)
var topic_46 = []byte(`</a>&nbsp;&nbsp; var topic_48 = []byte(`</a>&nbsp;&nbsp;
`) `)
var topic_47 = []byte(`<a href="/topic/like/submit/`) var topic_49 = []byte(`<a href="/topic/like/submit/`)
var topic_48 = []byte(`?session=`) var topic_50 = []byte(`?session=`)
var topic_49 = []byte(`" class="mod_button" title="Love it" `) var topic_51 = []byte(`" class="mod_button" title="Love it" `)
var topic_50 = []byte(`aria-label="Unlike this topic"`) var topic_52 = []byte(`aria-label="Unlike this topic"`)
var topic_51 = []byte(`aria-label="Like this topic"`) var topic_53 = []byte(`aria-label="Like this topic"`)
var topic_52 = []byte(` style="color:#202020;"> var topic_54 = []byte(` style="color:#202020;">
<button class="username like_label"`) <button class="username like_label"`)
var topic_53 = []byte(` style="background-color:#D6FFD6;"`) var topic_55 = []byte(` style="background-color:#D6FFD6;"`)
var topic_54 = []byte(`></button></a>`) var topic_56 = []byte(`></button></a>`)
var topic_55 = []byte(`<a href='/topic/edit/`) var topic_57 = []byte(`<a href='/topic/edit/`)
var topic_56 = []byte(`' class="mod_button open_edit" style="font-weight:normal;" title="Edit Topic" aria-label="Edit this topic"><button class="username edit_label"></button></a>`) var topic_58 = []byte(`' class="mod_button open_edit" style="font-weight:normal;" title="Edit Topic" aria-label="Edit this topic"><button class="username edit_label"></button></a>`)
var topic_57 = []byte(`<a href='/topic/delete/submit/`) var topic_59 = []byte(`<a href='/topic/delete/submit/`)
var topic_58 = []byte(`?session=`) var topic_60 = []byte(`?session=`)
var topic_59 = []byte(`' class="mod_button" style="font-weight:normal;" title="Delete Topic" aria-label="Delete this topic"><button class="username trash_label"></button></a>`) var topic_61 = []byte(`' class="mod_button" style="font-weight:normal;" title="Delete Topic" aria-label="Delete this topic"><button class="username trash_label"></button></a>`)
var topic_60 = []byte(`<a class="mod_button" href='/topic/unlock/submit/`) var topic_62 = []byte(`<a class="mod_button" href='/topic/unlock/submit/`)
var topic_61 = []byte(`?session=`) var topic_63 = []byte(`?session=`)
var topic_62 = []byte(`' style="font-weight:normal;" title="Unlock Topic" aria-label="Unlock this topic"><button class="username unlock_label"></button></a>`) var topic_64 = []byte(`' style="font-weight:normal;" title="Unlock Topic" aria-label="Unlock this topic"><button class="username unlock_label"></button></a>`)
var topic_63 = []byte(`<a href='/topic/lock/submit/`) var topic_65 = []byte(`<a href='/topic/lock/submit/`)
var topic_64 = []byte(`?session=`) var topic_66 = []byte(`?session=`)
var topic_65 = []byte(`' class="mod_button" style="font-weight:normal;" title="Lock Topic" aria-label="Lock this topic"><button class="username lock_label"></button></a>`) var topic_67 = []byte(`' class="mod_button" style="font-weight:normal;" title="Lock Topic" aria-label="Lock this topic"><button class="username lock_label"></button></a>`)
var topic_66 = []byte(`<a class="mod_button" href='/topic/unstick/submit/`) var topic_68 = []byte(`<a class="mod_button" href='/topic/unstick/submit/`)
var topic_67 = []byte(`?session=`) var topic_69 = []byte(`?session=`)
var topic_68 = []byte(`' style="font-weight:normal;" title="Unpin Topic" aria-label="Unpin this topic"><button class="username unpin_label"></button></a>`) var topic_70 = []byte(`' style="font-weight:normal;" title="Unpin Topic" aria-label="Unpin this topic"><button class="username unpin_label"></button></a>`)
var topic_69 = []byte(`<a href='/topic/stick/submit/`) var topic_71 = []byte(`<a href='/topic/stick/submit/`)
var topic_70 = []byte(`?session=`) var topic_72 = []byte(`?session=`)
var topic_71 = []byte(`' class="mod_button" style="font-weight:normal;" title="Pin Topic" aria-label="Pin this topic"><button class="username pin_label"></button></a>`) var topic_73 = []byte(`' class="mod_button" style="font-weight:normal;" title="Pin Topic" aria-label="Pin this topic"><button class="username pin_label"></button></a>`)
var topic_72 = []byte(`<a class="mod_button" href='/users/ips/?ip=`) var topic_74 = []byte(`<a class="mod_button" href='/users/ips/?ip=`)
var topic_73 = []byte(`' style="font-weight:normal;" title="View IP" aria-label="The poster's IP is `) var topic_75 = []byte(`' style="font-weight:normal;" title="View IP" aria-label="The poster's IP is `)
var topic_74 = []byte(`"><button class="username ip_label"></button></a>`) var topic_76 = []byte(`"><button class="username ip_label"></button></a>`)
var topic_75 = []byte(` var topic_77 = []byte(`
<a href="/report/submit/`) <a href="/report/submit/`)
var topic_76 = []byte(`?session=`) var topic_78 = []byte(`?session=`)
var topic_77 = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag this topic" aria-label="Flag this topic" rel="nofollow"><button class="username flag_label"></button></a> var topic_79 = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag this topic" aria-label="Flag this topic" rel="nofollow"><button class="username flag_label"></button></a>
`) `)
var topic_78 = []byte(`<a class="username hide_on_micro like_count" aria-label="The number of likes on this topic">`) var topic_80 = []byte(`<a class="username hide_on_micro like_count" aria-label="The number of likes on this topic">`)
var topic_79 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`) var topic_81 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
var topic_80 = []byte(`<a class="username hide_on_micro user_tag">`) var topic_82 = []byte(`<a class="username hide_on_micro user_tag">`)
var topic_81 = []byte(`</a>`) var topic_83 = []byte(`</a>`)
var topic_82 = []byte(`<a class="username hide_on_micro level" aria-label="The poster's level">`) var topic_84 = []byte(`<a class="username hide_on_micro level" aria-label="The poster's level">`)
var topic_83 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`) var topic_85 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
var topic_84 = []byte(` var topic_86 = []byte(`
</span> </span>
</div> </div>
</article> </article>
<div class="rowblock post_container" aria-label="The current page for this topic" style="overflow: hidden;">`) <div class="rowblock post_container" aria-label="The current page for this topic" style="overflow: hidden;">`)
var topic_85 = []byte(` var topic_87 = []byte(`
<article itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item action_item"> <article itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item action_item">
<span class="action_icon" style="font-size: 18px;padding-right: 5px;">`) <span class="action_icon" style="font-size: 18px;padding-right: 5px;">`)
var topic_86 = []byte(`</span> var topic_88 = []byte(`</span>
<span itemprop="text">`) <span itemprop="text">`)
var topic_87 = []byte(`</span> var topic_89 = []byte(`</span>
</article> </article>
`) `)
var topic_88 = []byte(` var topic_90 = []byte(`
<article itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item `) <article itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item `)
var topic_89 = []byte(`" style="background-image: url(`) var topic_91 = []byte(`" style="background-image: url(`)
var topic_90 = []byte(`), url(/static/`) var topic_92 = []byte(`), url(/static/`)
var topic_91 = []byte(`/post-avatar-bg.jpg);background-position: 0px `) var topic_93 = []byte(`/post-avatar-bg.jpg);background-position: 0px `)
var topic_92 = []byte(`-1`) var topic_94 = []byte(`-1`)
var topic_93 = []byte(`0px;background-repeat:no-repeat, repeat-y;"> var topic_95 = []byte(`0px;background-repeat:no-repeat, repeat-y;">
`) `)
var topic_94 = []byte(` var topic_96 = []byte(`
<p class="editable_block user_content" itemprop="text" style="margin:0;padding:0;">`) <p class="editable_block user_content" itemprop="text" style="margin:0;padding:0;">`)
var topic_95 = []byte(`</p> var topic_97 = []byte(`</p>
<span class="controls"> <span class="controls">
<a href="`) <a href="`)
var topic_96 = []byte(`" class="username real_username" rel="author">`) var topic_98 = []byte(`" class="username real_username" rel="author">`)
var topic_97 = []byte(`</a>&nbsp;&nbsp; var topic_99 = []byte(`</a>&nbsp;&nbsp;
`) `)
var topic_98 = []byte(`<a href="/reply/like/submit/`) var topic_100 = []byte(`<a href="/reply/like/submit/`)
var topic_99 = []byte(`?session=`) var topic_101 = []byte(`?session=`)
var topic_100 = []byte(`" class="mod_button" title="Love it" style="color:#202020;"><button class="username like_label"`) var topic_102 = []byte(`" class="mod_button" title="Love it" style="color:#202020;"><button class="username like_label"`)
var topic_101 = []byte(` style="background-color:#D6FFD6;"`) var topic_103 = []byte(` style="background-color:#D6FFD6;"`)
var topic_102 = []byte(`></button></a>`) var topic_104 = []byte(`></button></a>`)
var topic_103 = []byte(`<a href="/reply/edit/submit/`) var topic_105 = []byte(`<a href="/reply/edit/submit/`)
var topic_104 = []byte(`?session=`) var topic_106 = []byte(`?session=`)
var topic_105 = []byte(`" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>`) var topic_107 = []byte(`" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>`)
var topic_106 = []byte(`<a href="/reply/delete/submit/`) var topic_108 = []byte(`<a href="/reply/delete/submit/`)
var topic_107 = []byte(`?session=`) var topic_109 = []byte(`?session=`)
var topic_108 = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>`) var topic_110 = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>`)
var topic_109 = []byte(`<a class="mod_button" href='/users/ips/?ip=`) var topic_111 = []byte(`<a class="mod_button" href='/users/ips/?ip=`)
var topic_110 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`) var topic_112 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`)
var topic_111 = []byte(` var topic_113 = []byte(`
<a href="/report/submit/`) <a href="/report/submit/`)
var topic_112 = []byte(`?session=`) var topic_114 = []byte(`?session=`)
var topic_113 = []byte(`&type=reply" class="mod_button report_item" title="Flag this reply" aria-label="Flag this reply" rel="nofollow"><button class="username report_item flag_label"></button></a> var topic_115 = []byte(`&type=reply" class="mod_button report_item" title="Flag this reply" aria-label="Flag this reply" rel="nofollow"><button class="username report_item flag_label"></button></a>
`) `)
var topic_114 = []byte(`<a class="username hide_on_micro like_count">`) var topic_116 = []byte(`<a class="username hide_on_micro like_count">`)
var topic_115 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`) var topic_117 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
var topic_116 = []byte(`<a class="username hide_on_micro user_tag">`) var topic_118 = []byte(`<a class="username hide_on_micro user_tag">`)
var topic_117 = []byte(`</a>`) var topic_119 = []byte(`</a>`)
var topic_118 = []byte(`<a class="username hide_on_micro level">`) var topic_120 = []byte(`<a class="username hide_on_micro level">`)
var topic_119 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`) var topic_121 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
var topic_120 = []byte(` var topic_122 = []byte(`
</span> </span>
</article> </article>
`) `)
var topic_121 = []byte(`</div> var topic_123 = []byte(`</div>
`) `)
var topic_122 = []byte(` var topic_124 = []byte(`
<div class="rowblock topic_reply_form quick_create_form"> <div class="rowblock topic_reply_form quick_create_form">
<form id="quick_post_form" enctype="multipart/form-data" action="/reply/create/?session=`) <form id="quick_post_form" enctype="multipart/form-data" action="/reply/create/?session=`)
var topic_123 = []byte(`" method="post"></form> var topic_125 = []byte(`" method="post"></form>
<input form="quick_post_form" name="tid" value='`) <input form="quick_post_form" name="tid" value='`)
var topic_124 = []byte(`' type="hidden" /> var topic_126 = []byte(`' type="hidden" />
<input form="quick_post_form" id="has_poll_input" name="has_poll" value="0" type="hidden" /> <input form="quick_post_form" id="has_poll_input" name="has_poll" value="0" type="hidden" />
<div class="formrow real_first_child"> <div class="formrow real_first_child">
<div class="formitem"> <div class="formitem">
@ -317,16 +321,16 @@ var topic_124 = []byte(`' type="hidden" />
<button form="quick_post_form" name="reply-button" class="formbutton">Create Reply</button> <button form="quick_post_form" name="reply-button" class="formbutton">Create Reply</button>
<button form="quick_post_form" class="formbutton" id="add_poll_button">Add Poll</button> <button form="quick_post_form" class="formbutton" id="add_poll_button">Add Poll</button>
`) `)
var topic_125 = []byte(` var topic_127 = []byte(`
<input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" style="display: none;" /> <input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label> <label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`) <div id="upload_file_dock"></div>`)
var topic_126 = []byte(` var topic_128 = []byte(`
</div> </div>
</div> </div>
</div> </div>
`) `)
var topic_127 = []byte(` var topic_129 = []byte(`
</main> </main>

View File

@ -127,22 +127,22 @@ w.Write([]byte(tmpl_topic_vars.Topic.Title))
w.Write(topic_20) w.Write(topic_20)
} }
w.Write(topic_21) w.Write(topic_21)
w.Write([]byte(tmpl_topic_vars.Topic.ClassName)) if tmpl_topic_vars.Poll.ID > 0 {
w.Write(topic_22) w.Write(topic_22)
w.Write([]byte(tmpl_topic_vars.Topic.Avatar)) w.Write([]byte(tmpl_topic_vars.Topic.ClassName))
w.Write(topic_23) w.Write(topic_23)
w.Write([]byte(tmpl_topic_vars.Header.Theme.Name)) w.Write([]byte(tmpl_topic_vars.Topic.Avatar))
w.Write(topic_24) w.Write(topic_24)
if tmpl_topic_vars.Topic.ContentLines <= 5 { w.Write([]byte(tmpl_topic_vars.Header.Theme.Name))
w.Write(topic_25) w.Write(topic_25)
} if tmpl_topic_vars.Topic.ContentLines <= 5 {
w.Write(topic_26) w.Write(topic_26)
}
w.Write(topic_27)
if len(tmpl_topic_vars.Poll.QuickOptions) != 0 { if len(tmpl_topic_vars.Poll.QuickOptions) != 0 {
for _, item := range tmpl_topic_vars.Poll.QuickOptions { for _, item := range tmpl_topic_vars.Poll.QuickOptions {
w.Write(topic_27)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID)))
w.Write(topic_28) w.Write(topic_28)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID)))
w.Write(topic_29) w.Write(topic_29)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_30) w.Write(topic_30)
@ -150,214 +150,218 @@ w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_31) w.Write(topic_31)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_32) w.Write(topic_32)
w.Write([]byte(item.Value)) w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_33) w.Write(topic_33)
} w.Write([]byte(item.Value))
}
w.Write(topic_34) w.Write(topic_34)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID))) }
}
w.Write(topic_35) w.Write(topic_35)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID))) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID)))
w.Write(topic_36) w.Write(topic_36)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID))) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID)))
w.Write(topic_37) w.Write(topic_37)
w.Write([]byte(tmpl_topic_vars.Topic.ClassName)) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Poll.ID)))
w.Write(topic_38) w.Write(topic_38)
w.Write([]byte(tmpl_topic_vars.Topic.Avatar)) }
w.Write(topic_39) w.Write(topic_39)
w.Write([]byte(tmpl_topic_vars.Header.Theme.Name)) w.Write([]byte(tmpl_topic_vars.Topic.ClassName))
w.Write(topic_40) w.Write(topic_40)
if tmpl_topic_vars.Topic.ContentLines <= 5 { w.Write([]byte(tmpl_topic_vars.Topic.Avatar))
w.Write(topic_41) w.Write(topic_41)
} w.Write([]byte(tmpl_topic_vars.Header.Theme.Name))
w.Write(topic_42) w.Write(topic_42)
w.Write([]byte(tmpl_topic_vars.Topic.ContentHTML)) if tmpl_topic_vars.Topic.ContentLines <= 5 {
w.Write(topic_43) w.Write(topic_43)
w.Write([]byte(tmpl_topic_vars.Topic.Content))
w.Write(topic_44)
w.Write([]byte(tmpl_topic_vars.Topic.UserLink))
w.Write(topic_45)
w.Write([]byte(tmpl_topic_vars.Topic.CreatedByName))
w.Write(topic_46)
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
w.Write(topic_47)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_48)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_49)
if tmpl_topic_vars.Topic.Liked {
w.Write(topic_50)
} else {
w.Write(topic_51)
} }
w.Write(topic_52) w.Write(topic_44)
w.Write([]byte(tmpl_topic_vars.Topic.ContentHTML))
w.Write(topic_45)
w.Write([]byte(tmpl_topic_vars.Topic.Content))
w.Write(topic_46)
w.Write([]byte(tmpl_topic_vars.Topic.UserLink))
w.Write(topic_47)
w.Write([]byte(tmpl_topic_vars.Topic.CreatedByName))
w.Write(topic_48)
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
w.Write(topic_49)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_50)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_51)
if tmpl_topic_vars.Topic.Liked { if tmpl_topic_vars.Topic.Liked {
w.Write(topic_52)
} else {
w.Write(topic_53) w.Write(topic_53)
} }
w.Write(topic_54) w.Write(topic_54)
} if tmpl_topic_vars.Topic.Liked {
if tmpl_topic_vars.CurrentUser.Perms.EditTopic {
w.Write(topic_55) w.Write(topic_55)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) }
w.Write(topic_56) w.Write(topic_56)
} }
if tmpl_topic_vars.CurrentUser.Perms.DeleteTopic { if tmpl_topic_vars.CurrentUser.Perms.EditTopic {
w.Write(topic_57) w.Write(topic_57)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_58) w.Write(topic_58)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) }
if tmpl_topic_vars.CurrentUser.Perms.DeleteTopic {
w.Write(topic_59) w.Write(topic_59)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_60)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_61)
} }
if tmpl_topic_vars.CurrentUser.Perms.CloseTopic { if tmpl_topic_vars.CurrentUser.Perms.CloseTopic {
if tmpl_topic_vars.Topic.IsClosed { if tmpl_topic_vars.Topic.IsClosed {
w.Write(topic_60)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_61)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_62) w.Write(topic_62)
} else {
w.Write(topic_63)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_64) w.Write(topic_63)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_64)
} else {
w.Write(topic_65) w.Write(topic_65)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_66)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_67)
} }
} }
if tmpl_topic_vars.CurrentUser.Perms.PinTopic { if tmpl_topic_vars.CurrentUser.Perms.PinTopic {
if tmpl_topic_vars.Topic.Sticky { if tmpl_topic_vars.Topic.Sticky {
w.Write(topic_66)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_67)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_68) w.Write(topic_68)
} else {
w.Write(topic_69)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_70) w.Write(topic_69)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_70)
} else {
w.Write(topic_71) w.Write(topic_71)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_72)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_73)
} }
} }
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs { if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_72)
w.Write([]byte(tmpl_topic_vars.Topic.IPAddress))
w.Write(topic_73)
w.Write([]byte(tmpl_topic_vars.Topic.IPAddress))
w.Write(topic_74) w.Write(topic_74)
} w.Write([]byte(tmpl_topic_vars.Topic.IPAddress))
w.Write(topic_75) w.Write(topic_75)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) w.Write([]byte(tmpl_topic_vars.Topic.IPAddress))
w.Write(topic_76) w.Write(topic_76)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) }
w.Write(topic_77) w.Write(topic_77)
if tmpl_topic_vars.Topic.LikeCount > 0 { w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_78) w.Write(topic_78)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.LikeCount))) w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_79) w.Write(topic_79)
if tmpl_topic_vars.Topic.LikeCount > 0 {
w.Write(topic_80)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.LikeCount)))
w.Write(topic_81)
} }
if tmpl_topic_vars.Topic.Tag != "" { if tmpl_topic_vars.Topic.Tag != "" {
w.Write(topic_80)
w.Write([]byte(tmpl_topic_vars.Topic.Tag))
w.Write(topic_81)
} else {
w.Write(topic_82) w.Write(topic_82)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.Level))) w.Write([]byte(tmpl_topic_vars.Topic.Tag))
w.Write(topic_83) w.Write(topic_83)
} } else {
w.Write(topic_84) w.Write(topic_84)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.Level)))
w.Write(topic_85)
}
w.Write(topic_86)
if len(tmpl_topic_vars.ItemList) != 0 { if len(tmpl_topic_vars.ItemList) != 0 {
for _, item := range tmpl_topic_vars.ItemList { for _, item := range tmpl_topic_vars.ItemList {
if item.ActionType != "" { if item.ActionType != "" {
w.Write(topic_85)
w.Write([]byte(item.ActionIcon))
w.Write(topic_86)
w.Write([]byte(item.ActionType))
w.Write(topic_87) w.Write(topic_87)
} else { w.Write([]byte(item.ActionIcon))
w.Write(topic_88) w.Write(topic_88)
w.Write([]byte(item.ClassName)) w.Write([]byte(item.ActionType))
w.Write(topic_89) w.Write(topic_89)
w.Write([]byte(item.Avatar)) } else {
w.Write(topic_90) w.Write(topic_90)
w.Write([]byte(tmpl_topic_vars.Header.Theme.Name)) w.Write([]byte(item.ClassName))
w.Write(topic_91) w.Write(topic_91)
if item.ContentLines <= 5 { w.Write([]byte(item.Avatar))
w.Write(topic_92) w.Write(topic_92)
} w.Write([]byte(tmpl_topic_vars.Header.Theme.Name))
w.Write(topic_93) w.Write(topic_93)
if item.ContentLines <= 5 {
w.Write(topic_94) w.Write(topic_94)
w.Write([]byte(item.ContentHtml))
w.Write(topic_95)
w.Write([]byte(item.UserLink))
w.Write(topic_96)
w.Write([]byte(item.CreatedByName))
w.Write(topic_97)
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
w.Write(topic_98)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_99)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_100)
if item.Liked {
w.Write(topic_101)
} }
w.Write(topic_95)
w.Write(topic_96)
w.Write([]byte(item.ContentHtml))
w.Write(topic_97)
w.Write([]byte(item.UserLink))
w.Write(topic_98)
w.Write([]byte(item.CreatedByName))
w.Write(topic_99)
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
w.Write(topic_100)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_101)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_102) w.Write(topic_102)
if item.Liked {
w.Write(topic_103)
}
w.Write(topic_104)
} }
if tmpl_topic_vars.CurrentUser.Perms.EditReply { if tmpl_topic_vars.CurrentUser.Perms.EditReply {
w.Write(topic_103)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_104)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_105) w.Write(topic_105)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_106)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_107)
} }
if tmpl_topic_vars.CurrentUser.Perms.DeleteReply { if tmpl_topic_vars.CurrentUser.Perms.DeleteReply {
w.Write(topic_106)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_107)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_108) w.Write(topic_108)
} w.Write([]byte(strconv.Itoa(item.ID)))
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_109) w.Write(topic_109)
w.Write([]byte(item.IPAddress)) w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_110) w.Write(topic_110)
} }
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_111) w.Write(topic_111)
w.Write([]byte(strconv.Itoa(item.ID))) w.Write([]byte(item.IPAddress))
w.Write(topic_112) w.Write(topic_112)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) }
w.Write(topic_113) w.Write(topic_113)
if item.LikeCount > 0 { w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_114) w.Write(topic_114)
w.Write([]byte(strconv.Itoa(item.LikeCount))) w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_115) w.Write(topic_115)
if item.LikeCount > 0 {
w.Write(topic_116)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topic_117)
} }
if item.Tag != "" { if item.Tag != "" {
w.Write(topic_116)
w.Write([]byte(item.Tag))
w.Write(topic_117)
} else {
w.Write(topic_118) w.Write(topic_118)
w.Write([]byte(strconv.Itoa(item.Level))) w.Write([]byte(item.Tag))
w.Write(topic_119) w.Write(topic_119)
} } else {
w.Write(topic_120) w.Write(topic_120)
} w.Write([]byte(strconv.Itoa(item.Level)))
}
}
w.Write(topic_121) w.Write(topic_121)
if tmpl_topic_vars.CurrentUser.Perms.CreateReply { }
w.Write(topic_122) w.Write(topic_122)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session)) }
}
}
w.Write(topic_123) w.Write(topic_123)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) if tmpl_topic_vars.CurrentUser.Perms.CreateReply {
w.Write(topic_124) w.Write(topic_124)
if tmpl_topic_vars.CurrentUser.Perms.UploadFiles { w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(topic_125) w.Write(topic_125)
} w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_126) w.Write(topic_126)
} if tmpl_topic_vars.CurrentUser.Perms.UploadFiles {
w.Write(topic_127) w.Write(topic_127)
}
w.Write(topic_128)
}
w.Write(topic_129)
w.Write(footer_0) w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_topic_vars.Header))) w.Write([]byte(common.BuildWidget("footer",tmpl_topic_vars.Header)))
w.Write(footer_1) w.Write(footer_1)

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/agent/{{.Agent}}" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/agent/{{.Agent}}" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/agents/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/agents/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/posts/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/posts/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrer/{{.Agent}}" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrer/{{.Agent}}" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrers/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/referrers/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/route/{{.Route}}" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/route/{{.Route}}" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/routes/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/routes/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/system/{{.Agent}}" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/system/{{.Agent}}" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/systems/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/systems/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/topics/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/topics/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }} {{template "panel-menu.html" . }}
<main id="panel_dashboard_right" class="colstack_right"> <main id="panel_analytics_right" class="colstack_right">
<form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/views/" method="get"> <form id="timeRangeForm" name="timeRangeForm" action="/panel/analytics/views/" method="get">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"> <div class="rowitem">

View File

@ -21,6 +21,7 @@
{{end}} {{end}}
</div> </div>
</div> </div>
{{if .Poll.ID}}
<article class="rowblock post_container poll"> <article class="rowblock post_container poll">
<div class="rowitem passive editable_parent post_item poll_item {{.Topic.ClassName}}" style="background-image: url({{.Topic.Avatar}}), url(/static/{{.Header.Theme.Name}}/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;"> <div class="rowitem passive editable_parent post_item poll_item {{.Topic.ClassName}}" style="background-image: url({{.Topic.Avatar}}), url(/static/{{.Header.Theme.Name}}/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;">
<div class="topic_content user_content" style="margin:0;padding:0;"> <div class="topic_content user_content" style="margin:0;padding:0;">
@ -44,6 +45,7 @@
</div> </div>
</div> </div>
</article> </article>
{{end}}
<article itemscope itemtype="http://schema.org/CreativeWork" class="rowblock post_container top_post" aria-label="The opening post for this topic"> <article itemscope itemtype="http://schema.org/CreativeWork" class="rowblock post_container top_post" aria-label="The opening post for this topic">
<div class="rowitem passive editable_parent post_item {{.Topic.ClassName}}" style="background-image: url({{.Topic.Avatar}}), url(/static/{{.Header.Theme.Name}}/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;"> <div class="rowitem passive editable_parent post_item {{.Topic.ClassName}}" style="background-image: url({{.Topic.Avatar}}), url(/static/{{.Header.Theme.Name}}/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;">

View File

@ -917,6 +917,9 @@ textarea {
margin-top: 0px; margin-top: 0px;
} }
.postImage {
width: 100%;
}
.post_item { .post_item {
display: flex; display: flex;
margin-bottom: 16px; margin-bottom: 16px;

View File

@ -1,3 +1,16 @@
.submenu a {
margin-left: 4px;
}
.colstack_head .rowitem {
display: flex;
}
.colstack_head .rowitem h1, .colstack_head .rowitem a {
margin-right: auto;
}
.colstack_head .rowitem a h1 {
margin-right: 0px;
}
.rowlist .tag-mini { .rowlist .tag-mini {
font-size: 10px; font-size: 10px;
margin-left: 2px; margin-left: 2px;