Added better pagination to /forum/{id}
Fixed the ordering of the elements in the pagesets.
Added hooks to the task system.
Reduced the amount of boilerplate for RunVhook and RunVhookNoReturn.
Added RunVhookNeedHook to allow plugin developers to add custom content types to the report system.
Added BulkGetMap to the DefaultPollStore.
Renamed the MultiServer config setting to ServerCount and made it an integer.
Don't cache topic lists for empty groups.
Don't load replies for topics without any.

Tempra Simple:
Added chart and submenu CSS.
Improved the input and select CSS.
Fixed the pageset CSS.
Fixed the poll input CSS.

Tempra Conflux:
Fixed the quick reply button CSS.
Hid the avatar in the quick reply area.
Added the poll CSS.
Fixed the pageset CSS.

Shadow:
Fixed the time range selector CSS.
Fixed the poll input CSS.
This commit is contained in:
Azareal 2018-02-15 13:15:27 +00:00
parent fa065bc584
commit be47066770
24 changed files with 548 additions and 252 deletions

View File

@ -42,6 +42,15 @@ var VhookSkippable = map[string]func(...interface{}) (bool, RouteError){
//var vhookErrorable = map[string]func(...interface{}) (interface{}, RouteError){}
var taskHooks = map[string][]func() error{
"before_half_second_tick": nil,
"after_half_second_tick": nil,
"before_second_tick": nil,
"after_second_tick": nil,
"before_fifteen_minute_tick": nil,
"after_fifteen_minute_tick": nil,
}
// Coming Soon:
type Message interface {
ID() int
@ -225,6 +234,7 @@ func NewPlugin(uname string, name string, author string, url string, settings st
}
// ? - Is this racey?
// TODO: Generate the cases in this switch
func (plugin *Plugin) AddHook(name string, handler interface{}) {
switch h := handler.(type) {
case func(interface{}) interface{}:
@ -254,6 +264,15 @@ func (plugin *Plugin) AddHook(name string, handler interface{}) {
PreRenderHooks[name] = append(PreRenderHooks[name], h)
}
plugin.Hooks[name] = len(PreRenderHooks[name])
case func() error: // ! We might want a more generic name, as we might use this signature for things other than tasks hooks
if len(taskHooks[name]) == 0 {
var hookSlice []func() error
hookSlice = append(hookSlice, h)
taskHooks[name] = hookSlice
} else {
taskHooks[name] = append(taskHooks[name], h)
}
plugin.Hooks[name] = len(taskHooks[name])
case func(...interface{}) interface{}:
Vhooks[name] = h
plugin.Hooks[name] = 0
@ -266,6 +285,7 @@ func (plugin *Plugin) AddHook(name string, handler interface{}) {
}
// ? - Is this racey?
// TODO: Generate the cases in this switch
func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
switch handler.(type) {
case func(interface{}) interface{}:
@ -295,6 +315,15 @@ func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
hook = append(hook[:key], hook[key+1:]...)
}
PreRenderHooks[name] = hook
case func() error:
key := plugin.Hooks[name]
hook := taskHooks[name]
if len(hook) == 1 {
hook = []func() error{}
} else {
hook = append(hook[:key], hook[key+1:]...)
}
taskHooks[name] = hook
case func(...interface{}) interface{}:
delete(Vhooks, name)
case func(...interface{}) (bool, RouteError):
@ -340,7 +369,11 @@ func RunHookNoreturn(name string, data interface{}) {
}
func RunVhook(name string, data ...interface{}) interface{} {
return Vhooks[name](data...)
hook := Vhooks[name]
if hook != nil {
return hook(data...)
}
return nil
}
func RunVhookSkippable(name string, data ...interface{}) (bool, RouteError) {
@ -348,7 +381,29 @@ func RunVhookSkippable(name string, data ...interface{}) (bool, RouteError) {
}
func RunVhookNoreturn(name string, data ...interface{}) {
_ = Vhooks[name](data...)
hook := Vhooks[name]
if hook != nil {
_ = hook(data...)
}
}
// TODO: Find a better way of doing this
func RunVhookNeedHook(name string, data ...interface{}) (ret interface{}, hasHook bool) {
hook := Vhooks[name]
if hook != nil {
return hook(data...), true
}
return nil, false
}
func RunTaskHook(name string) error {
for _, hook := range taskHooks[name] {
err := hook()
if err != nil {
return err
}
}
return nil
}
// Trying to get a teeny bit of type-safety where-ever possible, especially for such a critical set of hooks
@ -360,14 +415,14 @@ func RunSshook(name string, data string) string {
}
func RunPreRenderHook(name string, w http.ResponseWriter, r *http.Request, user *User, data interface{}) (halt bool) {
// This hook runs on ALL pre_render hooks
// This hook runs on ALL PreRender hooks
for _, hook := range PreRenderHooks["pre_render"] {
if hook(w, r, user, data) {
return true
}
}
// The actual pre_render hook
// The actual PreRender hook
for _, hook := range PreRenderHooks[name] {
if hook(w, r, user, data) {
return true

View File

@ -232,9 +232,7 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod
var blankIntList []int
var pluginPerms = make(map[string]bool)
var pluginPermsBytes = []byte("{}")
if Vhooks["create_group_preappend"] != nil {
RunVhook("create_group_preappend", &pluginPerms, &pluginPermsBytes)
}
RunVhook("create_group_preappend", &pluginPerms, &pluginPermsBytes)
// Generate the forum permissions based on the presets...
fdata, err := Forums.GetAll()

View File

@ -79,6 +79,7 @@ type ForumPage struct {
Header *HeaderVars
ItemList []*TopicsRow
Forum *Forum
PageList []int
Page int
LastPage int
}
@ -218,15 +219,15 @@ type PanelAnalyticsRoutePage struct {
}
type PanelAnalyticsAgentPage struct {
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
Zone string
Agent string
Title string
CurrentUser User
Header *HeaderVars
Stats PanelStats
Zone string
Agent string
FriendlyAgent string
PrimaryGraph PanelTimeGraph
TimeRange string
PrimaryGraph PanelTimeGraph
TimeRange string
}
type PanelThemesPage struct {

View File

@ -3,6 +3,9 @@ package common
import (
"database/sql"
"encoding/json"
"errors"
"log"
"strconv"
"../query_gen/lib"
)
@ -116,6 +119,92 @@ func (store *DefaultPollStore) Get(id int) (*Poll, error) {
return poll, err
}
// TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts?
// TODO: ID of 0 should always error?
func (store *DefaultPollStore) BulkGetMap(ids []int) (list map[int]*Poll, err error) {
var idCount = len(ids)
list = make(map[int]*Poll)
if idCount == 0 {
return list, nil
}
var stillHere []int
sliceList := store.cache.BulkGet(ids)
for i, sliceItem := range sliceList {
if sliceItem != nil {
list[sliceItem.ID] = sliceItem
} else {
stillHere = append(stillHere, ids[i])
}
}
ids = stillHere
// If every user is in the cache, then return immediately
if len(ids) == 0 {
return list, nil
}
// TODO: Add a function for the qlist stuff
var qlist string
var pollIDList []interface{}
for _, id := range ids {
pollIDList = append(pollIDList, strconv.Itoa(id))
qlist += "?,"
}
qlist = qlist[0 : len(qlist)-1]
acc := qgen.Builder.Accumulator()
rows, err := acc.Select("polls").Columns("pollID, parentID, parentTable, type, options, votes").Where("pollID IN(" + qlist + ")").Query(pollIDList...)
if err != nil {
return list, err
}
for rows.Next() {
poll := &Poll{ID: 0}
var optionTxt []byte
err := rows.Scan(&poll.ID, &poll.ParentID, &poll.ParentTable, &poll.Type, &optionTxt, &poll.VoteCount)
if err != nil {
return list, err
}
err = json.Unmarshal(optionTxt, &poll.Options)
if err != nil {
return list, err
}
poll.QuickOptions = store.unpackOptionsMap(poll.Options)
store.cache.Set(poll)
list[poll.ID] = poll
}
// Did we miss any polls?
if idCount > len(list) {
var sidList string
for _, id := range ids {
_, ok := list[id]
if !ok {
sidList += strconv.Itoa(id) + ","
}
}
// We probably don't need this, but it might be useful in case of bugs in BulkCascadeGetMap
if sidList == "" {
if Dev.DebugMode {
log.Print("This data is sampled later in the BulkCascadeGetMap function, so it might miss the cached IDs")
log.Print("idCount", idCount)
log.Print("ids", ids)
log.Print("list", list)
}
return list, errors.New("We weren't able to find a poll, but we don't know which one")
}
sidList = sidList[0 : len(sidList)-1]
err = errors.New("Unable to find the polls with the following IDs: " + sidList)
}
return list, err
}
func (store *DefaultPollStore) Reload(id int) error {
poll := &Poll{ID: id}
var optionTxt []byte

View File

@ -67,7 +67,7 @@ type config struct {
StaffCSS string // ? - Move this into the settings table? Might be better to implement this as Group CSS
DefaultForum int // The forum posts go in by default, this used to be covered by the Uncategorised Forum, but we want to replace it with a more robust solution. Make this a setting?
MinifyTemplates bool
MultiServer bool
ServerCount int
Noavatar string // ? - Move this into the settings table?
ItemsPerPage int // ? - Move this into the settings table?
@ -104,6 +104,9 @@ func VerifyConfig() error {
if !Forums.Exists(Config.DefaultForum) {
return errors.New("Invalid default forum")
}
if Config.ServerCount < 1 {
return errors.New("You can't have less than one server")
}
return nil
}

View File

@ -85,7 +85,13 @@ func HandleExpiredScheduledGroups() error {
}
// TODO: Use AddScheduledSecondTask
// TODO: Be a little more granular with the synchronisation
func HandleServerSync() error {
// We don't want to run any unnecessary queries when there is nothing to synchronise
/*if Config.ServerCount > 1 {
return nil
}*/
var lastUpdate time.Time
err := taskStmts.getSync.QueryRow().Scan(&lastUpdate)
if err != nil {
@ -93,7 +99,6 @@ func HandleServerSync() error {
}
if lastUpdate.After(lastSync) {
// TODO: A more granular sync
err = Forums.LoadForums()
if err != nil {
log.Print("Unable to reload the forums")

View File

@ -162,7 +162,7 @@ func compileTemplates() error {
//var topicList []TopicUser
//topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false})
forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
forumPage := ForumPage{"General Forum", user, headerVars, topicsList, forumItem, 1, 1}
forumPage := ForumPage{"General Forum", user, headerVars, topicsList, forumItem, []int{1}, 1, 1}
forumTmpl, err := c.Compile("forum.html", "templates/", "common.ForumPage", forumPage, varList)
if err != nil {
return err

View File

@ -122,6 +122,10 @@ func (tList *DefaultTopicList) Tick() error {
if err != nil {
return err
}
if group.UserCount == 0 {
continue
}
topicList, forumList, pageList, page, lastPage, err := tList.getListByGroup(group, 1)
if err != nil {
return err
@ -258,9 +262,7 @@ func (tList *DefaultTopicList) getList(page int, argList []interface{}, qlist st
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)
}
RunVhook("topics_topic_row_assign", &topicItem, &forum)
topicList = append(topicList, &topicItem)
reqUserList[topicItem.CreatedBy] = true
reqUserList[topicItem.LastReplyBy] = true

View File

@ -128,14 +128,14 @@ func (mus *DefaultUserStore) BulkGetMap(ids []int) (list map[int]*User, err erro
acc := qgen.Builder.Accumulator()
rows, err := acc.Select("users").Columns("uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group").Where("uid IN(" + qlist + ")").Query(uidList...)
if err != nil {
return nil, err
return list, err
}
for rows.Next() {
user := &User{Loggedin: true}
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.IsSuperAdmin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.LastIP, &user.TempGroup)
if err != nil {
return nil, err
return list, err
}
user.Init()

View File

@ -51,7 +51,7 @@ func init() {
common.Config.StaffCSS = "staff_post"
common.Config.DefaultForum = 2
common.Config.MinifyTemplates = true
common.Config.MultiServer = false // Experimental: Enable Cross-Server Synchronisation and several other features
common.Config.ServerCount = 1 // Experimental: Enable Cross-Server Synchronisation and several other features
//common.Config.Noavatar = "https://api.adorable.io/avatars/{width}/{id}@{site_url}.png"
common.Config.Noavatar = "https://api.adorable.io/avatars/285/{id}@{site_url}.png"

View File

@ -148,7 +148,7 @@ func init() {
common.Config.StaffCSS = "staff_post"
common.Config.DefaultForum = 2
common.Config.MinifyTemplates = true
common.Config.MultiServer = false // Experimental: Enable Cross-Server Synchronisation and several other features
common.Config.ServerCount = 1 // Experimental: Enable Cross-Server Synchronisation and several other features
//common.Config.Noavatar = "https://api.adorable.io/avatars/{width}/{id}@{site_url}.png"
common.Config.Noavatar = "https://api.adorable.io/avatars/285/{id}@{site_url}.png"

20
main.go
View File

@ -289,14 +289,20 @@ func main() {
fifteenMinuteTicker := time.NewTicker(15 * time.Minute)
//hourTicker := time.NewTicker(1 * time.Hour)
go func() {
var runHook = func(name string) {
err := common.RunTaskHook(name)
if err != nil {
common.LogError(err)
}
}
for {
select {
case <-halfSecondTicker.C:
// TODO: Add a plugin hook here
runHook("before_half_second_tick")
runTasks(common.ScheduledHalfSecondTasks)
// TODO: Add a plugin hook here
runHook("after_half_second_tick")
case <-secondTicker.C:
// TODO: Add a plugin hook here
runHook("before_second_tick")
runTasks(common.ScheduledSecondTasks)
// TODO: Stop hard-coding this
@ -317,16 +323,14 @@ func main() {
// TODO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high
// TODO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task?
// TODO: Rescan the static files for changes
// TODO: Add a plugin hook here
runHook("after_second_tick")
case <-fifteenMinuteTicker.C:
// TODO: Add a plugin hook here
runHook("before_fifteen_minute_tick")
runTasks(common.ScheduledFifteenMinuteTasks)
// TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
// TODO: Publish scheduled posts.
// TODO: Add a plugin hook here
runHook("after_fifteen_minute_tick")
}
// TODO: Handle the daily clean-up.

View File

@ -405,10 +405,11 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user common.User,
title = "Topic: " + title
content = content + "\n\nOriginal Post: #tid-" + strconv.Itoa(itemID)
} else {
if common.Vhooks["report_preassign"] != nil {
common.RunVhookNoreturn("report_preassign", &itemID, &itemType)
_, hasHook := common.RunVhookNeedHook("report_preassign", &itemID, &itemType)
if hasHook {
return nil
}
// Don't try to guess the type
return common.LocalError("Unknown type", w, r, user)
}

View File

@ -97,9 +97,7 @@ func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid s
topicItem.Link = common.BuildTopicURL(common.NameToSlug(topicItem.Title), topicItem.ID)
topicItem.RelativeLastReplyAt = common.RelativeTime(topicItem.LastReplyAt)
if common.Vhooks["forum_trow_assign"] != nil {
common.RunVhook("forum_trow_assign", &topicItem, &forum)
}
common.RunVhook("forum_trow_assign", &topicItem, &forum)
topicList = append(topicList, &topicItem)
reqUserList[topicItem.CreatedBy] = true
reqUserList[topicItem.LastReplyBy] = true
@ -130,7 +128,8 @@ func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid s
topicItem.LastUser = userList[topicItem.LastReplyBy]
}
pi := common.ForumPage{forum.Name, user, headerVars, topicList, forum, page, lastPage}
pageList := common.Paginate(forum.TopicCount, common.Config.ItemsPerPage, 5)
pi := common.ForumPage{forum.Name, user, headerVars, topicList, forum, pageList, page, lastPage}
if common.PreRenderHooks["pre_render_forum"] != nil {
if common.RunPreRenderHook("pre_render_forum", w, r, &user, &pi) {
return nil

View File

@ -37,8 +37,6 @@ func init() {
var successJSONBytes = []byte(`{"success":"1"}`)
func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit string) common.RouteError {
var err error
var replyList []common.ReplyUser
page, _ := strconv.Atoi(r.FormValue("page"))
// SEO URLs...
@ -110,82 +108,80 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
// Calculate the offset
offset, page, lastPage := common.PageOffset(topic.PostCount, page, common.Config.ItemsPerPage)
tpage := common.TopicPage{topic.Title, user, headerVars, replyList, topic, poll, page, lastPage}
tpage := common.TopicPage{topic.Title, user, headerVars, []common.ReplyUser{}, topic, poll, page, lastPage}
// Get the replies..
rows, err := topicStmts.getReplies.Query(topic.ID, offset, common.Config.ItemsPerPage)
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)
} else if err != nil {
return common.InternalError(err, w, r)
}
defer rows.Close()
replyItem := common.ReplyUser{ClassName: ""}
for rows.Next() {
err := rows.Scan(&replyItem.ID, &replyItem.Content, &replyItem.CreatedBy, &replyItem.CreatedAt, &replyItem.LastEdit, &replyItem.LastEditBy, &replyItem.Avatar, &replyItem.CreatedByName, &replyItem.Group, &replyItem.URLPrefix, &replyItem.URLName, &replyItem.Level, &replyItem.IPAddress, &replyItem.LikeCount, &replyItem.ActionType)
if err != nil {
// Get the replies if we have any...
if topic.PostCount > 0 {
rows, err := topicStmts.getReplies.Query(topic.ID, offset, common.Config.ItemsPerPage)
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)
} else if err != nil {
return common.InternalError(err, w, r)
}
defer rows.Close()
replyItem.UserLink = common.BuildProfileURL(common.NameToSlug(replyItem.CreatedByName), replyItem.CreatedBy)
replyItem.ParentID = topic.ID
replyItem.ContentHtml = common.ParseMessage(replyItem.Content, topic.ParentID, "forums")
replyItem.ContentLines = strings.Count(replyItem.Content, "\n")
postGroup, err = common.Groups.Get(replyItem.Group)
if err != nil {
return common.InternalError(err, w, r)
}
if postGroup.IsMod || postGroup.IsAdmin {
replyItem.ClassName = common.Config.StaffCSS
} else {
replyItem.ClassName = ""
}
// TODO: Make a function for this? Build a more sophisticated noavatar handling system? Do bulk user loads and let the common.UserStore initialise this?
replyItem.Avatar = common.BuildAvatar(replyItem.CreatedBy, replyItem.Avatar)
replyItem.Tag = postGroup.Tag
replyItem.RelativeCreatedAt = common.RelativeTime(replyItem.CreatedAt)
// We really shouldn't have inline HTML, we should do something about this...
if replyItem.ActionType != "" {
switch replyItem.ActionType {
case "lock":
replyItem.ActionType = "This topic has been locked by <a href='" + replyItem.UserLink + "'>" + replyItem.CreatedByName + "</a>"
replyItem.ActionIcon = "&#x1F512;&#xFE0E"
case "unlock":
replyItem.ActionType = "This topic has been reopened by <a href='" + replyItem.UserLink + "'>" + replyItem.CreatedByName + "</a>"
replyItem.ActionIcon = "&#x1F513;&#xFE0E"
case "stick":
replyItem.ActionType = "This topic has been pinned by <a href='" + replyItem.UserLink + "'>" + replyItem.CreatedByName + "</a>"
replyItem.ActionIcon = "&#x1F4CC;&#xFE0E"
case "unstick":
replyItem.ActionType = "This topic has been unpinned by <a href='" + replyItem.UserLink + "'>" + replyItem.CreatedByName + "</a>"
replyItem.ActionIcon = "&#x1F4CC;&#xFE0E"
case "move":
replyItem.ActionType = "This topic has been moved by <a href='" + replyItem.UserLink + "'>" + replyItem.CreatedByName + "</a>"
default:
replyItem.ActionType = replyItem.ActionType + " has happened"
replyItem.ActionIcon = ""
replyItem := common.ReplyUser{ClassName: ""}
for rows.Next() {
err := rows.Scan(&replyItem.ID, &replyItem.Content, &replyItem.CreatedBy, &replyItem.CreatedAt, &replyItem.LastEdit, &replyItem.LastEditBy, &replyItem.Avatar, &replyItem.CreatedByName, &replyItem.Group, &replyItem.URLPrefix, &replyItem.URLName, &replyItem.Level, &replyItem.IPAddress, &replyItem.LikeCount, &replyItem.ActionType)
if err != nil {
return common.InternalError(err, w, r)
}
}
replyItem.Liked = false
if common.Vhooks["topic_reply_row_assign"] != nil {
replyItem.UserLink = common.BuildProfileURL(common.NameToSlug(replyItem.CreatedByName), replyItem.CreatedBy)
replyItem.ParentID = topic.ID
replyItem.ContentHtml = common.ParseMessage(replyItem.Content, topic.ParentID, "forums")
replyItem.ContentLines = strings.Count(replyItem.Content, "\n")
postGroup, err = common.Groups.Get(replyItem.Group)
if err != nil {
return common.InternalError(err, w, r)
}
if postGroup.IsMod || postGroup.IsAdmin {
replyItem.ClassName = common.Config.StaffCSS
} else {
replyItem.ClassName = ""
}
// TODO: Make a function for this? Build a more sophisticated noavatar handling system? Do bulk user loads and let the common.UserStore initialise this?
replyItem.Avatar = common.BuildAvatar(replyItem.CreatedBy, replyItem.Avatar)
replyItem.Tag = postGroup.Tag
replyItem.RelativeCreatedAt = common.RelativeTime(replyItem.CreatedAt)
// We really shouldn't have inline HTML, we should do something about this...
if replyItem.ActionType != "" {
switch replyItem.ActionType {
case "lock":
replyItem.ActionType = "This topic has been locked by <a href='" + replyItem.UserLink + "'>" + replyItem.CreatedByName + "</a>"
replyItem.ActionIcon = "&#x1F512;&#xFE0E"
case "unlock":
replyItem.ActionType = "This topic has been reopened by <a href='" + replyItem.UserLink + "'>" + replyItem.CreatedByName + "</a>"
replyItem.ActionIcon = "&#x1F513;&#xFE0E"
case "stick":
replyItem.ActionType = "This topic has been pinned by <a href='" + replyItem.UserLink + "'>" + replyItem.CreatedByName + "</a>"
replyItem.ActionIcon = "&#x1F4CC;&#xFE0E"
case "unstick":
replyItem.ActionType = "This topic has been unpinned by <a href='" + replyItem.UserLink + "'>" + replyItem.CreatedByName + "</a>"
replyItem.ActionIcon = "&#x1F4CC;&#xFE0E"
case "move":
replyItem.ActionType = "This topic has been moved by <a href='" + replyItem.UserLink + "'>" + replyItem.CreatedByName + "</a>"
default:
replyItem.ActionType = replyItem.ActionType + " has happened"
replyItem.ActionIcon = ""
}
}
replyItem.Liked = false
common.RunVhook("topic_reply_row_assign", &tpage, &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()
if err != nil {
return common.InternalError(err, w, r)
}
//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()
if err != nil {
return common.InternalError(err, w, r)
}
//tpage.ItemList = replyList
if common.PreRenderHooks["pre_render_view_topic"] != nil {
if common.RunPreRenderHook("pre_render_view_topic", w, r, &user, &tpage) {
return nil
@ -195,7 +191,7 @@ func ViewTopic(w http.ResponseWriter, r *http.Request, user common.User, urlBit
if err != nil {
return common.InternalError(err, w, r)
}
common.TopicViewCounter.Bump(topic.ID) // TODO Move this into the router?
common.TopicViewCounter.Bump(topic.ID) // TODO: Move this into the router?
return nil
}
@ -228,9 +224,7 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, user common.User, sfid
// Lock this to the forum being linked?
// Should we always put it in strictmode when it's linked from another forum? Well, the user might end up changing their mind on what forum they want to post in and it would be a hassle, if they had to switch pages, even if it is a single click for many (exc. mobile)
var strictmode bool
if common.Vhooks["topic_create_pre_loop"] != nil {
common.RunVhook("topic_create_pre_loop", w, r, fid, &headerVars, &user, &strictmode)
}
common.RunVhook("topic_create_pre_loop", w, r, fid, &headerVars, &user, &strictmode)
// TODO: Re-add support for plugin_guilds
var forumList []common.Forum

View File

@ -3,9 +3,9 @@
// Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "strconv"
import "net/http"
import "./common"
import "strconv"
// nolint
func init() {
@ -93,118 +93,142 @@ w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_4)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1)))
w.Write(forum_5)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
}
w.Write(forum_6)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1)))
if tmpl_forum_vars.CurrentUser.ID != 0 {
w.Write(forum_7)
}
w.Write(forum_8)
if tmpl_forum_vars.CurrentUser.ID != 0 {
w.Write(forum_9)
}
w.Write(forum_10)
w.Write([]byte(tmpl_forum_vars.Title))
w.Write(forum_9)
if tmpl_forum_vars.CurrentUser.ID != 0 {
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_10)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_11)
if tmpl_forum_vars.CurrentUser.ID != 0 {
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_12)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_13)
w.Write(forum_14)
} else {
w.Write(forum_13)
}
w.Write(forum_14)
}
w.Write(forum_15)
}
w.Write(forum_16)
}
w.Write(forum_17)
if tmpl_forum_vars.CurrentUser.ID != 0 {
w.Write(forum_18)
w.Write(forum_16)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_19)
w.Write(forum_17)
w.Write([]byte(tmpl_forum_vars.CurrentUser.Avatar))
w.Write(forum_20)
w.Write(forum_18)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_21)
w.Write(forum_19)
if tmpl_forum_vars.CurrentUser.Perms.UploadFiles {
w.Write(forum_20)
}
w.Write(forum_21)
}
}
w.Write(forum_22)
}
w.Write(forum_23)
}
}
w.Write(forum_24)
if len(tmpl_forum_vars.ItemList) != 0 {
for _, item := range tmpl_forum_vars.ItemList {
w.Write(forum_25)
w.Write(forum_23)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(forum_24)
if item.Sticky {
w.Write(forum_25)
} else {
if item.IsClosed {
w.Write(forum_26)
if item.Sticky {
}
}
w.Write(forum_27)
} else {
if item.IsClosed {
w.Write([]byte(item.Creator.Link))
w.Write(forum_28)
}
}
w.Write(forum_29)
w.Write([]byte(item.Creator.Link))
w.Write(forum_30)
w.Write([]byte(item.Creator.Avatar))
w.Write(forum_29)
w.Write([]byte(item.Creator.Name))
w.Write(forum_30)
w.Write([]byte(item.Creator.Name))
w.Write(forum_31)
w.Write([]byte(item.Creator.Name))
w.Write(forum_32)
w.Write([]byte(item.Creator.Name))
w.Write(forum_33)
w.Write([]byte(item.Link))
w.Write(forum_34)
w.Write(forum_32)
w.Write([]byte(item.Title))
w.Write(forum_35)
w.Write(forum_33)
w.Write([]byte(item.Creator.Link))
w.Write(forum_36)
w.Write(forum_34)
w.Write([]byte(item.Creator.Name))
w.Write(forum_37)
w.Write(forum_35)
if item.IsClosed {
w.Write(forum_36)
}
if item.Sticky {
w.Write(forum_37)
}
w.Write(forum_38)
}
if item.Sticky {
w.Write(forum_39)
}
w.Write(forum_40)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(forum_41)
w.Write(forum_39)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(forum_42)
w.Write(forum_40)
if item.Sticky {
w.Write(forum_43)
w.Write(forum_41)
} else {
if item.IsClosed {
w.Write(forum_44)
w.Write(forum_42)
}
}
w.Write(forum_45)
w.Write(forum_43)
w.Write([]byte(item.LastUser.Link))
w.Write(forum_46)
w.Write(forum_44)
w.Write([]byte(item.LastUser.Avatar))
w.Write(forum_47)
w.Write(forum_45)
w.Write([]byte(item.LastUser.Name))
w.Write(forum_46)
w.Write([]byte(item.LastUser.Name))
w.Write(forum_47)
w.Write([]byte(item.LastUser.Link))
w.Write(forum_48)
w.Write([]byte(item.LastUser.Name))
w.Write(forum_49)
w.Write([]byte(item.LastUser.Link))
w.Write(forum_50)
w.Write([]byte(item.LastUser.Name))
w.Write(forum_51)
w.Write([]byte(item.RelativeLastReplyAt))
w.Write(forum_52)
w.Write(forum_50)
}
} else {
w.Write(forum_53)
w.Write(forum_51)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_54)
w.Write(forum_52)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_53)
}
w.Write(forum_54)
}
w.Write(forum_55)
}
if tmpl_forum_vars.LastPage > 1 {
w.Write(forum_56)
}
if tmpl_forum_vars.Page > 1 {
w.Write(forum_57)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page - 1)))
w.Write(forum_58)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page - 1)))
w.Write(forum_59)
}
if len(tmpl_forum_vars.PageList) != 0 {
for _, item := range tmpl_forum_vars.PageList {
w.Write(forum_60)
w.Write([]byte(strconv.Itoa(item)))
w.Write(forum_61)
w.Write([]byte(strconv.Itoa(item)))
w.Write(forum_62)
}
}
if tmpl_forum_vars.LastPage != tmpl_forum_vars.Page {
w.Write(forum_63)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1)))
w.Write(forum_64)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1)))
w.Write(forum_65)
}
w.Write(forum_66)
}
w.Write(forum_67)
w.Write(footer_0)
w.Write([]byte(common.BuildWidget("footer",tmpl_forum_vars.Header)))
w.Write(footer_1)

