IP Search now searches profile replies.

Added the subscription and attachment stores.

Refactored the routes to use the new subscription and attachment stores.
Fixed up the buttons on the profile comments for Cosora.
Made various enhancements to Cosora.
Renamed routePanel to routePanelDashboard.
Changed routeCreateTopicSubmit into an upload action and moved it into /routes/topic.go and refactored some of the redundant bits away.
This commit is contained in:
Azareal 2018-01-22 08:15:45 +00:00
parent 8252c481df
commit a7fec11170
15 changed files with 266 additions and 193 deletions

29
common/attachments.go Normal file
View File

@ -0,0 +1,29 @@
package common
import (
"database/sql"
"../query_gen/lib"
)
var Attachments AttachmentStore
type AttachmentStore interface {
Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string) error
}
type DefaultAttachmentStore struct {
add *sql.Stmt
}
func NewDefaultAttachmentStore() (*DefaultAttachmentStore, error) {
acc := qgen.Builder.Accumulator()
return &DefaultAttachmentStore{
add: acc.Insert("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Prepare(),
}, acc.FirstError()
}
func (store *DefaultAttachmentStore) Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string) error {
_, err := store.add.Exec(sectionID, sectionTable, originID, originTable, uploadedBy, path)
return err
}

View File

@ -14,8 +14,9 @@ type IPSearcher interface {
type DefaultIPSearcher struct {
searchUsers *sql.Stmt
searchReplies *sql.Stmt
searchTopics *sql.Stmt
searchReplies *sql.Stmt
searchUsersReplies *sql.Stmt
}
// NewDefaultIPSearcher gives you a new instance of DefaultIPSearcher
@ -25,6 +26,7 @@ func NewDefaultIPSearcher() (*DefaultIPSearcher, error) {
searchUsers: acc.Select("users").Columns("uid").Where("last_ip = ?").Prepare(),
searchTopics: acc.Select("users").Columns("uid").InQ("uid", acc.Select("topics").Columns("createdBy").Where("ipaddress = ?")).Prepare(),
searchReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("replies").Columns("createdBy").Where("ipaddress = ?")).Prepare(),
searchUsersReplies: acc.Select("users").Columns("uid").InQ("uid", acc.Select("users_replies").Columns("createdBy").Where("ipaddress = ?")).Prepare(),
}, acc.FirstError()
}
@ -53,16 +55,18 @@ func (searcher *DefaultIPSearcher) Lookup(ip string) (uids []int, err error) {
if err != nil {
return uids, err
}
err = runQuery(searcher.searchTopics)
if err != nil {
return uids, err
}
err = runQuery(searcher.searchReplies)
if err != nil {
return uids, err
}
err = runQuery(searcher.searchUsersReplies)
if err != nil {
return uids, err
}
// Convert the user ID map to a slice, then bulk load the users
uids = make([]int, len(reqUserList))

27
common/subscription.go Normal file
View File

@ -0,0 +1,27 @@
package common
import "database/sql"
import "../query_gen/lib"
var Subscriptions SubscriptionStore
// ? Should we have a subscription store for each zone? topic, forum, etc?
type SubscriptionStore interface {
Add(uid int, elementID int, elementType string) error
}
type DefaultSubscriptionStore struct {
add *sql.Stmt
}
func NewDefaultSubscriptionStore() (*DefaultSubscriptionStore, error) {
acc := qgen.Builder.Accumulator()
return &DefaultSubscriptionStore{
add: acc.Insert("activity_subscriptions").Columns("user, targetID, targetType, level").Fields("?,?,?,2").Prepare(),
}, acc.FirstError()
}
func (store *DefaultSubscriptionStore) Add(uid int, elementID int, elementType string) error {
_, err := store.add.Exec(uid, elementID, elementType)
return err
}

View File

@ -35,11 +35,9 @@ type Stmts struct {
addActivity *sql.Stmt
notifyOne *sql.Stmt
addEmail *sql.Stmt
addSubscription *sql.Stmt
addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt
addTheme *sql.Stmt
addAttachment *sql.Stmt
createWordFilter *sql.Stmt
editReply *sql.Stmt
updatePlugin *sql.Stmt
@ -256,13 +254,6 @@ func _gen_mssql() (err error) {
return err
}
log.Print("Preparing addSubscription statement.")
stmts.addSubscription, err = db.Prepare("INSERT INTO [activity_subscriptions] ([user],[targetID],[targetType],[level]) VALUES (?,?,?,2)")
if err != nil {
log.Print("Bad Query: ","INSERT INTO [activity_subscriptions] ([user],[targetID],[targetType],[level]) VALUES (?,?,?,2)")
return err
}
log.Print("Preparing addForumPermsToForum statement.")
stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO [forums_permissions] ([gid],[fid],[preset],[permissions]) VALUES (?,?,?,?)")
if err != nil {
@ -284,13 +275,6 @@ func _gen_mssql() (err error) {
return err
}
log.Print("Preparing addAttachment statement.")
stmts.addAttachment, err = db.Prepare("INSERT INTO [attachments] ([sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path]) VALUES (?,?,?,?,?,?)")
if err != nil {
log.Print("Bad Query: ","INSERT INTO [attachments] ([sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path]) VALUES (?,?,?,?,?,?)")
return err
}
log.Print("Preparing createWordFilter statement.")
stmts.createWordFilter, err = db.Prepare("INSERT INTO [word_filters] ([find],[replacement]) VALUES (?,?)")
if err != nil {

View File

@ -37,11 +37,9 @@ type Stmts struct {
addActivity *sql.Stmt
notifyOne *sql.Stmt
addEmail *sql.Stmt
addSubscription *sql.Stmt
addForumPermsToForum *sql.Stmt
addPlugin *sql.Stmt
addTheme *sql.Stmt
addAttachment *sql.Stmt
createWordFilter *sql.Stmt
editReply *sql.Stmt
updatePlugin *sql.Stmt
@ -232,12 +230,6 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing addSubscription statement.")
stmts.addSubscription, err = db.Prepare("INSERT INTO `activity_subscriptions`(`user`,`targetID`,`targetType`,`level`) VALUES (?,?,?,2)")
if err != nil {
return err
}
log.Print("Preparing addForumPermsToForum statement.")
stmts.addForumPermsToForum, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) VALUES (?,?,?,?)")
if err != nil {
@ -256,12 +248,6 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing addAttachment statement.")
stmts.addAttachment, err = db.Prepare("INSERT INTO `attachments`(`sectionID`,`sectionTable`,`originID`,`originTable`,`uploadedBy`,`path`) VALUES (?,?,?,?,?,?)")
if err != nil {
return err
}
log.Print("Preparing createWordFilter statement.")
stmts.createWordFilter, err = db.Prepare("INSERT INTO `word_filters`(`find`,`replacement`) VALUES (?,?)")
if err != nil {

View File

@ -69,7 +69,7 @@ var RouteMap = map[string]interface{}{
"routePanelBackups": routePanelBackups,
"routePanelLogsMod": routePanelLogsMod,
"routePanelDebug": routePanelDebug,
"routePanel": routePanel,
"routePanelDashboard": routePanelDashboard,
"routeAccountEditCritical": routeAccountEditCritical,
"routeAccountEditCriticalSubmit": routeAccountEditCriticalSubmit,
"routeAccountEditAvatar": routeAccountEditAvatar,
@ -83,7 +83,7 @@ var RouteMap = map[string]interface{}{
"routes.UnbanUser": routes.UnbanUser,
"routes.ActivateUser": routes.ActivateUser,
"routes.IPSearch": routes.IPSearch,
"routeCreateTopicSubmit": routeCreateTopicSubmit,
"routes.CreateTopicSubmit": routes.CreateTopicSubmit,
"routes.EditTopicSubmit": routes.EditTopicSubmit,
"routes.DeleteTopicSubmit": routes.DeleteTopicSubmit,
"routes.StickTopicSubmit": routes.StickTopicSubmit,
@ -164,7 +164,7 @@ var routeMapEnum = map[string]int{
"routePanelBackups": 50,
"routePanelLogsMod": 51,
"routePanelDebug": 52,
"routePanel": 53,
"routePanelDashboard": 53,
"routeAccountEditCritical": 54,
"routeAccountEditCriticalSubmit": 55,
"routeAccountEditAvatar": 56,
@ -178,7 +178,7 @@ var routeMapEnum = map[string]int{
"routes.UnbanUser": 64,
"routes.ActivateUser": 65,
"routes.IPSearch": 66,
"routeCreateTopicSubmit": 67,
"routes.CreateTopicSubmit": 67,
"routes.EditTopicSubmit": 68,
"routes.DeleteTopicSubmit": 69,
"routes.StickTopicSubmit": 70,
@ -257,7 +257,7 @@ var reverseRouteMapEnum = map[int]string{
50: "routePanelBackups",
51: "routePanelLogsMod",
52: "routePanelDebug",
53: "routePanel",
53: "routePanelDashboard",
54: "routeAccountEditCritical",
55: "routeAccountEditCriticalSubmit",
56: "routeAccountEditAvatar",
@ -271,7 +271,7 @@ var reverseRouteMapEnum = map[int]string{
64: "routes.UnbanUser",
65: "routes.ActivateUser",
66: "routes.IPSearch",
67: "routeCreateTopicSubmit",
67: "routes.CreateTopicSubmit",
68: "routes.EditTopicSubmit",
69: "routes.DeleteTopicSubmit",
70: "routes.StickTopicSubmit",
@ -951,7 +951,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
err = routePanelDebug(w,req,user)
default:
common.RouteViewCounter.Bump(53)
err = routePanel(w,req,user)
err = routePanelDashboard(w,req,user)
}
if err != nil {
router.handleError(err,w,req,user)
@ -1130,20 +1130,25 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/topic":
switch(req.URL.Path) {
case "/topic/create/submit/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
err = common.MemberOnly(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
err = common.HandleUploadRoute(w,req,user,common.Config.MaxRequestSize)
if err != nil {
router.handleError(err,w,req,user)
return
}
err = common.NoUploadSessionMismatch(w,req,user)
if err != nil {
router.handleError(err,w,req,user)
return
}
common.RouteViewCounter.Bump(67)
err = routeCreateTopicSubmit(w,req,user)
err = routes.CreateTopicSubmit(w,req,user)
case "/topic/edit/submit/":
err = common.NoSessionMismatch(w,req,user)
if err != nil {

View File

@ -84,6 +84,14 @@ func afterDBInit() (err error) {
if err != nil {
return err
}
common.Subscriptions, err = common.NewDefaultSubscriptionStore()
if err != nil {
return err
}
common.Attachments, err = common.NewDefaultAttachmentStore()
if err != nil {
return err
}
common.GlobalViewCounter, err = common.NewGlobalViewCounter()
if err != nil {

View File

@ -16,135 +16,6 @@ import (
"./common"
)
func routeCreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
// TODO: Reduce this to 1MB for attachments for each file?
if r.ContentLength > int64(common.Config.MaxRequestSize) {
size, unit := common.ConvertByteUnit(float64(common.Config.MaxRequestSize))
return common.CustomError("Your attachments are too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, user)
}
r.Body = http.MaxBytesReader(w, r.Body, int64(common.Config.MaxRequestSize))
err := r.ParseMultipartForm(int64(common.Megabyte))
if err != nil {
return common.LocalError("Unable to parse the form", w, r, user)
}
fid, err := strconv.Atoi(r.PostFormValue("topic-board"))
if err != nil {
return common.LocalError("The provided ForumID is not a valid number.", w, r, user)
}
// TODO: Add hooks to make use of headerLite
_, ferr := common.SimpleForumUserCheck(w, r, &user, fid)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.CreateTopic {
return common.NoPermissions(w, r, user)
}
topicName := html.EscapeString(strings.Replace(r.PostFormValue("topic-name"), "\n", "", -1))
content := common.PreparseMessage(r.PostFormValue("topic-content"))
// TODO: Fully parse the post and store it in the parsed column
tid, err := common.Topics.Create(fid, topicName, content, user.ID, user.LastIP)
if err != nil {
switch err {
case common.ErrNoRows:
return common.LocalError("Something went wrong, perhaps the forum got deleted?", w, r, user)
case common.ErrNoTitle:
return common.LocalError("This topic doesn't have a title", w, r, user)
case common.ErrNoBody:
return common.LocalError("This topic doesn't have a body", w, r, user)
default:
return common.InternalError(err, w, r)
}
}
_, err = stmts.addSubscription.Exec(user.ID, tid, "topic")
if err != nil {
return common.InternalError(err, w, r)
}
err = user.IncreasePostStats(common.WordCount(content), true)
if err != nil {
return common.InternalError(err, w, r)
}
// Handle the file attachments
// TODO: Stop duplicating this code
if user.Perms.UploadFiles {
files, ok := r.MultipartForm.File["upload_files"]
if ok {
if len(files) > 5 {
return common.LocalError("You can't attach more than five files", w, r, user)
}
for _, file := range files {
if common.Dev.DebugMode {
log.Print("file.Filename ", file.Filename)
}
extarr := strings.Split(file.Filename, ".")
if len(extarr) < 2 {
return common.LocalError("Bad file", w, r, user)
}
ext := extarr[len(extarr)-1]
// TODO: Can we do this without a regex?
reg, err := regexp.Compile("[^A-Za-z0-9]+")
if err != nil {
return common.LocalError("Bad file extension", w, r, user)
}
ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
if !common.AllowedFileExts.Contains(ext) {
return common.LocalError("You're not allowed to upload files with this extension", w, r, user)
}
infile, err := file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
hasher := sha256.New()
_, err = io.Copy(hasher, infile)
if err != nil {
return common.LocalError("Upload failed [Hashing Failed]", w, r, user)
}
infile.Close()
checksum := hex.EncodeToString(hasher.Sum(nil))
filename := checksum + "." + ext
outfile, err := os.Create("." + "/attachs/" + filename)
if err != nil {
return common.LocalError("Upload failed [File Creation Failed]", w, r, user)
}
defer outfile.Close()
infile, err = file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
_, err = io.Copy(outfile, infile)
if err != nil {
return common.LocalError("Upload failed [Copy Failed]", w, r, user)
}
_, err = stmts.addAttachment.Exec(fid, "forums", tid, "topics", user.ID, filename)
if err != nil {
return common.InternalError(err, w, r)
}
}
}
}
common.PostCounter.Bump()
common.TopicCounter.Bump()
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
return nil
}
func routeCreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
tid, err := strconv.Atoi(r.PostFormValue("tid"))
if err != nil {
@ -226,7 +97,7 @@ func routeCreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.
return common.LocalError("Upload failed [Copy Failed]", w, r, user)
}
_, err = stmts.addAttachment.Exec(topic.ParentID, "forums", tid, "replies", user.ID, filename)
err = common.Attachments.Add(topic.ParentID, "forums", tid, "replies", user.ID, filename)
if err != nil {
return common.InternalError(err, w, r)
}

View File

@ -43,7 +43,7 @@ func panelRenderTemplate(tmplName string, w http.ResponseWriter, r *http.Request
return nil
}
func routePanel(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
func routePanelDashboard(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
headerVars, stats, ferr := common.PanelUserCheck(w, r, &user)
if ferr != nil {
return ferr

View File

@ -288,16 +288,12 @@ func writeInserts(adapter qgen.Adapter) error {
build.Insert("addEmail").Table("emails").Columns("email, uid, validated, token").Fields("?,?,?,?").Parse()
build.Insert("addSubscription").Table("activity_subscriptions").Columns("user, targetID, targetType, level").Fields("?,?,?,2").Parse()
build.Insert("addForumPermsToForum").Table("forums_permissions").Columns("gid,fid,preset,permissions").Fields("?,?,?,?").Parse()
build.Insert("addPlugin").Table("plugins").Columns("uname, active, installed").Fields("?,?,?").Parse()
build.Insert("addTheme").Table("themes").Columns("uname, default").Fields("?,?").Parse()
build.Insert("addAttachment").Table("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Parse()
build.Insert("createWordFilter").Table("word_filters").Columns("find, replacement").Fields("?,?").Parse()
return nil

View File

@ -65,7 +65,7 @@ func buildTopicRoutes() {
topicGroup := newRouteGroup("/topic/")
topicGroup.Routes(
View("routeTopicID", "/topic/", "extraData"),
Action("routeCreateTopicSubmit", "/topic/create/submit/"),
UploadAction("routes.CreateTopicSubmit", "/topic/create/submit/").MaxSizeVar("common.Config.MaxRequestSize"),
Action("routes.EditTopicSubmit", "/topic/edit/submit/", "extraData"),
Action("routes.DeleteTopicSubmit", "/topic/delete/submit/").LitBefore("req.URL.Path += extraData"),
Action("routes.StickTopicSubmit", "/topic/stick/submit/", "extraData"),
@ -120,7 +120,7 @@ func buildAccountRoutes() {
func buildPanelRoutes() {
panelGroup := newRouteGroup("/panel/").Before("SuperModOnly")
panelGroup.Routes(
View("routePanel", "/panel/"),
View("routePanelDashboard", "/panel/"),
View("routePanelForums", "/panel/forums/"),
Action("routePanelForumsCreateSubmit", "/panel/forums/create/"),
Action("routePanelForumsDelete", "/panel/forums/delete/", "extraData"),

View File

@ -1,11 +1,18 @@
package routes
import (
"crypto/sha256"
"database/sql"
"encoding/hex"
"encoding/json"
"html"
"io"
"log"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"../common"
)
@ -99,6 +106,123 @@ func CreateTopic(w http.ResponseWriter, r *http.Request, user common.User, sfid
return nil
}
func CreateTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
fid, err := strconv.Atoi(r.PostFormValue("topic-board"))
if err != nil {
return common.LocalError("The provided ForumID is not a valid number.", w, r, user)
}
// TODO: Add hooks to make use of headerLite
_, ferr := common.SimpleForumUserCheck(w, r, &user, fid)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.CreateTopic {
return common.NoPermissions(w, r, user)
}
topicName := html.EscapeString(strings.Replace(r.PostFormValue("topic-name"), "\n", "", -1))
content := common.PreparseMessage(r.PostFormValue("topic-content"))
// TODO: Fully parse the post and store it in the parsed column
tid, err := common.Topics.Create(fid, topicName, content, user.ID, user.LastIP)
if err != nil {
switch err {
case common.ErrNoRows:
return common.LocalError("Something went wrong, perhaps the forum got deleted?", w, r, user)
case common.ErrNoTitle:
return common.LocalError("This topic doesn't have a title", w, r, user)
case common.ErrNoBody:
return common.LocalError("This topic doesn't have a body", w, r, user)
default:
return common.InternalError(err, w, r)
}
}
err = common.Subscriptions.Add(user.ID, tid, "topic")
if err != nil {
return common.InternalError(err, w, r)
}
err = user.IncreasePostStats(common.WordCount(content), true)
if err != nil {
return common.InternalError(err, w, r)
}
// Handle the file attachments
// TODO: Stop duplicating this code
if user.Perms.UploadFiles {
files, ok := r.MultipartForm.File["upload_files"]
if ok {
if len(files) > 5 {
return common.LocalError("You can't attach more than five files", w, r, user)
}
for _, file := range files {
if common.Dev.DebugMode {
log.Print("file.Filename ", file.Filename)
}
extarr := strings.Split(file.Filename, ".")
if len(extarr) < 2 {
return common.LocalError("Bad file", w, r, user)
}
ext := extarr[len(extarr)-1]
// TODO: Can we do this without a regex?
reg, err := regexp.Compile("[^A-Za-z0-9]+")
if err != nil {
return common.LocalError("Bad file extension", w, r, user)
}
ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
if !common.AllowedFileExts.Contains(ext) {
return common.LocalError("You're not allowed to upload files with this extension", w, r, user)
}
infile, err := file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
hasher := sha256.New()
_, err = io.Copy(hasher, infile)
if err != nil {
return common.LocalError("Upload failed [Hashing Failed]", w, r, user)
}
infile.Close()
checksum := hex.EncodeToString(hasher.Sum(nil))
filename := checksum + "." + ext
outfile, err := os.Create("." + "/attachs/" + filename)
if err != nil {
return common.LocalError("Upload failed [File Creation Failed]", w, r, user)
}
defer outfile.Close()
infile, err = file.Open()
if err != nil {
return common.LocalError("Upload failed", w, r, user)
}
defer infile.Close()
_, err = io.Copy(outfile, infile)
if err != nil {
return common.LocalError("Upload failed [Copy Failed]", w, r, user)
}
err = common.Attachments.Add(fid, "forums", tid, "topics", user.ID, filename)
if err != nil {
return common.InternalError(err, w, r)
}
}
}
}
common.PostCounter.Bump()
common.TopicCounter.Bump()
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
return nil
}
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
// TODO: Disable stat updates in posts handled by plugin_guilds
func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {

View File

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

View File

@ -642,6 +642,10 @@ textarea {
.rowlist.bgavatars .rowitem {
flex-direction: column;
}
.rowlist.bgavatars .rowitem {
padding-top: 16px;
padding-bottom: 10px;
}
.bgavatars .bgsub {
border-radius: 30px;
height: 48px;
@ -1060,6 +1064,28 @@ textarea {
#profile_comments .content_column {
margin-bottom: 16px;
}
#profile_comments button {
background: inherit;
color: var(--lighter-text-color);
padding-left: 8px;
padding-right: 8px;
cursor: pointer;
}
#profile_comments button:hover {
color: var(--light-text-color);
}
#profile_comments button.edit_item:after {
font: normal normal normal 14px/1 FontAwesome;
content: "\f040";
}
#profile_comments button.delete_item:after {
font: normal normal normal 14px/1 FontAwesome;
content: "\f1f8";
}
#profile_comments button.report_item:after {
font: normal normal normal 14px/1 FontAwesome;
content: "\f024";
}
#profile_comments_head {
margin-top: 6px;
}
@ -1167,7 +1193,7 @@ textarea {
/* TODO: Highlight the one we're currently on? */
.pageset {
display: flex;
margin-left: 16px;
margin-left: 14px;
}
.pageitem {
padding: 8px;

View File

@ -91,6 +91,9 @@
#panel_users .panel_tag:not(.panel_right_button) {
display: none;
}
.panel_right_button + .panel_right_button {
margin-left: 3px;
}
.panel_group_perms .formitem a {
margin-top: 5px;
@ -128,6 +131,12 @@
display: block;
}
#panel_users .rowitem .rowTitle {
border-bottom: 1px solid var(--lighter-text-color);
padding-bottom: 4px;
margin-bottom: 4px;
}
#forum_quick_perms .formitem {
display: flex;
}
@ -190,3 +199,7 @@
font-size: 16px;
}
*/
.pageset {
margin-left: 16px;
}