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 (
"bytes"
"errors"
"fmt"
"mime"
"strings"
"sync"
@ -38,9 +37,10 @@ type CSSData struct {
}
func (list SFileList) JSTmplInit() error {
DebugLog("Initialising the client side templates")
var fragMap = make(map[string][][]byte)
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 {
if f.IsDir() {
return nil
@ -75,15 +75,15 @@ func (list SFileList) JSTmplInit() error {
if !hasBrace {
return errors.New("no right brace found after the template function name")
}
fmt.Println("spaceIndex: ", spaceIndex)
fmt.Println("endBrace: ", endBrace)
fmt.Println("string(data[spaceIndex:endBrace]): ", string(data[spaceIndex:endBrace]))
//fmt.Println("spaceIndex: ", spaceIndex)
//fmt.Println("endBrace: ", endBrace)
//fmt.Println("string(data[spaceIndex:endBrace]): ", string(data[spaceIndex:endBrace]))
preLen := len(data)
data = replace(data, string(data[spaceIndex:endBrace]), "")
data = replace(data, "))\n", "\n")
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 = "["
for j, char := range data {
if index == j {
@ -93,16 +93,16 @@ func (list SFileList) JSTmplInit() error {
}
}
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
var each = func(phrase string, handle func(index int)) {
fmt.Println("find each '" + phrase + "'")
//fmt.Println("find each '" + phrase + "'")
var index = endBrace
var foundIt bool
for {
fmt.Println("in index: ", index)
fmt.Println("pos: ", showPos(data, index))
//fmt.Println("in index: ", index)
//fmt.Println("pos: ", showPos(data, index))
index, foundIt = skipAllUntilCharsExist(data, index, []byte(phrase))
if !foundIt {
break
@ -136,7 +136,7 @@ func (list SFileList) JSTmplInit() error {
}
})
each("if ", func(index int) {
fmt.Println("if index: ", index)
//fmt.Println("if index: ", index)
braceAt, hasBrace := skipUntilIfExists(data, index, '{')
if hasBrace {
if data[braceAt-1] != ' ' {
@ -161,7 +161,7 @@ func (list SFileList) JSTmplInit() error {
tmplName := strings.TrimSuffix(path, ".go")
fragset, ok := fragMap[strings.TrimPrefix(tmplName, "template_")]
if !ok {
fmt.Println("tmplName: ", tmplName)
DebugLog("tmplName: ", tmplName)
return errors.New("couldn't find template in fragmap")
}

View File

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

View File

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

View File

@ -21,7 +21,6 @@ type Stmts struct {
getTopicBasic *sql.Stmt
forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt
getAttachment *sql.Stmt
getForumTopics *sql.Stmt
createReport *sql.Stmt
addForumPermsToForum *sql.Stmt
@ -154,14 +153,6 @@ func _gen_mssql() (err error) {
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.")
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 {

View File

@ -23,7 +23,6 @@ type Stmts struct {
getTopicBasic *sql.Stmt
forumEntryExists *sql.Stmt
groupEntryExists *sql.Stmt
getAttachment *sql.Stmt
getForumTopics *sql.Stmt
createReport *sql.Stmt
addForumPermsToForum *sql.Stmt
@ -144,13 +143,6 @@ func _gen_mysql() (err error) {
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.")
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 {

View File

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

View File

@ -701,6 +701,7 @@
"panel_themes_make_default":"Make Default",
"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_delete_button_aria":"Delete menu item",

View File

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

View File

@ -2,166 +2,12 @@ package main
import (
"net/http"
"path/filepath"
"strconv"
"strings"
"./common"
"./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 {
itemID, err := strconv.Atoi(sitemID)
if err != nil {
@ -292,9 +138,13 @@ func routeAccountEditEmail(w http.ResponseWriter, r *http.Request, user common.U
email.Primary = true
emailList = append(emailList, email)
}
if !common.Site.EnableEmails {
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}
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)
}
}
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
}

View File

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

View File

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

View File

@ -152,3 +152,57 @@ func patch1(scanner *bufio.Scanner) error {
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(){
runHook("start_init");
$.getScript( "./static/template_alert.js", () => {
loadScript("template_alert.js",() => {
console.log("Loaded template_alert.js");
alertsInitted = true;
var alertMenuList = document.getElementsByClassName("menu_alerts");
for(var i = 0; i < alertMenuList.length; i++) {
loadAlerts(alertMenuList[i]);
}
});
})
if(window["WebSocket"]) runWebSockets();
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("getAttachment").Table("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Where("path = ? AND sectionID = ? AND sectionTable = ?").Parse()
return nil
}

View File

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

View File

@ -7,9 +7,9 @@ func routes() {
addRoute(View("routes.CustomPage", "/pages/", "extraData"))
addRoute(View("routes.ForumList", "/forums/" /*,"&forums"*/))
addRoute(View("routes.ViewForum", "/forum/", "extraData"))
addRoute(AnonAction("routeChangeTheme", "/theme/"))
addRoute(AnonAction("routes.ChangeTheme", "/theme/"))
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?
@ -74,7 +74,7 @@ func buildTopicRoutes() {
Action("routes.LockTopicSubmit", "/topic/lock/submit/").LitBefore("req.URL.Path += extraData"),
Action("routes.UnlockTopicSubmit", "/topic/unlock/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)
}
@ -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/
Action("routes.ReplyEditSubmit", "/reply/edit/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)
}
@ -98,7 +98,7 @@ func buildProfileReplyRoutes() {
//router.HandleFunc("/user/edit/submit/", routeLogout) // routeLogout? what on earth? o.o
pReplyGroup := newRouteGroup("/profile/")
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.ProfileReplyDeleteSubmit", "/profile/reply/delete/submit/", "extraData"),
)
@ -120,7 +120,7 @@ func buildAccountRoutes() {
accReplyGroup.Routes(
View("routes.AccountLogin", "/accounts/login/"),
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.AccountRegisterSubmit", "/accounts/create/submit/"),
)

View File

@ -7,7 +7,6 @@
package main
import (
"html"
"net/http"
"strconv"
@ -20,51 +19,8 @@ var tList []interface{}
//var nList []string
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: Use the phrase system
var phraseLoginAlerts = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`)
// 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)
}
module := r.FormValue("module")
switch module {
switch r.FormValue("module") {
case "dismiss-alert":
asid, err := strconv.Atoi(r.FormValue("asid"))
if err != nil {

View File

@ -77,6 +77,15 @@ func AccountLoginSubmit(w http.ResponseWriter, r *http.Request, user common.User
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 {
header, ferr := common.UserCheck(w, r, &user)
if ferr != nil {

View File

@ -2,13 +2,17 @@ package routes
import (
"bytes"
"database/sql"
"html"
"io"
"net/http"
"path/filepath"
"strconv"
"strings"
"time"
"../common"
"../query_gen/lib"
)
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
}
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
}
// 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 {
isJs := (r.PostFormValue("js") == "1")
@ -372,3 +407,64 @@ func ProfileReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user commo
}
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)
}
// 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",
"MinGoVersion":"1.10",
"MinVersion":""

View File

@ -17,9 +17,15 @@
<div class="rowitem passive">
<a href="/panel/settings/word-filters/">{{lang "panel_menu_word_filters"}}</a> <a class="menu_stats" href="#">({{.Stats.WordFilters}})</a>
</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>
</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 class="colstack_item colstack_head">
<div class="rowitem"><a href="#">{{lang "panel_menu_events"}}</a></div>

View File

@ -1,15 +1,6 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
<nav class="colstack_left" aria-label="The control panel menu">
<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>
{{template "panel-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_logs_administration_head"}}</h1></div>

View File

@ -1,15 +1,6 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
<nav class="colstack_left" aria-label="The control panel menu">
<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>
{{template "panel-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_logs_moderation_head"}}</h1></div>

View File

@ -1,19 +1,6 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
<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>
{{template "panel-menu.html" . }}
{{/** Stop inlining this x.x **/}}
<style type="text/css">
.rowitem::after {

View File

@ -1,19 +1,6 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
<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>
{{template "panel-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_head"}}</h1></div>
@ -21,7 +8,7 @@
<div id="panel_settings" class="colstack_item rowlist">
{{range .ItemList}}
<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>
</div>
{{end}}

View File

@ -1,20 +1,8 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
{{/** TODO: Set the order based on the order here **/}}
{{/** TODO: Write the backend code and JS code for saving this menu **/}}
<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>
{{template "panel-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_edit_head"}}</h1></div>

View File

@ -1,17 +1,6 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
<nav class="colstack_left" aria-label="Control panel 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>
{{template "panel-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_head"}}</h1></div>

View File

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

View File

@ -145,7 +145,7 @@
#panel_users .rowitem .rowTitle {
border-bottom: 1px solid var(--lighter-text-color);
padding-bottom: 4px;
margin-bottom: 4px;
margin-bottom: 6px;
}
#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 {
padding: 7px;
font-family: monospace;
font-size: 13px;
}
.topic_create_form select {
padding: 6px;
@ -519,8 +519,11 @@ input, select, textarea {
width: calc(100% - 14px);
}
.topic_create_form textarea, .topic_reply_form textarea {
width: calc(100% - 5px);
width: calc(100% - 26px);
min-height: 80px;
font-family: arial;
font-size: 14px;
padding: 12px;
}
.topic_create_form textarea {
padding: 7px;
@ -690,8 +693,8 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
}
.topic_left.topic_sticky .topic_inner_left {
border-top: 4px solid hsl(41, 100%, 50%);
padding-left: 8px;
padding-top: 8px;
padding-left: 10px;
padding-top: 10px;
margin-top: 0px;
margin-left: 0px;
width: 100%;

View File

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

View File

@ -769,7 +769,9 @@ button.username {
margin-top: 20px;
}
#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 */
@ -924,7 +926,7 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
border-bottom: none;
}
.topic_reply_form {
border-top: 1px solid var(--main-border-color);
border: none;
}
.post_container .post_item {
background-color: #eaeaea;

View File

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

View File

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