View File

@ -1114,11 +1114,10 @@ var topics_62 = []byte(`
var topics_63 = []byte(`
<div class="pageset">
`)
var topics_64 = []byte(`
var topics_64 = []byte(`<div class="pageitem"><a href="?page=`)
var topics_65 = []byte(`" rel="prev" aria-label="Go to the previous page">Prev</a></div>
<link rel="prev" href="?page=`)
var topics_65 = []byte(`" />
<div class="pageitem"><a href="?page=`)
var topics_66 = []byte(`" rel="prev" aria-label="Go to the previous page">Prev</a></div>`)
var topics_66 = []byte(`" />`)
var topics_67 = []byte(`
<div class="pageitem"><a href="?page=`)
var topics_68 = []byte(`">`)
@ -1139,41 +1138,38 @@ var topics_74 = []byte(`
var forum_0 = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/forum/`)
var forum_1 = []byte(`?page=`)
var forum_2 = []byte(`">&lt;</a></div>`)
var forum_3 = []byte(`<link rel="prerender" href="/forum/`)
var forum_3 = []byte(`<div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/forum/`)
var forum_4 = []byte(`?page=`)
var forum_5 = []byte(`" />
<div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/forum/`)
var forum_6 = []byte(`?page=`)
var forum_7 = []byte(`">&gt;</a></div>`)
var forum_8 = []byte(`
var forum_5 = []byte(`">&gt;</a></div>`)
var forum_6 = []byte(`
<main itemscope itemtype="http://schema.org/ItemList">
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block">
<div class="rowitem forum_title`)
var forum_9 = []byte(` has_opt`)
var forum_10 = []byte(`">
var forum_7 = []byte(` has_opt`)
var forum_8 = []byte(`">
<h1 itemprop="name">`)
var forum_11 = []byte(`</h1>
var forum_9 = []byte(`</h1>
</div>
`)
var forum_12 = []byte(`
var forum_10 = []byte(`
<div class="pre_opt auto_hide"></div>
<div class="opt create_topic_opt" title="Create Topic" aria-label="Create a topic"><a class="create_topic_link" href="/topics/create/`)
var forum_13 = []byte(`"></a></div>
var forum_11 = []byte(`"></a></div>
`)
var forum_14 = []byte(`
var forum_12 = []byte(`
<div class="opt mod_opt" title="Moderate">
<a class="moderate_link" href="#" aria-label="Moderate Posts"></a>
</div>
`)
var forum_15 = []byte(`<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic in this forum"><a></a></div>`)
var forum_16 = []byte(`
var forum_13 = []byte(`<div class="opt locked_opt" title="You don't have the permissions needed to create a topic" aria-label="You don't have the permissions needed to make a topic in this forum"><a></a></div>`)
var forum_14 = []byte(`
<div style="clear: both;"></div>
`)
var forum_17 = []byte(`
var forum_15 = []byte(`
</div>
`)
var forum_18 = []byte(`
var forum_16 = []byte(`
<div class="mod_floater auto_hide">
<form method="post">
<div class="mod_floater_head">
@ -1190,13 +1186,13 @@ var forum_18 = []byte(`
</form>
</div>
`)
var forum_19 = []byte(`
var forum_17 = []byte(`
<div id="forum_topic_create_form" class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="Quick Topic Form">
<form id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<img class="little_row_avatar" src="`)
var forum_20 = []byte(`" height="64" alt="Your Avatar" title="Your Avatar" />
var forum_18 = []byte(`" height="64" alt="Your Avatar" title="Your Avatar" />
<input form="quick_post_form" id="topic_board_input" name="topic-board" value="`)
var forum_21 = []byte(`" type="hidden">
var forum_19 = []byte(`" type="hidden">
<div class="main_form">
<div class="topic_meta">
<div class="formrow topic_name_row real_first_child">
@ -1220,75 +1216,99 @@ var forum_21 = []byte(`" type="hidden">
<button form="quick_post_form" name="topic-button" class="formbutton">Create Topic</button>
<button form="quick_post_form" class="formbutton" id="add_poll_button">Add Poll</button>
`)
var forum_22 = []byte(`
var forum_20 = []byte(`
<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>
<div id="upload_file_dock"></div>`)
var forum_23 = []byte(`
var forum_21 = []byte(`
<button class="formbutton close_form">Cancel</button>
</div>
</div>
</div>
</div>
`)
var forum_24 = []byte(`
var forum_22 = []byte(`
<div id="forum_topic_list" class="rowblock topic_list">
`)
var forum_25 = []byte(`<div class="topic_row" data-tid="`)
var forum_26 = []byte(`">
var forum_23 = []byte(`<div class="topic_row" data-tid="`)
var forum_24 = []byte(`">
<div class="rowitem topic_left passive datarow `)
var forum_27 = []byte(`topic_sticky`)
var forum_28 = []byte(`topic_closed`)
var forum_29 = []byte(`">
var forum_25 = []byte(`topic_sticky`)
var forum_26 = []byte(`topic_closed`)
var forum_27 = []byte(`">
<span class="selector"></span>
<a href="`)
var forum_30 = []byte(`"><img src="`)
var forum_31 = []byte(`" height="64" alt="`)
var forum_32 = []byte(`'s Avatar" title="`)
var forum_33 = []byte(`'s Avatar" /></a>
var forum_28 = []byte(`"><img src="`)
var forum_29 = []byte(`" height="64" alt="`)
var forum_30 = []byte(`'s Avatar" title="`)
var forum_31 = []byte(`'s Avatar" /></a>
<span class="topic_inner_left">
<a class="rowtopic" href="`)
var forum_34 = []byte(`" itemprop="itemListElement"><span>`)
var forum_35 = []byte(`</span></a>
var forum_32 = []byte(`" itemprop="itemListElement"><span>`)
var forum_33 = []byte(`</span></a>
<br /><a class="rowsmall starter" href="`)
var forum_36 = []byte(`">`)
var forum_37 = []byte(`</a>
var forum_34 = []byte(`">`)
var forum_35 = []byte(`</a>
`)
var forum_38 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var forum_39 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var forum_40 = []byte(`
var forum_36 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var forum_37 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var forum_38 = []byte(`
</span>
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`)
var forum_41 = []byte(`</span><br />
var forum_39 = []byte(`</span><br />
<span class="likeCount">`)
var forum_42 = []byte(`</span>
var forum_40 = []byte(`</span>
</span>
</div>
<div class="rowitem topic_right passive datarow `)
var forum_43 = []byte(`topic_sticky`)
var forum_44 = []byte(`topic_closed`)
var forum_45 = []byte(`">
var forum_41 = []byte(`topic_sticky`)
var forum_42 = []byte(`topic_closed`)
var forum_43 = []byte(`">
<a href="`)
var forum_46 = []byte(`"><img src="`)
var forum_47 = []byte(`" height="64" alt="`)
var forum_48 = []byte(`'s Avatar" title="`)
var forum_49 = []byte(`'s Avatar" /></a>
var forum_44 = []byte(`"><img src="`)
var forum_45 = []byte(`" height="64" alt="`)
var forum_46 = []byte(`'s Avatar" title="`)
var forum_47 = []byte(`'s Avatar" /></a>
<span>
<a href="`)
var forum_50 = []byte(`" class="lastName" style="font-size: 14px;">`)
var forum_51 = []byte(`</a><br>
var forum_48 = []byte(`" class="lastName" style="font-size: 14px;">`)
var forum_49 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">`)
var forum_52 = []byte(`</span>
var forum_50 = []byte(`</span>
</span>
</div>
</div>`)
var forum_53 = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
var forum_54 = []byte(` <a href="/topics/create/`)
var forum_55 = []byte(`">Start one?</a>`)
var forum_56 = []byte(`</div>`)
var forum_57 = []byte(`
var forum_51 = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
var forum_52 = []byte(` <a href="/topics/create/`)
var forum_53 = []byte(`">Start one?</a>`)
var forum_54 = []byte(`</div>`)
var forum_55 = []byte(`
</div>
`)
var forum_56 = []byte(`
<div class="pageset">
`)
var forum_57 = []byte(`<div class="pageitem"><a href="?page=`)
var forum_58 = []byte(`" rel="prev" aria-label="Go to the previous page">Prev</a></div>
<link rel="prev" href="?page=`)
var forum_59 = []byte(`" />`)
var forum_60 = []byte(`
<div class="pageitem"><a href="?page=`)
var forum_61 = []byte(`">`)
var forum_62 = []byte(`</a></div>
`)
var forum_63 = []byte(`
<link rel="next" href="?page=`)
var forum_64 = []byte(`" />
<div class="pageitem"><a href="?page=`)
var forum_65 = []byte(`" rel="next" aria-label="Go to the next page">Next</a></div>`)
var forum_66 = []byte(`
</div>
`)
var forum_67 = []byte(`
</main>
`)
var guilds_guild_list_0 = []byte(`

View File

@ -1,8 +1,7 @@
{{template "header.html" . }}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/forum/{{.Forum.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
{{if ne .LastPage .Page}}<div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<main itemscope itemtype="http://schema.org/ItemList">
<div id="forum_head_block" class="rowblock rowhead topic_list_title_block">
@ -101,5 +100,19 @@
</div>
</div>{{else}}<div class="rowitem passive">There aren't any topics in this forum yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/{{.Forum.ID}}">Start one?</a>{{end}}</div>{{end}}
</div>
{{if gt .LastPage 1}}
<div class="pageset">
{{if gt .Page 1}}<div class="pageitem"><a href="?page={{subtract .Page 1}}" rel="prev" aria-label="Go to the previous page">Prev</a></div>
<link rel="prev" href="?page={{subtract .Page 1}}" />{{end}}
{{range .PageList}}
<div class="pageitem"><a href="?page={{.}}">{{.}}</a></div>
{{end}}
{{if ne .LastPage .Page}}
<link rel="next" href="?page={{add .Page 1}}" />
<div class="pageitem"><a href="?page={{add .Page 1}}" rel="next" aria-label="Go to the next page">Next</a></div>{{end}}
</div>
{{end}}
</main>
{{template "footer.html" . }}

View File

@ -128,9 +128,8 @@
{{if gt .LastPage 1}}
<div class="pageset">
{{if gt .Page 1}}
<link rel="prev" href="?page={{subtract .Page 1}}" />
<div class="pageitem"><a href="?page={{subtract .Page 1}}" rel="prev" aria-label="Go to the previous page">Prev</a></div>{{end}}
{{if gt .Page 1}}<div class="pageitem"><a href="?page={{subtract .Page 1}}" rel="prev" aria-label="Go to the previous page">Prev</a></div>
<link rel="prev" href="?page={{subtract .Page 1}}" />{{end}}
{{range .PageList}}
<div class="pageitem"><a href="?page={{.}}">{{.}}</a></div>
{{end}}

View File

@ -484,7 +484,7 @@ textarea.large {
padding: 5px;
width: calc(100% - 16px);
}
.formitem select {
select {
background-color: var(--input-background-color);
border: 1px solid var(--input-border-color);
color: var(--input-text-color);
@ -613,6 +613,13 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
margin-left: 3px;
background: var(--bright-input-border-color);
}
.pollinput {
display: flex;
margin-bottom: 8px;
}
.quick_create_form .pollinputlabel {
display: none;
}
/*#poll_option_text_0 {
color: hsl(359,98%,43%);

View File

@ -16,6 +16,11 @@
margin-left: 2px;
}
.timeRangeSelector {
padding: 2px;
margin-top: -3px;
margin-bottom: -3px;
}
.panel_floater {
margin-left: auto;
}

View File

@ -488,24 +488,24 @@ li a {
.little_row_avatar {
display: none;
}
.topic_create_form .topic_button_row .formitem {
.quick_create_form .quick_button_row .formitem {
display: flex;
}
.topic_create_form .formbutton:first-child {
.quick_create_form .formbutton:first-child {
margin-left: 0px;
margin-right: 5px;
}
.topic_create_form .formbutton:not(:first-child) {
.quick_create_form .formbutton:not(:first-child) {
margin-left: 0px;
margin-right: 5px;
}
.topic_create_form .formbutton:last-child {
.quick_create_form .formbutton:last-child {
margin-left: auto;
}
.topic_create_form .upload_file_dock {
.quick_create_form .upload_file_dock {
display: flex;
}
.topic_create_form .uploadItem {
.quick_create_form .uploadItem {
display: inline-block;
margin-left: 8px;
margin-right: 8px;
@ -765,6 +765,51 @@ button.username {
border-bottom: 1.5px inset var(--main-border-color);
}
/* TODO: Show the avatar next to the reply form */
.topic_reply_container .userinfo {
display: none;
}
input[type=checkbox] {
display: none;
}
input[type=checkbox] + label {
display: inline-block;
width: 12px;
height: 12px;
margin-bottom: -2px;
border: 1px solid hsl(0, 0%, 80%);
background-color: white;
}
input[type=checkbox]:checked + label .sel {
display: inline-block;
width: 5px;
height: 5px;
background-color: white;
}
input[type=checkbox] + label.poll_option_label {
width: 18px;
height: 18px;
margin-right: 2px;
background-color: white;
border: 1px solid hsl(0, 0%, 70%);
color: #505050;
}
input[type=checkbox]:checked + label.poll_option_label .sel {
display: inline-block;
width: 10px;
height: 10px;
margin-left: 3px;
background: hsl(0,0%,70%);
}
.poll_option {
margin-bottom: 1px;
}
.quick_create_form .pollinputlabel {
display: none;
}
.simple {
background-color: white;
}
@ -945,7 +990,7 @@ button.username {
.pageset {
display: flex;
margin-bottom: 10px;
margin-top: -5px;
margin-top: 8px;
}
.pageitem {
border: 1px solid var(--main-border-color);
@ -958,6 +1003,9 @@ button.username {
color: black;
text-decoration: none;
}
.colstack_right .pageset {
margin-top: -5px;
}
/* Firefox specific CSS */
@supports (-moz-appearance: none) {

View File

@ -372,8 +372,18 @@ li a {
border: none;
}
input, select {
padding: 3px;
}
/* Mostly for textareas */
.formitem:only-child { width: 100%; }
.formitem:only-child {
width: 100%;
}
.formitem:only-child select {
padding: 1px;
margin-top: -1px;
margin-bottom: -1px;
}
.formitem textarea {
width: 100%;
height: 100px;
@ -383,9 +393,6 @@ li a {
margin: 0 auto;
float: none;
}
.formitem:not(:only-child) input, .formitem:not(:only-child) select {
padding: 3px;
}
.formitem:not(:only-child).formlabel {
padding-top: 15px;
padding-bottom: 12px;
@ -417,6 +424,9 @@ li a {
/* Topics */
.topic_list {
border-bottom: none;
}
.topic_list .topic_row {
display: grid;
grid-template-columns: calc(100% - 204px) 204px;
@ -737,6 +747,10 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
margin-left: auto;
}
.quick_create_form .pollinputlabel {
display: none;
}
.alert {
display: block;
padding: 5px;
@ -869,7 +883,7 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
.pageset {
display: flex;
margin-bottom: 10px;
margin-top: -5px;
margin-top: 8px;
}
.pageitem {
background-color: white;
@ -882,5 +896,8 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
color: black;
text-decoration: none;
}
.colstack_right .pageset {
margin-top: -5px;
}
{{template "media.partial.css" }}

View File

@ -1,4 +1,6 @@
/* Control Panel */
.submenu a {
margin-left: 8px;
}
.edit_button:before {
content: "Edit";
@ -125,4 +127,14 @@
content: " || ";
padding-left: 2px;
padding-right: 2px;
}
.ct_chart {
padding-left: 10px;
padding-top: 14px;
padding-bottom: 4px;
padding-right: 10px;
margin-bottom: 12px;
background-color: white;
border: 1px solid hsl(0,0%,85%);
}