Commented out more debug code.

Main Menu is now shown on the main menu in the menu list for extra clarity.
Travis should now be able to run it's tests.
Moved routeChangeTheme to the routes package.
Moved routeShowAttachment to the routes package and partially refactored it.
Moved routeLikeTopicSubmit to the routes package.
Moved routeReplyLikeSubmit to the routes package and partially refactored it.
Moved routeProfileReplyCreateSubmit to the routes package.
Moved routeLogout to the routes package, now known as routes.AccountLogout.
Moved the routeDynamic stub to the routes package, now known as routes.DynamicRoute.
Moved the routeUploads stub to the routes package, now known as routes.UploadedFile.
Moved the BadRoute stub to the routes package, now known as routes.BadRoute.
All routes moved to the routes package have had the route prefix dropped from their name.
Simplified the email token route to redirect back to the main email route instead of rendering the same template.
Refactored the panel menus to use the new submenu system instead of the old one which had a lot of menu duplication.
Added a stub directory for Nox, the next major theme after Cosora.
Fixed a bug where the alerts wouldn't load outside of the index.

Tweaked the CSS in the topic creation and reply forms on Shadow.
Tweaked the padding on the stickies on Shadow.
Improved the submenu CSS on every theme.
Fixed the submitrow CSS on Shadow, Tempra Conflux.
Fixed some double borders on Tempra Conflux.
The frontend sidebar should no longer show up in the Control Panel in Tempra Conflux and Tempra Simple.
Tweaked the title CSS on Cosora.
Tweaked the user manager CSS on Cosora.
Changed the primary text colour on Cosora.
Fixed attachment images taking up too much space on Cosora.

Run the patcher or update script for this commit.
This commit is contained in:
Azareal 2018-05-15 15:59:52 +10:00
parent 3976c49054
commit 2be884edc1
39 changed files with 519 additions and 449 deletions

View File

@ -3,7 +3,6 @@ package common
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"mime" "mime"
"strings" "strings"
"sync" "sync"
@ -38,9 +37,10 @@ type CSSData struct {
} }
func (list SFileList) JSTmplInit() error { func (list SFileList) JSTmplInit() error {
DebugLog("Initialising the client side templates")
var fragMap = make(map[string][][]byte) var fragMap = make(map[string][][]byte)
fragMap["alert"] = tmpl.GetFrag("alert") fragMap["alert"] = tmpl.GetFrag("alert")
fmt.Println("fragMap: ", fragMap) DebugLog("fragMap: ", fragMap)
return filepath.Walk("./tmpl_client", func(path string, f os.FileInfo, err error) error { return filepath.Walk("./tmpl_client", func(path string, f os.FileInfo, err error) error {
if f.IsDir() { if f.IsDir() {
return nil return nil
@ -75,15 +75,15 @@ func (list SFileList) JSTmplInit() error {
if !hasBrace { if !hasBrace {
return errors.New("no right brace found after the template function name") return errors.New("no right brace found after the template function name")
} }
fmt.Println("spaceIndex: ", spaceIndex) //fmt.Println("spaceIndex: ", spaceIndex)
fmt.Println("endBrace: ", endBrace) //fmt.Println("endBrace: ", endBrace)
fmt.Println("string(data[spaceIndex:endBrace]): ", string(data[spaceIndex:endBrace])) //fmt.Println("string(data[spaceIndex:endBrace]): ", string(data[spaceIndex:endBrace]))
preLen := len(data) preLen := len(data)
data = replace(data, string(data[spaceIndex:endBrace]), "") data = replace(data, string(data[spaceIndex:endBrace]), "")
data = replace(data, "))\n", "\n") data = replace(data, "))\n", "\n")
endBrace -= preLen - len(data) // Offset it as we've deleted portions endBrace -= preLen - len(data) // Offset it as we've deleted portions
var showPos = func(data []byte, index int) (out string) { /*var showPos = func(data []byte, index int) (out string) {
out = "[" out = "["
for j, char := range data { for j, char := range data {
if index == j { if index == j {
@ -93,16 +93,16 @@ func (list SFileList) JSTmplInit() error {
} }
} }
return out + "]" return out + "]"
} }*/
// ? Can we just use a regex? I'm thinking of going more efficient, or just outright rolling wasm, this is a temp hack in a place where performance doesn't particularly matter // ? Can we just use a regex? I'm thinking of going more efficient, or just outright rolling wasm, this is a temp hack in a place where performance doesn't particularly matter
var each = func(phrase string, handle func(index int)) { var each = func(phrase string, handle func(index int)) {
fmt.Println("find each '" + phrase + "'") //fmt.Println("find each '" + phrase + "'")
var index = endBrace var index = endBrace
var foundIt bool var foundIt bool
for { for {
fmt.Println("in index: ", index) //fmt.Println("in index: ", index)
fmt.Println("pos: ", showPos(data, index)) //fmt.Println("pos: ", showPos(data, index))
index, foundIt = skipAllUntilCharsExist(data, index, []byte(phrase)) index, foundIt = skipAllUntilCharsExist(data, index, []byte(phrase))
if !foundIt { if !foundIt {
break break
@ -136,7 +136,7 @@ func (list SFileList) JSTmplInit() error {
} }
}) })
each("if ", func(index int) { each("if ", func(index int) {
fmt.Println("if index: ", index) //fmt.Println("if index: ", index)
braceAt, hasBrace := skipUntilIfExists(data, index, '{') braceAt, hasBrace := skipUntilIfExists(data, index, '{')
if hasBrace { if hasBrace {
if data[braceAt-1] != ' ' { if data[braceAt-1] != ' ' {
@ -161,7 +161,7 @@ func (list SFileList) JSTmplInit() error {
tmplName := strings.TrimSuffix(path, ".go") tmplName := strings.TrimSuffix(path, ".go")
fragset, ok := fragMap[strings.TrimPrefix(tmplName, "template_")] fragset, ok := fragMap[strings.TrimPrefix(tmplName, "template_")]
if !ok { if !ok {
fmt.Println("tmplName: ", tmplName) DebugLog("tmplName: ", tmplName)
return errors.New("couldn't find template in fragmap") return errors.New("couldn't find template in fragmap")
} }

View File

@ -254,6 +254,7 @@ type PanelThemesPage struct {
} }
type PanelMenuListItem struct { type PanelMenuListItem struct {
Name string
ID int ID int
ItemCount int ItemCount int
} }

View File

@ -1,4 +1,4 @@
package main package config
import "../common" import "../common"

View File

@ -21,7 +21,6 @@ type Stmts struct {
getTopicBasic *sql.Stmt getTopicBasic *sql.Stmt
forumEntryExists *sql.Stmt forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt groupEntryExists *sql.Stmt
getAttachment *sql.Stmt
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
createReport *sql.Stmt createReport *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
@ -154,14 +153,6 @@ func _gen_mssql() (err error) {
return err return err
} }
common.DebugLog("Preparing getAttachment statement.")
stmts.getAttachment, err = db.Prepare("SELECT [sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path] FROM [attachments] WHERE [path] = ?1 AND [sectionID] = ?2 AND [sectionTable] = ?3")
if err != nil {
log.Print("Error in getAttachment statement.")
log.Print("Bad Query: ","SELECT [sectionID],[sectionTable],[originID],[originTable],[uploadedBy],[path] FROM [attachments] WHERE [path] = ?1 AND [sectionID] = ?2 AND [sectionTable] = ?3")
return err
}
common.DebugLog("Preparing getForumTopics statement.") common.DebugLog("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

@ -23,7 +23,6 @@ type Stmts struct {
getTopicBasic *sql.Stmt getTopicBasic *sql.Stmt
forumEntryExists *sql.Stmt forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt groupEntryExists *sql.Stmt
getAttachment *sql.Stmt
getForumTopics *sql.Stmt getForumTopics *sql.Stmt
createReport *sql.Stmt createReport *sql.Stmt
addForumPermsToForum *sql.Stmt addForumPermsToForum *sql.Stmt
@ -144,13 +143,6 @@ func _gen_mysql() (err error) {
return err return err
} }
common.DebugLog("Preparing getAttachment statement.")
stmts.getAttachment, err = db.Prepare("SELECT `sectionID`,`sectionTable`,`originID`,`originTable`,`uploadedBy`,`path` FROM `attachments` WHERE `path` = ? AND `sectionID` = ? AND `sectionTable` = ?")
if err != nil {
log.Print("Error in getAttachment statement.")
return err
}
common.DebugLog("Preparing getForumTopics statement.") common.DebugLog("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

@ -24,8 +24,8 @@ var RouteMap = map[string]interface{}{
"routes.CustomPage": routes.CustomPage, "routes.CustomPage": routes.CustomPage,
"routes.ForumList": routes.ForumList, "routes.ForumList": routes.ForumList,
"routes.ViewForum": routes.ViewForum, "routes.ViewForum": routes.ViewForum,
"routeChangeTheme": routeChangeTheme, "routes.ChangeTheme": routes.ChangeTheme,
"routeShowAttachment": routeShowAttachment, "routes.ShowAttachment": routes.ShowAttachment,
"common.RouteWebsockets": common.RouteWebsockets, "common.RouteWebsockets": common.RouteWebsockets,
"routeReportSubmit": routeReportSubmit, "routeReportSubmit": routeReportSubmit,
"routes.CreateTopic": routes.CreateTopic, "routes.CreateTopic": routes.CreateTopic,
@ -109,28 +109,28 @@ var RouteMap = map[string]interface{}{
"routes.LockTopicSubmit": routes.LockTopicSubmit, "routes.LockTopicSubmit": routes.LockTopicSubmit,
"routes.UnlockTopicSubmit": routes.UnlockTopicSubmit, "routes.UnlockTopicSubmit": routes.UnlockTopicSubmit,
"routes.MoveTopicSubmit": routes.MoveTopicSubmit, "routes.MoveTopicSubmit": routes.MoveTopicSubmit,
"routeLikeTopicSubmit": routeLikeTopicSubmit, "routes.LikeTopicSubmit": routes.LikeTopicSubmit,
"routes.ViewTopic": routes.ViewTopic, "routes.ViewTopic": routes.ViewTopic,
"routes.CreateReplySubmit": routes.CreateReplySubmit, "routes.CreateReplySubmit": routes.CreateReplySubmit,
"routes.ReplyEditSubmit": routes.ReplyEditSubmit, "routes.ReplyEditSubmit": routes.ReplyEditSubmit,
"routes.ReplyDeleteSubmit": routes.ReplyDeleteSubmit, "routes.ReplyDeleteSubmit": routes.ReplyDeleteSubmit,
"routeReplyLikeSubmit": routeReplyLikeSubmit, "routes.ReplyLikeSubmit": routes.ReplyLikeSubmit,
"routeProfileReplyCreateSubmit": routeProfileReplyCreateSubmit, "routes.ProfileReplyCreateSubmit": routes.ProfileReplyCreateSubmit,
"routes.ProfileReplyEditSubmit": routes.ProfileReplyEditSubmit, "routes.ProfileReplyEditSubmit": routes.ProfileReplyEditSubmit,
"routes.ProfileReplyDeleteSubmit": routes.ProfileReplyDeleteSubmit, "routes.ProfileReplyDeleteSubmit": routes.ProfileReplyDeleteSubmit,
"routes.PollVote": routes.PollVote, "routes.PollVote": routes.PollVote,
"routes.PollResults": routes.PollResults, "routes.PollResults": routes.PollResults,
"routes.AccountLogin": routes.AccountLogin, "routes.AccountLogin": routes.AccountLogin,
"routes.AccountRegister": routes.AccountRegister, "routes.AccountRegister": routes.AccountRegister,
"routeLogout": routeLogout, "routes.AccountLogout": routes.AccountLogout,
"routes.AccountLoginSubmit": routes.AccountLoginSubmit, "routes.AccountLoginSubmit": routes.AccountLoginSubmit,
"routes.AccountRegisterSubmit": routes.AccountRegisterSubmit, "routes.AccountRegisterSubmit": routes.AccountRegisterSubmit,
"routeDynamic": routeDynamic, "routes.DynamicRoute": routes.DynamicRoute,
"routeUploads": routeUploads, "routes.UploadedFile": routes.UploadedFile,
"routes.StaticFile": routes.StaticFile, "routes.StaticFile": routes.StaticFile,
"routes.RobotsTxt": routes.RobotsTxt, "routes.RobotsTxt": routes.RobotsTxt,
"routes.SitemapXml": routes.SitemapXml, "routes.SitemapXml": routes.SitemapXml,
"BadRoute": BadRoute, "routes.BadRoute": routes.BadRoute,
} }
// ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS // ! NEVER RELY ON THESE REMAINING THE SAME BETWEEN COMMITS
@ -140,8 +140,8 @@ var routeMapEnum = map[string]int{
"routes.CustomPage": 2, "routes.CustomPage": 2,
"routes.ForumList": 3, "routes.ForumList": 3,
"routes.ViewForum": 4, "routes.ViewForum": 4,
"routeChangeTheme": 5, "routes.ChangeTheme": 5,
"routeShowAttachment": 6, "routes.ShowAttachment": 6,
"common.RouteWebsockets": 7, "common.RouteWebsockets": 7,
"routeReportSubmit": 8, "routeReportSubmit": 8,
"routes.CreateTopic": 9, "routes.CreateTopic": 9,
@ -225,28 +225,28 @@ var routeMapEnum = map[string]int{
"routes.LockTopicSubmit": 87, "routes.LockTopicSubmit": 87,
"routes.UnlockTopicSubmit": 88, "routes.UnlockTopicSubmit": 88,
"routes.MoveTopicSubmit": 89, "routes.MoveTopicSubmit": 89,
"routeLikeTopicSubmit": 90, "routes.LikeTopicSubmit": 90,
"routes.ViewTopic": 91, "routes.ViewTopic": 91,
"routes.CreateReplySubmit": 92, "routes.CreateReplySubmit": 92,
"routes.ReplyEditSubmit": 93, "routes.ReplyEditSubmit": 93,
"routes.ReplyDeleteSubmit": 94, "routes.ReplyDeleteSubmit": 94,
"routeReplyLikeSubmit": 95, "routes.ReplyLikeSubmit": 95,
"routeProfileReplyCreateSubmit": 96, "routes.ProfileReplyCreateSubmit": 96,
"routes.ProfileReplyEditSubmit": 97, "routes.ProfileReplyEditSubmit": 97,
"routes.ProfileReplyDeleteSubmit": 98, "routes.ProfileReplyDeleteSubmit": 98,
"routes.PollVote": 99, "routes.PollVote": 99,
"routes.PollResults": 100, "routes.PollResults": 100,
"routes.AccountLogin": 101, "routes.AccountLogin": 101,
"routes.AccountRegister": 102, "routes.AccountRegister": 102,
"routeLogout": 103, "routes.AccountLogout": 103,
"routes.AccountLoginSubmit": 104, "routes.AccountLoginSubmit": 104,
"routes.AccountRegisterSubmit": 105, "routes.AccountRegisterSubmit": 105,
"routeDynamic": 106, "routes.DynamicRoute": 106,
"routeUploads": 107, "routes.UploadedFile": 107,
"routes.StaticFile": 108, "routes.StaticFile": 108,
"routes.RobotsTxt": 109, "routes.RobotsTxt": 109,
"routes.SitemapXml": 110, "routes.SitemapXml": 110,
"BadRoute": 111, "routes.BadRoute": 111,
} }
var reverseRouteMapEnum = map[int]string{ var reverseRouteMapEnum = map[int]string{
0: "routeAPI", 0: "routeAPI",
@ -254,8 +254,8 @@ var reverseRouteMapEnum = map[int]string{
2: "routes.CustomPage", 2: "routes.CustomPage",
3: "routes.ForumList", 3: "routes.ForumList",
4: "routes.ViewForum", 4: "routes.ViewForum",
5: "routeChangeTheme", 5: "routes.ChangeTheme",
6: "routeShowAttachment", 6: "routes.ShowAttachment",
7: "common.RouteWebsockets", 7: "common.RouteWebsockets",
8: "routeReportSubmit", 8: "routeReportSubmit",
9: "routes.CreateTopic", 9: "routes.CreateTopic",
@ -339,28 +339,28 @@ var reverseRouteMapEnum = map[int]string{
87: "routes.LockTopicSubmit", 87: "routes.LockTopicSubmit",
88: "routes.UnlockTopicSubmit", 88: "routes.UnlockTopicSubmit",
89: "routes.MoveTopicSubmit", 89: "routes.MoveTopicSubmit",
90: "routeLikeTopicSubmit", 90: "routes.LikeTopicSubmit",
91: "routes.ViewTopic", 91: "routes.ViewTopic",
92: "routes.CreateReplySubmit", 92: "routes.CreateReplySubmit",
93: "routes.ReplyEditSubmit", 93: "routes.ReplyEditSubmit",
94: "routes.ReplyDeleteSubmit", 94: "routes.ReplyDeleteSubmit",
95: "routeReplyLikeSubmit", 95: "routes.ReplyLikeSubmit",
96: "routeProfileReplyCreateSubmit", 96: "routes.ProfileReplyCreateSubmit",
97: "routes.ProfileReplyEditSubmit", 97: "routes.ProfileReplyEditSubmit",
98: "routes.ProfileReplyDeleteSubmit", 98: "routes.ProfileReplyDeleteSubmit",
99: "routes.PollVote", 99: "routes.PollVote",
100: "routes.PollResults", 100: "routes.PollResults",
101: "routes.AccountLogin", 101: "routes.AccountLogin",
102: "routes.AccountRegister", 102: "routes.AccountRegister",
103: "routeLogout", 103: "routes.AccountLogout",
104: "routes.AccountLoginSubmit", 104: "routes.AccountLoginSubmit",
105: "routes.AccountRegisterSubmit", 105: "routes.AccountRegisterSubmit",
106: "routeDynamic", 106: "routes.DynamicRoute",
107: "routeUploads", 107: "routes.UploadedFile",
108: "routes.StaticFile", 108: "routes.StaticFile",
109: "routes.RobotsTxt", 109: "routes.RobotsTxt",
110: "routes.SitemapXml", 110: "routes.SitemapXml",
111: "BadRoute", 111: "routes.BadRoute",
} }
var osMapEnum = map[string]int{ var osMapEnum = map[string]int{
"unknown": 0, "unknown": 0,
@ -860,7 +860,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(5) counters.RouteViewCounter.Bump(5)
err = routeChangeTheme(w,req,user) err = routes.ChangeTheme(w,req,user)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
@ -872,7 +872,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(6) counters.RouteViewCounter.Bump(6)
err = routeShowAttachment(w,req,user,extraData) err = routes.ShowAttachment(w,req,user,extraData)
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
} }
@ -1632,7 +1632,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(90) counters.RouteViewCounter.Bump(90)
err = routeLikeTopicSubmit(w,req,user,extraData) err = routes.LikeTopicSubmit(w,req,user,extraData)
default: default:
counters.RouteViewCounter.Bump(91) counters.RouteViewCounter.Bump(91)
err = routes.ViewTopic(w,req,user, extraData) err = routes.ViewTopic(w,req,user, extraData)
@ -1712,7 +1712,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(95) counters.RouteViewCounter.Bump(95)
err = routeReplyLikeSubmit(w,req,user,extraData) err = routes.ReplyLikeSubmit(w,req,user,extraData)
} }
if err != nil { if err != nil {
router.handleError(err,w,req,user) router.handleError(err,w,req,user)
@ -1733,7 +1733,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(96) counters.RouteViewCounter.Bump(96)
err = routeProfileReplyCreateSubmit(w,req,user) err = routes.ProfileReplyCreateSubmit(w,req,user)
case "/profile/reply/edit/submit/": case "/profile/reply/edit/submit/":
err = common.NoSessionMismatch(w,req,user) err = common.NoSessionMismatch(w,req,user)
if err != nil { if err != nil {
@ -1814,7 +1814,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
counters.RouteViewCounter.Bump(103) counters.RouteViewCounter.Bump(103)
err = routeLogout(w,req,user) err = routes.AccountLogout(w,req,user)
case "/accounts/login/submit/": case "/accounts/login/submit/":
err = common.ParseForm(w,req,user) err = common.ParseForm(w,req,user)
if err != nil { if err != nil {

View File

@ -701,6 +701,7 @@
"panel_themes_make_default":"Make Default", "panel_themes_make_default":"Make Default",
"panel_themes_menus_head":"Menus", "panel_themes_menus_head":"Menus",
"panel_themes_menus_main":"Main Menu",
"panel_themes_menus_item_edit_button_aria":"Edit menu item", "panel_themes_menus_item_edit_button_aria":"Edit menu item",
"panel_themes_menus_item_delete_button_aria":"Delete menu item", "panel_themes_menus_item_delete_button_aria":"Delete menu item",

View File

@ -23,6 +23,7 @@ import (
"./common/counters" "./common/counters"
"./config" "./config"
"./query_gen/lib" "./query_gen/lib"
"./routes"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
) )
@ -445,7 +446,7 @@ func main() {
// TODO: Redirect to port 443 // TODO: Redirect to port 443
go func() { go func() {
log.Print("Listening on port 80") log.Print("Listening on port 80")
err = newServer(":80", &HTTPSRedirect{}).ListenAndServe() err = newServer(":80", &routes.HTTPSRedirect{}).ListenAndServe()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -2,166 +2,12 @@ package main
import ( import (
"net/http" "net/http"
"path/filepath"
"strconv" "strconv"
"strings"
"./common" "./common"
"./common/counters" "./common/counters"
) )
// TODO: Refactor this
func routeLikeTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
isJs := (r.PostFormValue("isJs") == "1")
tid, err := strconv.Atoi(stid)
if err != nil {
return common.PreErrorJSQ("Topic IDs can only ever be numbers.", w, r, isJs)
}
topic, err := common.Topics.Get(tid)
if err == ErrNoRows {
return common.PreErrorJSQ("The requested topic doesn't exist.", w, r, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
// TODO: Add hooks to make use of headerLite
_, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
return common.NoPermissionsJSQ(w, r, user, isJs)
}
if topic.CreatedBy == user.ID {
return common.LocalErrorJSQ("You can't like your own topics", w, r, user, isJs)
}
_, err = common.Users.Get(topic.CreatedBy)
if err != nil && err == ErrNoRows {
return common.LocalErrorJSQ("The target user doesn't exist", w, r, user, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
score := 1
err = topic.Like(score, user.ID)
//log.Print("likeErr: ", err)
if err == common.ErrAlreadyLiked {
return common.LocalErrorJSQ("You already liked this", w, r, user, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
err = common.AddActivityAndNotifyTarget(user.ID, topic.CreatedBy, "like", "topic", tid)
if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
if !isJs {
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
} else {
_, _ = w.Write(successJSONBytes)
}
return nil
}
func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError {
isJs := (r.PostFormValue("isJs") == "1")
rid, err := strconv.Atoi(srid)
if err != nil {
return common.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, isJs)
}
reply, err := common.Rstore.Get(rid)
if err == ErrNoRows {
return common.PreErrorJSQ("You can't like something which doesn't exist!", w, r, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
var fid int
err = stmts.getTopicFID.QueryRow(reply.ParentID).Scan(&fid)
if err == ErrNoRows {
return common.PreErrorJSQ("The parent topic doesn't exist.", w, r, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
// 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.LikeItem {
return common.NoPermissionsJSQ(w, r, user, isJs)
}
if reply.CreatedBy == user.ID {
return common.LocalErrorJSQ("You can't like your own replies", w, r, user, isJs)
}
_, err = common.Users.Get(reply.CreatedBy)
if err != nil && err != ErrNoRows {
return common.LocalErrorJSQ("The target user doesn't exist", w, r, user, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
err = reply.Like(user.ID)
if err == common.ErrAlreadyLiked {
return common.LocalErrorJSQ("You've already liked this!", w, r, user, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
err = common.AddActivityAndNotifyTarget(user.ID, reply.CreatedBy, "like", "post", rid)
if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
if !isJs {
http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther)
} else {
_, _ = w.Write(successJSONBytes)
}
return nil
}
func routeProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
if !user.Perms.ViewTopic || !user.Perms.CreateReply {
return common.NoPermissions(w, r, user)
}
uid, err := strconv.Atoi(r.PostFormValue("uid"))
if err != nil {
return common.LocalError("Invalid UID", w, r, user)
}
profileOwner, err := common.Users.Get(uid)
if err == ErrNoRows {
return common.LocalError("The profile you're trying to post on doesn't exist.", w, r, user)
} else if err != nil {
return common.InternalError(err, w, r)
}
content := common.PreparseMessage(r.PostFormValue("reply-content"))
// TODO: Fully parse the post and store it in the parsed column
_, err = common.Prstore.Create(profileOwner.ID, content, user.ID, user.LastIP)
if err != nil {
return common.InternalError(err, w, r)
}
err = common.AddActivityAndNotifyTarget(user.ID, profileOwner.ID, "reply", "user", profileOwner.ID)
if err != nil {
return common.InternalError(err, w, r)
}
counters.PostCounter.Bump()
http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther)
return nil
}
func routeReportSubmit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError { func routeReportSubmit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError {
itemID, err := strconv.Atoi(sitemID) itemID, err := strconv.Atoi(sitemID)
if err != nil { if err != nil {
@ -292,9 +138,13 @@ func routeAccountEditEmail(w http.ResponseWriter, r *http.Request, user common.U
email.Primary = true email.Primary = true
emailList = append(emailList, email) emailList = append(emailList, email)
} }
if !common.Site.EnableEmails { if !common.Site.EnableEmails {
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_disabled")) headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_disabled"))
} }
if r.FormValue("verified") == "1" {
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_verify_success"))
}
pi := common.Page{"Email Manager", user, headerVars, emailList, nil} pi := common.Page{"Email Manager", user, headerVars, emailList, nil}
if common.RunPreRenderHook("pre_render_account_own_edit_email", w, r, &user, &pi) { if common.RunPreRenderHook("pre_render_account_own_edit_email", w, r, &user, &pi) {
@ -361,72 +211,7 @@ func routeAccountEditEmailTokenSubmit(w http.ResponseWriter, r *http.Request, us
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
} }
http.Redirect(w, r, "/user/edit/email/?verified=1", http.StatusSeeOther)
if !common.Site.EnableEmails {
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_disabled"))
}
headerVars.NoticeList = append(headerVars.NoticeList, common.GetNoticePhrase("account_mail_verify_success"))
pi := common.Page{"Email Manager", user, headerVars, emailList, nil}
if common.RunPreRenderHook("pre_render_account_own_edit_email", w, r, &user, &pi) {
return nil
}
err = common.Templates.ExecuteTemplate(w, "account_own_edit_email.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
}
func routeLogout(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
if !user.Loggedin {
return common.LocalError("You can't logout without logging in first.", w, r, user)
}
common.Auth.Logout(w, user.ID)
http.Redirect(w, r, "/", http.StatusSeeOther)
return nil
}
func routeShowAttachment(w http.ResponseWriter, r *http.Request, user common.User, filename string) common.RouteError {
filename = common.Stripslashes(filename)
var ext = filepath.Ext("./attachs/" + filename)
//log.Print("ext ", ext)
//log.Print("filename ", filename)
if !common.AllowedFileExts.Contains(strings.TrimPrefix(ext, ".")) {
return common.LocalError("Bad extension", w, r, user)
}
sectionID, err := strconv.Atoi(r.FormValue("sectionID"))
if err != nil {
return common.LocalError("The sectionID is not an integer", w, r, user)
}
var sectionTable = r.FormValue("sectionType")
var originTable string
var originID, uploadedBy int
err = stmts.getAttachment.QueryRow(filename, sectionID, sectionTable).Scan(&sectionID, &sectionTable, &originID, &originTable, &uploadedBy, &filename)
if err == ErrNoRows {
return common.NotFound(w, r, nil)
} else if err != nil {
return common.InternalError(err, w, r)
}
if sectionTable == "forums" {
_, ferr := common.SimpleForumUserCheck(w, r, &user, sectionID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic {
return common.NoPermissions(w, r, user)
}
} else {
return common.LocalError("Unknown section", w, r, user)
}
if originTable != "topics" && originTable != "replies" {
return common.LocalError("Unknown origin", w, r, user)
}
// TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side
http.ServeFile(w, r, "./attachs/"+filename)
return nil return nil
} }

View File

@ -2397,7 +2397,12 @@ func routePanelThemesMenus(w http.ResponseWriter, r *http.Request, user common.U
var menuList []common.PanelMenuListItem var menuList []common.PanelMenuListItem
for mid, list := range common.Menus.GetAllMap() { for mid, list := range common.Menus.GetAllMap() {
var name = ""
if mid == 1 {
name = common.GetTmplPhrase("panel_themes_menus_main")
}
menuList = append(menuList, common.PanelMenuListItem{ menuList = append(menuList, common.PanelMenuListItem{
Name: name,
ID: mid, ID: mid,
ItemCount: len(list.List), ItemCount: len(list.List),
}) })

View File

@ -92,6 +92,7 @@ func patcher(scanner *bufio.Scanner) error {
} }
fmt.Println("Applying the patches") fmt.Println("Applying the patches")
if dbVersion < 2 {
if dbVersion < 1 { if dbVersion < 1 {
err := patch0(scanner) err := patch0(scanner)
if err != nil { if err != nil {
@ -99,6 +100,8 @@ func patcher(scanner *bufio.Scanner) error {
} }
} }
return patch1(scanner) return patch1(scanner)
}
return patch2(scanner)
} }
func execStmt(stmt *sql.Stmt, err error) error { func execStmt(stmt *sql.Stmt, err error) error {

View File

@ -152,3 +152,57 @@ func patch1(scanner *bufio.Scanner) error {
return nil return nil
} }
func patch2(scanner *bufio.Scanner) error {
// ! Don't reuse this function blindly, it doesn't escape apostrophes
var replaceTextWhere = func(replaceThis string, withThis string) error {
return execStmt(qgen.Builder.SimpleUpdate("viewchunks", "route = '"+withThis+"'", "route = '"+replaceThis+"'"))
}
err := replaceTextWhere("routeLogout", "routes.AccountLogout")
if err != nil {
return err
}
err = replaceTextWhere("routeShowAttachment", "routes.ShowAttachment")
if err != nil {
return err
}
err = replaceTextWhere("routeChangeTheme", "routes.ChangeTheme")
if err != nil {
return err
}
err = replaceTextWhere("routeProfileReplyCreateSubmit", "routes.ProfileReplyCreateSubmit")
if err != nil {
return err
}
err = replaceTextWhere("routeLikeTopicSubmit", "routes.LikeTopicSubmit")
if err != nil {
return err
}
err = replaceTextWhere("routeReplyLikeSubmit", "routes.ReplyLikeSubmit")
if err != nil {
return err
}
err = replaceTextWhere("routeDynamic", "routes.DynamicRoute")
if err != nil {
return err
}
err = replaceTextWhere("routeUploads", "routes.UploadedFile")
if err != nil {
return err
}
err = replaceTextWhere("BadRoute", "routes.BadRoute")
if err != nil {
return err
}
return nil
}

View File

@ -217,16 +217,30 @@ function runWebSockets() {
} }
} }
function loadScript(name, callback) {
let url = "//" +siteURL+"/static/"+name
$.getScript(url)
.done(callback)
.fail((e,xhr,settings,ex) => {
console.log("Unable to get script '"+url+"'");
console.log("e: ", e);
console.log("xhr: ", xhr);
console.log("settings: ", settings);
console.log("ex: ",ex);
console.trace();
});
}
$(document).ready(function(){ $(document).ready(function(){
runHook("start_init"); runHook("start_init");
$.getScript( "./static/template_alert.js", () => { loadScript("template_alert.js",() => {
console.log("Loaded template_alert.js"); console.log("Loaded template_alert.js");
alertsInitted = true; alertsInitted = true;
var alertMenuList = document.getElementsByClassName("menu_alerts"); var alertMenuList = document.getElementsByClassName("menu_alerts");
for(var i = 0; i < alertMenuList.length; i++) { for(var i = 0; i < alertMenuList.length; i++) {
loadAlerts(alertMenuList[i]); loadAlerts(alertMenuList[i]);
} }
}); })
if(window["WebSocket"]) runWebSockets(); if(window["WebSocket"]) runWebSockets();
else conn = false; else conn = false;

View File

@ -283,8 +283,6 @@ func writeSelects(adapter qgen.Adapter) error {
build.Select("groupEntryExists").Table("users_groups").Columns("gid").Where("name = ''").Orderby("gid ASC").Limit("0,1").Parse() build.Select("groupEntryExists").Table("users_groups").Columns("gid").Where("name = ''").Orderby("gid ASC").Limit("0,1").Parse()
build.Select("getAttachment").Table("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Where("path = ? AND sectionID = ? AND sectionTable = ?").Parse()
return nil return nil
} }

View File

@ -155,12 +155,12 @@ func main() {
} }
// Stubs for us to refer to these routes through // Stubs for us to refer to these routes through
mapIt("routeDynamic") mapIt("routes.DynamicRoute")
mapIt("routeUploads") mapIt("routes.UploadedFile")
mapIt("routes.StaticFile") mapIt("routes.StaticFile")
mapIt("routes.RobotsTxt") mapIt("routes.RobotsTxt")
mapIt("routes.SitemapXml") mapIt("routes.SitemapXml")
mapIt("BadRoute") mapIt("routes.BadRoute")
tmplVars.AllRouteNames = allRouteNames tmplVars.AllRouteNames = allRouteNames
tmplVars.AllRouteMap = allRouteMap tmplVars.AllRouteMap = allRouteMap
@ -652,7 +652,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
common.NotFound(w,req,nil) common.NotFound(w,req,nil)
return return
} }
counters.RouteViewCounter.Bump({{.AllRouteMap.routeUploads}}) counters.RouteViewCounter.Bump({{index .AllRouteMap "routes.UploadedFile" }})
req.URL.Path += extraData req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this? // TODO: Find a way to propagate errors up from this?
router.UploadHandler(w,req) // TODO: Count these views router.UploadHandler(w,req) // TODO: Count these views
@ -697,7 +697,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.RUnlock() router.RUnlock()
if ok { if ok {
counters.RouteViewCounter.Bump({{.AllRouteMap.routeDynamic}}) // TODO: Be more specific about *which* dynamic route it is counters.RouteViewCounter.Bump({{index .AllRouteMap "routes.DynamicRoute" }}) // TODO: Be more specific about *which* dynamic route it is
req.URL.Path += extraData req.URL.Path += extraData
err = handle(w,req,user) err = handle(w,req,user)
if err != nil { if err != nil {
@ -712,7 +712,7 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} else { } else {
router.DumpRequest(req,"Bad Route") router.DumpRequest(req,"Bad Route")
} }
counters.RouteViewCounter.Bump({{.AllRouteMap.BadRoute}}) counters.RouteViewCounter.Bump({{index .AllRouteMap "routes.BadRoute" }})
common.NotFound(w,req,nil) common.NotFound(w,req,nil)
} }
} }

View File

@ -7,9 +7,9 @@ func routes() {
addRoute(View("routes.CustomPage", "/pages/", "extraData")) addRoute(View("routes.CustomPage", "/pages/", "extraData"))
addRoute(View("routes.ForumList", "/forums/" /*,"&forums"*/)) addRoute(View("routes.ForumList", "/forums/" /*,"&forums"*/))
addRoute(View("routes.ViewForum", "/forum/", "extraData")) addRoute(View("routes.ViewForum", "/forum/", "extraData"))
addRoute(AnonAction("routeChangeTheme", "/theme/")) addRoute(AnonAction("routes.ChangeTheme", "/theme/"))
addRoute( addRoute(
View("routeShowAttachment", "/attachs/", "extraData").Before("ParseForm"), View("routes.ShowAttachment", "/attachs/", "extraData").Before("ParseForm"),
) )
// TODO: Reduce the number of Befores. With a new method, perhaps? // TODO: Reduce the number of Befores. With a new method, perhaps?
@ -74,7 +74,7 @@ func buildTopicRoutes() {
Action("routes.LockTopicSubmit", "/topic/lock/submit/").LitBefore("req.URL.Path += extraData"), Action("routes.LockTopicSubmit", "/topic/lock/submit/").LitBefore("req.URL.Path += extraData"),
Action("routes.UnlockTopicSubmit", "/topic/unlock/submit/", "extraData"), Action("routes.UnlockTopicSubmit", "/topic/unlock/submit/", "extraData"),
Action("routes.MoveTopicSubmit", "/topic/move/submit/", "extraData"), Action("routes.MoveTopicSubmit", "/topic/move/submit/", "extraData"),
Action("routeLikeTopicSubmit", "/topic/like/submit/", "extraData").Before("ParseForm"), Action("routes.LikeTopicSubmit", "/topic/like/submit/", "extraData").Before("ParseForm"),
) )
addRouteGroup(topicGroup) addRouteGroup(topicGroup)
} }
@ -88,7 +88,7 @@ func buildReplyRoutes() {
UploadAction("routes.CreateReplySubmit", "/reply/create/").MaxSizeVar("int(common.Config.MaxRequestSize)"), // TODO: Rename the route so it's /reply/create/submit/ UploadAction("routes.CreateReplySubmit", "/reply/create/").MaxSizeVar("int(common.Config.MaxRequestSize)"), // TODO: Rename the route so it's /reply/create/submit/
Action("routes.ReplyEditSubmit", "/reply/edit/submit/", "extraData"), Action("routes.ReplyEditSubmit", "/reply/edit/submit/", "extraData"),
Action("routes.ReplyDeleteSubmit", "/reply/delete/submit/", "extraData"), Action("routes.ReplyDeleteSubmit", "/reply/delete/submit/", "extraData"),
Action("routeReplyLikeSubmit", "/reply/like/submit/", "extraData").Before("ParseForm"), Action("routes.ReplyLikeSubmit", "/reply/like/submit/", "extraData").Before("ParseForm"),
) )
addRouteGroup(replyGroup) addRouteGroup(replyGroup)
} }
@ -98,7 +98,7 @@ func buildProfileReplyRoutes() {
//router.HandleFunc("/user/edit/submit/", routeLogout) // routeLogout? what on earth? o.o //router.HandleFunc("/user/edit/submit/", routeLogout) // routeLogout? what on earth? o.o
pReplyGroup := newRouteGroup("/profile/") pReplyGroup := newRouteGroup("/profile/")
pReplyGroup.Routes( pReplyGroup.Routes(
Action("routeProfileReplyCreateSubmit", "/profile/reply/create/"), // TODO: Add /submit/ to the end Action("routes.ProfileReplyCreateSubmit", "/profile/reply/create/"), // TODO: Add /submit/ to the end
Action("routes.ProfileReplyEditSubmit", "/profile/reply/edit/submit/", "extraData"), Action("routes.ProfileReplyEditSubmit", "/profile/reply/edit/submit/", "extraData"),
Action("routes.ProfileReplyDeleteSubmit", "/profile/reply/delete/submit/", "extraData"), Action("routes.ProfileReplyDeleteSubmit", "/profile/reply/delete/submit/", "extraData"),
) )
@ -120,7 +120,7 @@ func buildAccountRoutes() {
accReplyGroup.Routes( accReplyGroup.Routes(
View("routes.AccountLogin", "/accounts/login/"), View("routes.AccountLogin", "/accounts/login/"),
View("routes.AccountRegister", "/accounts/create/"), View("routes.AccountRegister", "/accounts/create/"),
Action("routeLogout", "/accounts/logout/"), Action("routes.AccountLogout", "/accounts/logout/"),
AnonAction("routes.AccountLoginSubmit", "/accounts/login/submit/"), // TODO: Guard this with a token, maybe the IP hashed with a rotated key? AnonAction("routes.AccountLoginSubmit", "/accounts/login/submit/"), // TODO: Guard this with a token, maybe the IP hashed with a rotated key?
AnonAction("routes.AccountRegisterSubmit", "/accounts/create/submit/"), AnonAction("routes.AccountRegisterSubmit", "/accounts/create/submit/"),
) )

View File

@ -7,7 +7,6 @@
package main package main
import ( import (
"html"
"net/http" "net/http"
"strconv" "strconv"
@ -20,51 +19,8 @@ var tList []interface{}
//var nList []string //var nList []string
var successJSONBytes = []byte(`{"success":"1"}`) var successJSONBytes = []byte(`{"success":"1"}`)
// HTTPSRedirect is a connection handler which redirects all HTTP requests to HTTPS
type HTTPSRedirect struct {
}
func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
dest := "https://" + req.Host + req.URL.Path
if len(req.URL.RawQuery) > 0 {
dest += "?" + req.URL.RawQuery
}
http.Redirect(w, req, dest, http.StatusTemporaryRedirect)
}
// Temporary stubs for view tracking
func routeDynamic() {
}
func routeUploads() {
}
func BadRoute() {
}
// TODO: Set the cookie domain
func routeChangeTheme(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
//headerLite, _ := SimpleUserCheck(w, r, &user)
// TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly?
isJs := (r.PostFormValue("isJs") == "1")
newTheme := html.EscapeString(r.PostFormValue("newTheme"))
theme, ok := common.Themes[newTheme]
if !ok || theme.HideFromThemes {
return common.LocalErrorJSQ("That theme doesn't exist", w, r, user, isJs)
}
cookie := http.Cookie{Name: "current_theme", Value: newTheme, Path: "/", MaxAge: int(common.Year)}
http.SetCookie(w, &cookie)
if !isJs {
http.Redirect(w, r, "/", http.StatusSeeOther)
} else {
_, _ = w.Write(successJSONBytes)
}
return nil
}
// TODO: Refactor this // TODO: Refactor this
// TODO: Use the phrase system
var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`) var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`)
// TODO: Refactor this endpoint // TODO: Refactor this endpoint
@ -81,8 +37,7 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
return common.PreErrorJS("Invalid Action", w, r) return common.PreErrorJS("Invalid Action", w, r)
} }
module := r.FormValue("module") switch r.FormValue("module") {
switch module {
case "dismiss-alert": case "dismiss-alert":
asid, err := strconv.Atoi(r.FormValue("asid")) asid, err := strconv.Atoi(r.FormValue("asid"))
if err != nil { if err != nil {

View File

@ -77,6 +77,15 @@ func AccountLoginSubmit(w http.ResponseWriter, r *http.Request, user common.User
return nil return nil
} }
func AccountLogout(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
if !user.Loggedin {
return common.LocalError("You can't logout without logging in first.", w, r, user)
}
common.Auth.Logout(w, user.ID)
http.Redirect(w, r, "/", http.StatusSeeOther)
return nil
}
func AccountRegister(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func AccountRegister(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
header, ferr := common.UserCheck(w, r, &user) header, ferr := common.UserCheck(w, r, &user)
if ferr != nil { if ferr != nil {

View File

@ -2,13 +2,17 @@ package routes
import ( import (
"bytes" "bytes"
"database/sql"
"html"
"io" "io"
"net/http" "net/http"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"../common" "../common"
"../query_gen/lib"
) )
var cacheControlMaxAge = "max-age=" + strconv.Itoa(int(common.Day)) // TODO: Make this a common.Config value var cacheControlMaxAge = "max-age=" + strconv.Itoa(int(common.Day)) // TODO: Make this a common.Config value
@ -86,3 +90,87 @@ func CustomPage(w http.ResponseWriter, r *http.Request, user common.User, name s
} }
return nil return nil
} }
type AttachmentStmts struct {
get *sql.Stmt
}
var attachmentStmts AttachmentStmts
// TODO: Move these DbInits into a TopicList abstraction
func init() {
common.DbInits.Add(func(acc *qgen.Accumulator) error {
attachmentStmts = AttachmentStmts{
get: acc.Select("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Where("path = ? AND sectionID = ? AND sectionTable = ?").Prepare(),
}
return acc.FirstError()
})
}
func ShowAttachment(w http.ResponseWriter, r *http.Request, user common.User, filename string) common.RouteError {
filename = common.Stripslashes(filename)
var ext = filepath.Ext("./attachs/" + filename)
//log.Print("ext ", ext)
//log.Print("filename ", filename)
if !common.AllowedFileExts.Contains(strings.TrimPrefix(ext, ".")) {
return common.LocalError("Bad extension", w, r, user)
}
sectionID, err := strconv.Atoi(r.FormValue("sectionID"))
if err != nil {
return common.LocalError("The sectionID is not an integer", w, r, user)
}
var sectionTable = r.FormValue("sectionType")
var originTable string
var originID, uploadedBy int
err = attachmentStmts.get.QueryRow(filename, sectionID, sectionTable).Scan(&sectionID, &sectionTable, &originID, &originTable, &uploadedBy, &filename)
if err == sql.ErrNoRows {
return common.NotFound(w, r, nil)
} else if err != nil {
return common.InternalError(err, w, r)
}
if sectionTable == "forums" {
_, ferr := common.SimpleForumUserCheck(w, r, &user, sectionID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic {
return common.NoPermissions(w, r, user)
}
} else {
return common.LocalError("Unknown section", w, r, user)
}
if originTable != "topics" && originTable != "replies" {
return common.LocalError("Unknown origin", w, r, user)
}
// TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side
http.ServeFile(w, r, "./attachs/"+filename)
return nil
}
// TODO: Set the cookie domain
func ChangeTheme(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
//headerLite, _ := SimpleUserCheck(w, r, &user)
// TODO: Rename isJs to something else, just in case we rewrite the JS side in WebAssembly?
isJs := (r.PostFormValue("isJs") == "1")
newTheme := html.EscapeString(r.PostFormValue("newTheme"))
theme, ok := common.Themes[newTheme]
if !ok || theme.HideFromThemes {
return common.LocalErrorJSQ("That theme doesn't exist", w, r, user, isJs)
}
cookie := http.Cookie{Name: "current_theme", Value: newTheme, Path: "/", MaxAge: int(common.Year)}
http.SetCookie(w, &cookie)
if !isJs {
http.Redirect(w, r, "/", http.StatusSeeOther)
} else {
_, _ = w.Write(successJSONBytes)
}
return nil
}

View File

@ -297,6 +297,41 @@ func ReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.User,
return nil return nil
} }
// TODO: Move the profile reply routes to their own file?
func ProfileReplyCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
if !user.Perms.ViewTopic || !user.Perms.CreateReply {
return common.NoPermissions(w, r, user)
}
uid, err := strconv.Atoi(r.PostFormValue("uid"))
if err != nil {
return common.LocalError("Invalid UID", w, r, user)
}
profileOwner, err := common.Users.Get(uid)
if err == sql.ErrNoRows {
return common.LocalError("The profile you're trying to post on doesn't exist.", w, r, user)
} else if err != nil {
return common.InternalError(err, w, r)
}
content := common.PreparseMessage(r.PostFormValue("reply-content"))
// TODO: Fully parse the post and store it in the parsed column
_, err = common.Prstore.Create(profileOwner.ID, content, user.ID, user.LastIP)
if err != nil {
return common.InternalError(err, w, r)
}
err = common.AddActivityAndNotifyTarget(user.ID, profileOwner.ID, "reply", "user", profileOwner.ID)
if err != nil {
return common.InternalError(err, w, r)
}
counters.PostCounter.Bump()
http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther)
return nil
}
func ProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError { func ProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError {
isJs := (r.PostFormValue("js") == "1") isJs := (r.PostFormValue("js") == "1")
@ -372,3 +407,64 @@ func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user commo
} }
return nil return nil
} }
func ReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError {
isJs := (r.PostFormValue("isJs") == "1")
rid, err := strconv.Atoi(srid)
if err != nil {
return common.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, isJs)
}
reply, err := common.Rstore.Get(rid)
if err == sql.ErrNoRows {
return common.PreErrorJSQ("You can't like something which doesn't exist!", w, r, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
topic, err := common.Topics.Get(reply.ParentID)
if err == sql.ErrNoRows {
return common.PreErrorJSQ("The parent topic doesn't exist.", w, r, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
// TODO: Add hooks to make use of headerLite
_, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
return common.NoPermissionsJSQ(w, r, user, isJs)
}
if reply.CreatedBy == user.ID {
return common.LocalErrorJSQ("You can't like your own replies", w, r, user, isJs)
}
_, err = common.Users.Get(reply.CreatedBy)
if err != nil && err != sql.ErrNoRows {
return common.LocalErrorJSQ("The target user doesn't exist", w, r, user, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
err = reply.Like(user.ID)
if err == common.ErrAlreadyLiked {
return common.LocalErrorJSQ("You've already liked this!", w, r, user, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
err = common.AddActivityAndNotifyTarget(user.ID, reply.CreatedBy, "like", "post", rid)
if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
if !isJs {
http.Redirect(w, r, "/topic/"+strconv.Itoa(reply.ParentID), http.StatusSeeOther)
} else {
_, _ = w.Write(successJSONBytes)
}
return nil
}

24
routes/stubs.go Normal file
View File

@ -0,0 +1,24 @@
package routes
import "net/http"
// HTTPSRedirect is a connection handler which redirects all HTTP requests to HTTPS
type HTTPSRedirect struct {
}
func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
dest := "https://" + req.Host + req.URL.Path
if len(req.URL.RawQuery) > 0 {
dest += "?" + req.URL.RawQuery
}
http.Redirect(w, req, dest, http.StatusTemporaryRedirect)
}
// Temporary stubs for view tracking
func DynamicRoute() {
}
func UploadedFile() {
}
func BadRoute() {
}

View File

@ -842,3 +842,59 @@ func addTopicAction(action string, topic *common.Topic, user common.User) error
} }
return topic.CreateActionReply(action, user.LastIP, user) return topic.CreateActionReply(action, user.LastIP, user)
} }
// TODO: Refactor this
func LikeTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {
isJs := (r.PostFormValue("isJs") == "1")
tid, err := strconv.Atoi(stid)
if err != nil {
return common.PreErrorJSQ("Topic IDs can only ever be numbers.", w, r, isJs)
}
topic, err := common.Topics.Get(tid)
if err == sql.ErrNoRows {
return common.PreErrorJSQ("The requested topic doesn't exist.", w, r, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
// TODO: Add hooks to make use of headerLite
_, ferr := common.SimpleForumUserCheck(w, r, &user, topic.ParentID)
if ferr != nil {
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.LikeItem {
return common.NoPermissionsJSQ(w, r, user, isJs)
}
if topic.CreatedBy == user.ID {
return common.LocalErrorJSQ("You can't like your own topics", w, r, user, isJs)
}
_, err = common.Users.Get(topic.CreatedBy)
if err != nil && err == sql.ErrNoRows {
return common.LocalErrorJSQ("The target user doesn't exist", w, r, user, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
score := 1
err = topic.Like(score, user.ID)
//log.Print("likeErr: ", err)
if err == common.ErrAlreadyLiked {
return common.LocalErrorJSQ("You already liked this", w, r, user, isJs)
} else if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
err = common.AddActivityAndNotifyTarget(user.ID, topic.CreatedBy, "like", "topic", tid)
if err != nil {
return common.InternalErrorJSQ(err, w, r, isJs)
}
if !isJs {
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
} else {
_, _ = w.Write(successJSONBytes)
}
return nil
}

View File

@ -1,5 +1,5 @@
{ {
"DBVersion":"2", "DBVersion":"3",
"DynamicFileVersion":"0", "DynamicFileVersion":"0",
"MinGoVersion":"1.10", "MinGoVersion":"1.10",
"MinVersion":"" "MinVersion":""

View File

@ -17,9 +17,15 @@
<div class="rowitem passive"> <div class="rowitem passive">
<a href="/panel/settings/word-filters/">{{lang "panel_menu_word_filters"}}</a> <a class="menu_stats" href="#">({{.Stats.WordFilters}})</a> <a href="/panel/settings/word-filters/">{{lang "panel_menu_word_filters"}}</a> <a class="menu_stats" href="#">({{.Stats.WordFilters}})</a>
</div>{{end}} </div>{{end}}
{{if .CurrentUser.Perms.ManageThemes}}<div class="rowitem passive"> {{if .CurrentUser.Perms.ManageThemes}}
<div class="rowitem passive">
<a href="/panel/themes/">{{lang "panel_menu_themes"}}</a> <a class="menu_stats" href="#">({{.Stats.Themes}})</a> <a href="/panel/themes/">{{lang "panel_menu_themes"}}</a> <a class="menu_stats" href="#">({{.Stats.Themes}})</a>
</div>{{end}} </div>
{{if eq .Zone "themes"}}
<div class="rowitem passive submenu"><a href="/panel/themes/menus/">Menus</a></div>
<div class="rowitem passive submenu"><a href="#">Widgets</a></div>
{{end}}
{{end}}
</div> </div>
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><a href="#">{{lang "panel_menu_events"}}</a></div> <div class="rowitem"><a href="#">{{lang "panel_menu_events"}}</a></div>

View File

@ -1,15 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
<nav class="colstack_left" aria-label="The control panel menu"> {{template "panel-menu.html" . }}
<div class="colstack_item colstack_head submenu_fallback">
<div class="rowitem"><a href="/panel/logs/mod/">{{lang "panel_logs_menu_head"}}</a></div>
</div>
<div class="colstack_item rowmenu submenu_fallback">
<div class="rowitem passive"><a href="/panel/logs/mod/">{{lang "panel_logs_menu_moderation"}}</a></div>
{{if .CurrentUser.Perms.ViewAdminLogs}}<div class="rowitem passive"><a>{{lang "panel_logs_menu_administration"}}</a></div>{{end}}
</div>
{{template "panel-inner-menu.html" . }}
</nav>
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_logs_administration_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_logs_administration_head"}}</h1></div>

View File

@ -1,15 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
<nav class="colstack_left" aria-label="The control panel menu"> {{template "panel-menu.html" . }}
<div class="colstack_item colstack_head submenu_fallback">
<div class="rowitem"><a href="/panel/logs/mod/">{{lang "panel_logs_menu_head"}}</a></div>
</div>
<div class="colstack_item rowmenu submenu_fallback">
<div class="rowitem passive"><a href="/panel/logs/mod/">{{lang "panel_logs_menu_moderation"}}</a></div>
{{if .CurrentUser.Perms.ViewAdminLogs}}<div class="rowitem passive"><a>{{lang "panel_logs_menu_administration"}}</a></div>{{end}}
</div>
{{template "panel-inner-menu.html" . }}
</nav>
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_logs_moderation_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_logs_moderation_head"}}</h1></div>

View File

@ -1,19 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }}
<nav class="colstack_left" aria-label="Theme manager menu">
<div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="/panel/themes/menus/">Menus</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="#">Widgets</a></div>
</div>
{{template "panel-inner-menu.html" . }}
</nav>
{{/** Stop inlining this x.x **/}} {{/** Stop inlining this x.x **/}}
<style type="text/css"> <style type="text/css">
.rowitem::after { .rowitem::after {

View File

@ -1,19 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{template "panel-menu.html" . }}
<nav class="colstack_left" aria-label="Theme manager menu">
<div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="/panel/themes/menus/">Menus</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="#">Widgets</a></div>
</div>
{{template "panel-inner-menu.html" . }}
</nav>
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_themes_menus_head"}}</h1></div>
@ -21,7 +8,7 @@
<div id="panel_settings" class="colstack_item rowlist"> <div id="panel_settings" class="colstack_item rowlist">
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem panel_compactrow editable_parent"> <div class="rowitem panel_compactrow editable_parent">
<a href="/panel/themes/menus/edit/{{.ID}}" class="editable_block panel_upshift">#{{.ID}}</a> <a href="/panel/themes/menus/edit/{{.ID}}" class="editable_block panel_upshift">{{if .Name}}{{.Name}} - {{end}}#{{.ID}}</a>
<a class="panel_compacttext to_right">{{.ItemCount}} items</a> <a class="panel_compacttext to_right">{{.ItemCount}} items</a>
</div> </div>
{{end}} {{end}}

View File

@ -1,20 +1,8 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
{{/** TODO: Set the order based on the order here **/}} {{/** TODO: Set the order based on the order here **/}}
{{/** TODO: Write the backend code and JS code for saving this menu **/}} {{/** TODO: Write the backend code and JS code for saving this menu **/}}
<nav class="colstack_left" aria-label="Theme manager menu"> {{template "panel-menu.html" . }}
<div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="/panel/themes/menus/">Menus</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="#">Widgets</a></div>
</div>
{{template "panel-inner-menu.html" . }}
</nav>
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_edit_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_themes_menus_edit_head"}}</h1></div>

View File

@ -1,17 +1,6 @@
{{template "header.html" . }} {{template "header.html" . }}
<div class="colstack panel_stack"> <div class="colstack panel_stack">
<nav class="colstack_left" aria-label="Control panel menu"> {{template "panel-menu.html" . }}
<div class="colstack_item colstack_head">
<div class="rowitem"><a href="/panel/themes/">Theme Manager</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="/panel/themes/menus/">Menus</a></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="#">Widgets</a></div>
</div>
{{template "panel-inner-menu.html" . }}
</nav>
<main class="colstack_right"> <main class="colstack_right">
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_themes_menus_head"}}</h1></div>

View File

@ -35,7 +35,7 @@ body {
font-size: 16px; font-size: 16px;
font-family: arial; font-family: arial;
margin: 0px; margin: 0px;
color: var(--primary-text-color); color: var(--lightened-primary-text-color);
} }
a { a {
text-decoration: none; text-decoration: none;
@ -228,9 +228,9 @@ ul {
margin-top: 8px; margin-top: 8px;
} }
.rowhead h1, .opthead h1, .colstack_head h1 { .rowhead h1, .opthead h1, .colstack_head h1 {
font-size: 20px; font-size: 19px;
font-weight: normal; font-weight: normal;
color: var(--primary-text-color); color: var(--lightened-primary-text-color);
display: inline-block; display: inline-block;
} }
.colstack_head a h1 { .colstack_head a h1 {
@ -905,6 +905,7 @@ textarea {
.postImage { .postImage {
width: 100%; width: 100%;
max-width: 150px;
} }
.post_item { .post_item {
display: flex; display: flex;

View File

@ -145,7 +145,7 @@
#panel_users .rowitem .rowTitle { #panel_users .rowitem .rowTitle {
border-bottom: 1px solid var(--lighter-text-color); border-bottom: 1px solid var(--lighter-text-color);
padding-bottom: 4px; padding-bottom: 4px;
margin-bottom: 4px; margin-bottom: 6px;
} }
#forum_quick_perms .formitem { #forum_quick_perms .formitem {

View File

32
themes/nox/theme.json Normal file
View File

@ -0,0 +1,32 @@
{
"Name": "nox",
"FriendlyName": "Nox",
"Version": "0.0.1",
"Creator": "Azareal",
"URL": "github.com/Azareal/Gosora",
"Tag": "WIP",
"HideFromThemes":true,
"Docks":["topMenu","rightSidebar","footer"],
"Templates": [
{
"Name": "topic",
"Source": "topic_alt"
}
],
"Resources": [
{
"Name":"EQCSS.js",
"Location":"global"
},
{
"Name":"trumbowyg/trumbowyg.min.js",
"Location":"global",
"Loggedin": true
},
{
"Name":"trumbowyg/ui/trumbowyg.custom.css",
"Location":"global",
"Loggedin":true
}
]
}

View File

@ -510,7 +510,7 @@ input, select, textarea {
} }
.topic_create_form input, .topic_create_form select { .topic_create_form input, .topic_create_form select {
padding: 7px; padding: 7px;
font-family: monospace; font-size: 13px;
} }
.topic_create_form select { .topic_create_form select {
padding: 6px; padding: 6px;
@ -519,8 +519,11 @@ input, select, textarea {
width: calc(100% - 14px); width: calc(100% - 14px);
} }
.topic_create_form textarea, .topic_reply_form textarea { .topic_create_form textarea, .topic_reply_form textarea {
width: calc(100% - 5px); width: calc(100% - 26px);
min-height: 80px; min-height: 80px;
font-family: arial;
font-size: 14px;
padding: 12px;
} }
.topic_create_form textarea { .topic_create_form textarea {
padding: 7px; padding: 7px;
@ -690,8 +693,8 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
} }
.topic_left.topic_sticky .topic_inner_left { .topic_left.topic_sticky .topic_inner_left {
border-top: 4px solid hsl(41, 100%, 50%); border-top: 4px solid hsl(41, 100%, 50%);
padding-left: 8px; padding-left: 10px;
padding-top: 8px; padding-top: 10px;
margin-top: 0px; margin-top: 0px;
margin-left: 0px; margin-left: 0px;
width: 100%; width: 100%;

View File

@ -1,5 +1,6 @@
.submenu a { .submenu:before {
margin-left: 4px; content: "-";
margin-right: 6px;
} }
.colstack_head .rowitem { .colstack_head .rowitem {
display: flex; display: flex;
@ -68,16 +69,16 @@
padding-right: 2px; padding-right: 2px;
} }
.panel_submitrow { .panel_submitrow .rowitem {
display: flex; display: flex;
} }
.panel_submitrow *:first-child { .panel_submitrow .rowitem *:first-child {
margin-left: auto; margin-left: auto;
} }
.panel_submitrow *:last-child { .panel_submitrow .rowitem *:last-child {
margin-right: auto; margin-right: auto;
} }
.panel_submitrow button { .panel_submitrow .rowitem button {
padding-top: 5px; padding-top: 5px;
padding-bottom: 5px; padding-bottom: 5px;
} }

View File

@ -769,7 +769,9 @@ button.username {
margin-top: 20px; margin-top: 20px;
} }
#profile_right_lane .topic_reply_form { #profile_right_lane .topic_reply_form {
border-bottom: 1.5px inset var(--main-border-color); border-bottom: 2px inset var(--main-border-color);
border-left: 1px solid var(--main-border-color);
border-right: 1px solid var(--main-border-color);
} }
/* TODO: Show the avatar next to the reply form */ /* TODO: Show the avatar next to the reply form */
@ -924,7 +926,7 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
border-bottom: none; border-bottom: none;
} }
.topic_reply_form { .topic_reply_form {
border-top: 1px solid var(--main-border-color); border: none;
} }
.post_container .post_item { .post_container .post_item {
background-color: #eaeaea; background-color: #eaeaea;

View File

@ -1,3 +1,6 @@
.sidebar {
display: none;
}
/*.submenu { /*.submenu {
padding-left: 18px; padding-left: 18px;
}*/ }*/
@ -102,8 +105,13 @@
padding-left: 2px; padding-left: 2px;
padding-right: 2px; padding-right: 2px;
} }
.colstack_item + .panel_submitrow { .panel_submitrow {
margin-bottom: 0px; margin-top: -12px;
border-top: none;
}
.panel_submitrow button {
padding-top: 3px;
padding-bottom: 3px;
} }
.ct_chart { .ct_chart {

View File

@ -1,3 +1,9 @@
.sidebar {
display: none;
}
.submenu:before {
content: "-";
}
.submenu a { .submenu a {
margin-left: 8px; margin-left: 8px;
} }
@ -118,8 +124,13 @@
display: none; display: none;
} }
.colstack_item + .panel_submitrow { .panel_submitrow {
margin-bottom: 0px; margin-top: -12px;
border-top: none;
}
.panel_submitrow button {
padding-top: 3px;
padding-bottom: 3px;
} }
#panel_word_filters .itemSeparator:before { #panel_word_filters .itemSeparator:before {