diff --git a/README.md b/README.md index d854f7f3..bca745cf 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,14 @@ In-memory static file, forum and group caches. A profile system including profile comments and moderation tools for the profile owner. +A template engine which compiles templates down into machine code. Over ten times faster than html/templates. + A plugin system. # Dependencies -Go. The programming language this program is written in, and the compiler which it requires. You will need to install this. https://golang.org/doc/install +Go 1.7. The programming language this program is written in, and the compiler which it requires. You will need to install this. https://golang.org/doc/install MySQL Database. You will need to setup a MySQL Database somewhere. A MariaDB Database works equally well, and is much faster than MySQL. If you're curious about how to install this, you might want to try the WNMP or XAMPP bundles on Windows. diff --git a/config.go b/config.go index e610a9ee..ef9ebec7 100644 --- a/config.go +++ b/config.go @@ -23,4 +23,4 @@ var noavatar = "https://api.adorable.io/avatars/285/{id}@" + siteurl + ".png" var items_per_page = 50 // Developer flag -var debug = true \ No newline at end of file +var debug = false \ No newline at end of file diff --git a/data.sql b/data.sql index ce190c66..ddf955c3 100644 --- a/data.sql +++ b/data.sql @@ -11,6 +11,7 @@ CREATE TABLE `users`( `createdAt` datetime not null, `lastActiveAt` datetime not null, `session` varchar(200) DEFAULT '' not null, + `last_ip` varchar(200) DEFAULT '0.0.0.0.0' not null, `email` varchar(200) DEFAULT '' not null, `avatar` varchar(20) DEFAULT '' not null, `message` text not null, @@ -54,6 +55,7 @@ CREATE TABLE `topics`( `is_closed` tinyint DEFAULT 0 not null, `sticky` tinyint DEFAULT 0 not null, `parentID` int DEFAULT 1 not null, + `ipaddress` varchar(200) DEFAULT '0.0.0.0.0' not null, `data` varchar(200) DEFAULT '' not null, primary key(`tid`) ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; @@ -67,6 +69,7 @@ CREATE TABLE `replies`( `createdBy` int not null, `lastEdit` int not null, `lastEditBy` int not null, + `ipaddress` varchar(200) DEFAULT '0.0.0.0.0' not null, primary key(`rid`) ) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; diff --git a/experimental/center-experiment.css b/experimental/center-experiment.css new file mode 100644 index 00000000..22be1bae --- /dev/null +++ b/experimental/center-experiment.css @@ -0,0 +1,333 @@ +* { + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; +} + +body +{ + font-family: arial; +} + +@font-face { + font-family: 'EmojiFont'; + src: url('https://github.com/Ranks/emojione/raw/master/assets/fonts/emojione-svg.woff2') format('woff2'), + url('https://github.com/Ranks/emojione/raw/master/assets/fonts/emojione-svg.woff') format('woff'), local("arial"); +} + +@supports (-ms-ime-align:auto) { +.user_content +{ + font-family: EmojiFont, arial; +} +} +@-moz-document url-prefix() { +.user_content +{ + font-family: EmojiFont, arial; +} +} + +.move_left +{ + float: left; + position: relative; + left: 50%; +} +.move_right +{ + float: left; + position: relative; + left: -50%; +} +ul +{ + padding-left: 0px; + padding-right: 0px; + height: 28px; + list-style-type: none; +} +li +{ + height: 28px; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + padding-left: 10px; + padding-top: 5px; + padding-bottom: 5px; + font-weight: bold; + text-transform: uppercase; +} +li a +{ + text-decoration: none; + color: #515151; +} +li a:hover +{ + color: #7a7a7a; +} +.menu_left +{ + float: left; + border-right: 1px solid #ccc; + padding-right: 10px; +} +.menu_left:first-child +{ + border-left: 1px solid #ccc +} +.menu_right +{ + float: right; + border-left: 1px solid #ccc; + padding-right: 10px; +} + +.container +{ + width: 90%; + padding: 0px; + margin-left: auto; + margin-right: auto; +} + +.rowblock +{ + border: 1px solid #ccc; + width: 100%; + padding: 0px; + padding-top: 0px; +} +.rowblock:empty +{ + display: none; +} + +.colblock_left +{ + border: 1px solid #ccc; + padding: 0px; + padding-top: 0px; + width: 30%; + float: left; + margin-right: 8px; +} +.colblock_right +{ + border: 1px solid #ccc; + padding: 0px; + padding-top: 0px; + width: 65%; + overflow: hidden; + word-wrap: break-word; +} +.colblock_left:empty +{ + display: none; +} +.colblock_right:empty +{ + display: none; +} + +.rowitem +{ + width: 100%; + padding-left: 8px; + padding-right: 8px; + padding-top: 17px; + padding-bottom: 12px; + font-weight: bold; + text-transform: uppercase; +} +.rowitem.passive +{ + font-weight: normal; + text-transform: none; +} +.rowitem:not(:last-child)/*:not(:only-child)*/ +{ + border-bottom: 1px dotted #ccc; +} +.rowitem a +{ + text-decoration: none; + color: black; +} +.rowitem a:hover +{ + color: silver; +} + +.col_left +{ + width: 30%; + float: left; +} +.col_right +{ + width: 69%; + overflow: hidden; +} +.colitem +{ + padding-left: 8px; + padding-right: 8px; + padding-top: 17px; + padding-bottom: 12px; + font-weight: bold; + text-transform: uppercase; +} +.colitem.passive +{ + font-weight: normal; + text-transform: none; +} +.colitem a +{ + text-decoration: none; + color: black; +} +.colitem a:hover +{ + color: silver; +} + +.formrow +{ + /*height: 40px;*/ + width: 100%; +} + +/*Clearfix*/ +.formrow:before, +.formrow:after { + content: " "; + display: table; +} + +.formrow:after { + clear: both; +} + +.formrow:not(:last-child) +{ + border-bottom: 1px dotted #ccc; +} + +.formitem +{ + float: left; + padding-left: 8px; + padding-right: 8px; + padding-top: 13px; + padding-bottom: 8px; + font-weight: bold; +} + +.formitem:first-child +{ + font-weight: bold; +} + +.formitem:not(:last-child) +{ + border-right: 1px dotted #ccc; +} + +.formitem.invisible_border +{ + border: none; +} + +/* Mostly for textareas */ +.formitem:only-child +{ + width: 97%; +} +.formitem textarea +{ + width: 100%; + height: 100px; +} +.formitem:has-child() +{ + margin: 0 auto; + float: none; +} + +button +{ + background: white; + border: 1px solid #8e8e8e; +} + +/* Topics */ +.topic_status +{ + text-transform: none; + margin-left: 8px; + padding-left: 2px; + padding-right: 2px; + padding-top: 2px; + padding-bottom: 2px; + background-color: #E8E8E8; /* 232,232,232. All three RGB colours being the same seems to create a shade of gray */ + color: #505050; /* 80,80,80 */ + border-radius: 2px; +} + +.topic_status:empty +{ + display: none; +} + +.username +{ + text-transform: none; + margin-left: 0px; + padding-left: 4px; + padding-right: 4px; + padding-top: 2px; + padding-bottom: 2px; + color: #505050; /* 80,80,80 */ + background-color: #FFFFFF; + border-style: dotted; + border-color: #505050; /* 232,232,232. All three RGB colours being the same seems to create a shade of gray */ + border-width: 1px; + font-size: 15px; +} +button.username +{ + position: relative; + top: -0.25px; +} + +.show_on_edit +{ + display: none; +} + +.alert +{ + display: block; + padding: 5px; + margin-bottom: 10px; + border: 1px solid #ccc; +} +.alert_success +{ + display: block; + padding: 5px; + border: 1px solid A2FC00; + margin-bottom: 10px; + background-color: DAF7A6; +} +.alert_error +{ + display: block; + padding: 5px; + border: 1px solid #FF004B; + margin-bottom: 8px; + background-color: #FEB7CC; +} \ No newline at end of file diff --git a/experimental/plugin_sendmail.go b/experimental/plugin_sendmail.go index dc3159f8..9b250d34 100644 --- a/experimental/plugin_sendmail.go +++ b/experimental/plugin_sendmail.go @@ -14,7 +14,7 @@ func init() { } func init_sendmail() { - add_vhook("email_send_intercept", send_sendmail) + add_hook("email_send_intercept", send_sendmail) } // Sendmail is only available on Linux diff --git a/extend.go b/extend.go index 8bc50c15..546ff1c1 100644 --- a/extend.go +++ b/extend.go @@ -20,8 +20,19 @@ type Plugin struct Deactivate func() } -func add_hook(name string, handler func(interface{})interface{}) { +/*func add_hook(name string, handler func(interface{})interface{}) { hooks[name] = handler +}*/ + +func add_hook(name string, handler interface{}) { + switch h := handler.(type) { + case func(interface{})interface{}: + hooks[name] = h + case func(...interface{}) interface{}: + vhooks[name] = h + default: + panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()? + } } func remove_hook(name string) { @@ -32,10 +43,6 @@ func run_hook(name string, data interface{}) interface{} { return hooks[name](data) } -func add_vhook(name string, handler func(...interface{}) interface{}) { - vhooks[name] = handler -} - func remove_vhook(name string) { delete(vhooks, name) } diff --git a/general_test.go b/general_test.go index eb6f4701..758feccc 100644 --- a/general_test.go +++ b/general_test.go @@ -10,25 +10,45 @@ func BenchmarkTemplates(b *testing.B) { noticeList[0] = "test" topic := TopicUser{0,"Lol",template.HTML("Hey everyone!"),0,false,false,"",0,"","","",no_css_tmpl,0,"","","",""} + var replyList map[int]interface{} = make(map[int]interface{}) replyList[0] = Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""} - pi := Page{"Topic Blah","topic",user,noticeList,replyList,topic} - pi2 := Page{"Topic Blah","topic",admin,noticeList,replyList,topic} + replyList[1] = Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""} + replyList[2] = Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""} + replyList[3] = Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""} + replyList[4] = Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""} + replyList[5] = Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""} + replyList[6] = Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""} + replyList[7] = Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""} + replyList[8] = Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""} + replyList[9] = Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""} + + var replyList2 []interface{} + replyList2 = append(replyList2, Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""}) + replyList2 = append(replyList2, Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""}) + replyList2 = append(replyList2, Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""}) + replyList2 = append(replyList2, Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""}) + replyList2 = append(replyList2, Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""}) + replyList2 = append(replyList2, Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""}) + replyList2 = append(replyList2, Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""}) + replyList2 = append(replyList2, Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""}) + replyList2 = append(replyList2, Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""}) + replyList2 = append(replyList2, Reply{0,0,"Hey everyone!",template.HTML("Hey everyone!"),0,"","",0,0,"",no_css_tmpl,0,"","","",""}) + + pi := Page2{"Topic Blah","topic",user,noticeList,replyList,topic} + pi2 := Page2{"Topic Blah","topic",admin,noticeList,replyList,topic} + pi3 := Page{"Topic Blah","topic",user,noticeList,replyList2,topic} + pi4 := Page{"Topic Blah","topic",admin,noticeList,replyList2,topic} w := ioutil.Discard - b.Run("compiled_writer_collated_useradmin", func(b *testing.B) { + b.Run("compiled_sliceloop_useradmin", func(b *testing.B) { for i := 0; i < b.N; i++ { - template_topic(pi2,w) + template_topic(pi4,w) } }) - b.Run("compiled_writer_useradmin", func(b *testing.B) { + b.Run("compiled_maploop_useradmin", func(b *testing.B) { for i := 0; i < b.N; i++ { - template_topic2(pi2,w) - } - }) - b.Run("compiled_useradmin", func(b *testing.B) { - for i := 0; i < b.N; i++ { - w.Write([]byte(template_topic3(pi2))) + template_topic_maploop(pi2,w) } }) b.Run("interpreted_useradmin", func(b *testing.B) { @@ -36,19 +56,15 @@ func BenchmarkTemplates(b *testing.B) { templates.ExecuteTemplate(w,"topic.html", pi2) } }) - b.Run("compiled_writer_collated_userguest", func(b *testing.B) { + + b.Run("compiled_sliceloop_userguest", func(b *testing.B) { for i := 0; i < b.N; i++ { - template_topic(pi,w) + template_topic(pi3,w) } }) - b.Run("compiled_writer_userguest", func(b *testing.B) { + b.Run("compiled_maploop_userguest", func(b *testing.B) { for i := 0; i < b.N; i++ { - template_topic2(pi,w) - } - }) - b.Run("compiled_userguest", func(b *testing.B) { - for i := 0; i < b.N; i++ { - w.Write([]byte(template_topic3(pi))) + template_topic_maploop(pi,w) } }) b.Run("interpreted_userguest", func(b *testing.B) { diff --git a/gosora.exe b/gosora.exe index e07ce32f..d0a4f659 100644 Binary files a/gosora.exe and b/gosora.exe differ diff --git a/gosora.exe~ b/gosora.exe~ new file mode 100644 index 00000000..e693a454 Binary files /dev/null and b/gosora.exe~ differ diff --git a/images/notices.PNG b/images/notices.PNG new file mode 100644 index 00000000..217e4c8b Binary files /dev/null and b/images/notices.PNG differ diff --git a/images/gosora-test.PNG b/images/test_compiled_vs_interpreted.PNG similarity index 100% rename from images/gosora-test.PNG rename to images/test_compiled_vs_interpreted.PNG diff --git a/images/test_maps_vs_slices.PNG b/images/test_maps_vs_slices.PNG new file mode 100644 index 00000000..c0d246fa Binary files /dev/null and b/images/test_maps_vs_slices.PNG differ diff --git a/main.go b/main.go index ec5cabb5..df6d14e2 100644 --- a/main.go +++ b/main.go @@ -43,22 +43,43 @@ func compile_templates() { log.Print("Compiling the templates") topic := TopicUser{0,"",template.HTML(""),0,false,false,"",0,"","","",no_css_tmpl,0,"","","",""} - var replyList map[int]interface{} = make(map[int]interface{}) - replyList[0] = Reply{0,0,"",template.HTML(""),0,"","",0,0,"",no_css_tmpl,0,"","","",""} + var replyList []interface{} + replyList = append(replyList, Reply{0,0,"",template.HTML(""),0,"","",0,0,"",no_css_tmpl,0,"","","",""}) var varList map[string]VarItem = make(map[string]VarItem) varList["extra_data"] = VarItem{"extra_data","tmpl_topic_vars.Something.(TopicUser)","TopicUser"} pi := Page{"Title","name",user,noticeList,replyList,topic} - topic_id := c.compile_template("topic.html","templates/","Page", pi, varList) + topic_id_tmpl := c.compile_template("topic.html","templates/","Page", pi, varList) varList = make(map[string]VarItem) varList["extra_data"] = VarItem{"extra_data","tmpl_profile_vars.Something.(User)","User"} pi = Page{"Title","name",user,noticeList,replyList,user} - profile := c.compile_template("profile.html","templates/","Page", pi, varList) + profile_tmpl := c.compile_template("profile.html","templates/","Page", pi, varList) + + var forumList []interface{} + for _, forum := range forums { + if forum.Active { + forumList = append(forumList, forum) + } + } + varList = make(map[string]VarItem) + pi = Page{"Forum List","forums",user,noticeList,forumList,0} + forums_tmpl := c.compile_template("forums.html","templates/","Page", pi, varList) + + var topicList []interface{} + topicList = append(topicList, TopicUser{1,"Topic Title","The topic content.",1,false,false,"",1,"open","Admin","","",0,"","","",""}) + pi = Page{"Topic List","topics",user,noticeList,topicList,""} + topics_tmpl := c.compile_template("topics.html","templates/","Page", pi, varList) + + pi = Page{"General Forum","forum",user,noticeList,topicList,"There aren't any topics in this forum yet."} + forum_tmpl := c.compile_template("forum.html","templates/","Page", pi, varList) log.Print("Writing the templates") - write_template("topic", topic_id) - write_template("profile", profile) + write_template("topic", topic_id_tmpl) + write_template("profile", profile_tmpl) + write_template("forums", forums_tmpl) + write_template("topics", topics_tmpl) + write_template("forum", forum_tmpl) } func write_template(name string, content string) { @@ -77,8 +98,8 @@ func write_template(name string, content string) { func main(){ var err error - compile_templates() init_database(err); + compile_templates() log.Print("Loading the static files.") err = filepath.Walk("./public", func(path string, f os.FileInfo, err error) error { @@ -167,6 +188,7 @@ func main(){ http.HandleFunc("/users/ban/", route_ban) http.HandleFunc("/users/ban/submit/", route_ban_submit) http.HandleFunc("/users/unban/", route_unban) + http.HandleFunc("/users/activate/", route_activate) // Admin http.HandleFunc("/panel/forums/", route_panel_forums) diff --git a/mod_routes.go b/mod_routes.go index dc470f2d..23d5cf1b 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -454,7 +454,6 @@ func route_unban(w http.ResponseWriter, r *http.Request) { if !ok { return } - if !user.Is_Mod && !user.Is_Admin { NoPermissions(w,r,user) return @@ -490,6 +489,46 @@ func route_unban(w http.ResponseWriter, r *http.Request) { http.Redirect(w,r,"/users/" + strconv.Itoa(uid),http.StatusSeeOther) } +func route_activate(w http.ResponseWriter, r *http.Request) { + user, ok := SimpleSessionCheck(w,r) + if !ok { + return + } + if !user.Is_Mod && !user.Is_Admin { + NoPermissions(w,r,user) + return + } + + uid, err := strconv.Atoi(r.URL.Path[len("/users/activate/"):]) + if err != nil { + LocalError("The provided User ID is not a valid number.",w,r,user) + return + } + + var uname string + var active bool + err = db.QueryRow("SELECT `name`, `active` from users where `uid` = ?", uid).Scan(&uname, &active) + if err == sql.ErrNoRows { + LocalError("The account you're trying to activate no longer exists.",w,r,user) + return + } else if err != nil { + InternalError(err,w,r,user) + return + } + + if active { + LocalError("The account you're trying to activate has already been activated.",w,r,user) + return + } + + _, err = activate_user_stmt.Exec(uid) + if err != nil { + InternalError(err,w,r,user) + return + } + http.Redirect(w,r,"/users/" + strconv.Itoa(uid),http.StatusSeeOther) +} + func route_panel_forums(w http.ResponseWriter, r *http.Request){ user, noticeList, ok := SessionCheck(w,r) if !ok { @@ -501,13 +540,10 @@ func route_panel_forums(w http.ResponseWriter, r *http.Request){ return } - var forumList map[int]interface{} = make(map[int]interface{}) - currentID := 0 - + var forumList []interface{} for _, forum := range forums { if forum.ID > -1 { - forumList[currentID] = forum - currentID++ + forumList = append(forumList, forum) } } @@ -778,18 +814,14 @@ func route_panel_plugins(w http.ResponseWriter, r *http.Request){ if !ok { return } - if !user.Is_Admin { NoPermissions(w,r,user) return } - var pluginList map[int]interface{} = make(map[int]interface{}) - currentID := 0 - + var pluginList []interface{} for _, plugin := range plugins { - pluginList[currentID] = plugin - currentID++ + pluginList = append(pluginList, plugin) } pi := Page{"Plugin Manager","panel-plugins",user,noticeList,pluginList,0} @@ -911,9 +943,7 @@ func route_panel_users(w http.ResponseWriter, r *http.Request){ return } - var userList map[int]interface{} = make(map[int]interface{}) - currentID := 0 - + var userList []interface{} rows, err := db.Query("SELECT `uid`, `name`, `group`, `active`, `is_super_admin`, `avatar` FROM users") if err != nil { InternalError(err,w,r,user) @@ -951,8 +981,7 @@ func route_panel_users(w http.ResponseWriter, r *http.Request){ puser.Tag = "" } - userList[currentID] = puser - currentID++ + userList = append(userList, puser) } err = rows.Err() if err != nil { diff --git a/mysql.go b/mysql.go index 12f35072..2018a98d 100644 --- a/mysql.go +++ b/mysql.go @@ -28,6 +28,7 @@ var set_username_stmt *sql.Stmt var register_stmt *sql.Stmt var username_exists_stmt *sql.Stmt var change_group_stmt *sql.Stmt +var activate_user_stmt *sql.Stmt var create_profile_reply_stmt *sql.Stmt var edit_profile_reply_stmt *sql.Stmt var delete_profile_reply_stmt *sql.Stmt @@ -183,6 +184,12 @@ func init_database(err error) { log.Fatal(err) } + log.Print("Preparing activate_user statement.") + activate_user_stmt, err = db.Prepare("UPDATE users SET active = 1 WHERE uid = ?") + if err != nil { + log.Fatal(err) + } + log.Print("Preparing create_profile_reply statement.") create_profile_reply_stmt, err = db.Prepare("INSERT INTO users_replies(uid,content,parsed_content,createdAt,createdBy) VALUES(?,?,?,NOW(),?)") if err != nil { diff --git a/pages.go b/pages.go index e13be6ec..892b5b9f 100644 --- a/pages.go +++ b/pages.go @@ -3,6 +3,17 @@ import "strings" //import "regexp" type Page struct +{ + Title string + Name string + CurrentUser User + NoticeList map[int]string + ItemList []interface{} + Something interface{} +} + +/* For testing maps versus slices */ +type Page2 struct { Title string Name string diff --git a/public/main.css b/public/main.css index 180d1e98..0b34c255 100644 --- a/public/main.css +++ b/public/main.css @@ -28,41 +28,56 @@ body } } +/*.move_left +{ + float: left; + position: relative; + left: 50%; +} +.move_right +{ + float: left; + position: relative; + left: -50%; +}*/ ul { - border: 1px solid #ccc; - padding-top: 5px; - padding-bottom: 5px; padding-left: 0px; padding-right: 0px; height: 28px; list-style-type: none; + border: 1px solid #ccc; } - li { - float: left; + height: 26px; padding-left: 10px; + padding-top: 5px; + padding-bottom: 5px; font-weight: bold; text-transform: uppercase; } - li a { text-decoration: none; color: #515151; } - li a:hover { color: #7a7a7a; } - -li:not(:last-child) +.menu_left { + float: left; border-right: 1px solid #ccc; padding-right: 10px; } +.menu_right +{ + float: right; + border-left: 1px solid #ccc; + padding-right: 10px; +} .container { @@ -288,18 +303,26 @@ button.username display: none; } +.alert +{ + display: block; + padding: 5px; + margin-bottom: 10px; + border: 1px solid #ccc; +} .alert_success { display: block; - padding: 3px; + padding: 5px; border: 1px solid A2FC00; + margin-bottom: 10px; background-color: DAF7A6; } - .alert_error { display: block; - padding: 3px; + padding: 5px; border: 1px solid #FF004B; + margin-bottom: 8px; background-color: #FEB7CC; } \ No newline at end of file diff --git a/routes.go b/routes.go index 3181c301..1648b2a2 100644 --- a/routes.go +++ b/routes.go @@ -19,7 +19,7 @@ import _ "github.com/go-sql-driver/mysql" import "golang.org/x/crypto/bcrypt" // A blank list to fill out that parameter in Page for routes which don't use it -var tList map[int]interface{} +var tList []interface{} var nList map[int]string // GET functions @@ -80,7 +80,7 @@ func route_topics(w http.ResponseWriter, r *http.Request){ } var( - topicList map[int]interface{} + topicList []interface{} currentID int tid int @@ -95,8 +95,6 @@ func route_topics(w http.ResponseWriter, r *http.Request){ name string avatar string ) - topicList = make(map[int]interface{}) - currentID = 0 rows, err := db.Query("select topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.parentID, users.name, users.avatar from topics left join users ON topics.createdBy = users.uid order by topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC") if err != nil { @@ -125,12 +123,11 @@ func route_topics(w http.ResponseWriter, r *http.Request){ avatar = strings.Replace(noavatar,"{id}",strconv.Itoa(createdBy),1) } - topicList[currentID] = TopicUser{tid,title,content,createdBy,is_closed,sticky, createdAt,parentID,status,name,avatar,"",0,"","","",""} + topicList = append(topicList, TopicUser{tid,title,content,createdBy,is_closed,sticky, createdAt,parentID,status,name,avatar,"",0,"","","",""}) if hooks["trow_assign"] != nil { topicList[currentID] = run_hook("trow_assign", topicList[currentID]) } - currentID++ } err = rows.Err() if err != nil { @@ -146,10 +143,14 @@ func route_topics(w http.ResponseWriter, r *http.Request){ } pi := Page{"Topic List","topics",user,noticeList,topicList,msg} - err = templates.ExecuteTemplate(w,"topics.html", pi) - if err != nil { - InternalError(err, w, r, user) - } + if ctemplates["topics"] != nil { + ctemplates["topics"](pi,w) + } else { + err = templates.ExecuteTemplate(w,"topics.html", pi) + if err != nil { + InternalError(err, w, r, user) + } + } } func route_forum(w http.ResponseWriter, r *http.Request){ @@ -159,7 +160,7 @@ func route_forum(w http.ResponseWriter, r *http.Request){ } var( - topicList map[int]interface{} + topicList []interface{} currentID int tid int @@ -174,8 +175,6 @@ func route_forum(w http.ResponseWriter, r *http.Request){ name string avatar string ) - topicList = make(map[int]interface{}) - currentID = 0 fid, err := strconv.Atoi(r.URL.Path[len("/forum/"):]) if err != nil { @@ -216,12 +215,11 @@ func route_forum(w http.ResponseWriter, r *http.Request){ avatar = strings.Replace(noavatar,"{id}",strconv.Itoa(createdBy),1) } - topicList[currentID] = TopicUser{tid,title,content,createdBy,is_closed,sticky,createdAt,parentID,status,name,avatar,"",0,"","","",""} + topicList = append(topicList, TopicUser{tid,title,content,createdBy,is_closed,sticky,createdAt,parentID,status,name,avatar,"",0,"","","",""}) if hooks["trow_assign"] != nil { topicList[currentID] = run_hook("trow_assign", topicList[currentID]) } - currentID++ } err = rows.Err() if err != nil { @@ -237,10 +235,14 @@ func route_forum(w http.ResponseWriter, r *http.Request){ } pi := Page{forums[fid].Name,"forum",user,noticeList,topicList,msg} - err = templates.ExecuteTemplate(w,"forum.html", pi) - if err != nil { - InternalError(err, w, r, user) - } + if ctemplates["forum"] != nil { + ctemplates["forum"](pi,w) + } else { + err = templates.ExecuteTemplate(w,"forum.html", pi) + if err != nil { + InternalError(err, w, r, user) + } + } } func route_forums(w http.ResponseWriter, r *http.Request){ @@ -249,13 +251,10 @@ func route_forums(w http.ResponseWriter, r *http.Request){ return } - var forumList map[int]interface{} = make(map[int]interface{}) - currentID := 0 - + var forumList []interface{} for _, forum := range forums { if forum.Active { - forumList[currentID] = forum - currentID++ + forumList = append(forumList, forum) } } @@ -265,10 +264,14 @@ func route_forums(w http.ResponseWriter, r *http.Request){ } pi := Page{"Forum List","forums",user,noticeList,forumList,0} - err := templates.ExecuteTemplate(w,"forums.html", pi) - if err != nil { - InternalError(err, w, r, user) - } + if ctemplates["forums"] != nil { + ctemplates["forums"](pi,w) + } else { + err := templates.ExecuteTemplate(w,"forums.html", pi) + if err != nil { + InternalError(err, w, r, user) + } + } } func route_topic_id(w http.ResponseWriter, r *http.Request){ @@ -297,11 +300,8 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){ is_super_admin bool group int - currentID int - replyList map[int]interface{} + replyList []interface{} ) - replyList = make(map[int]interface{}) - currentID = 0 topic := TopicUser{0,"","",0,false,false,"",0,"","","",no_css_tmpl,0,"","","",""} topic.ID, err = strconv.Atoi(r.URL.Path[len("/topic/"):]) @@ -398,12 +398,12 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){ } } - replyList[currentID] = Reply{rid,topic.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss,replyLines,replyTag,replyURL,replyURLPrefix,replyURLName} + replyItem := Reply{rid,topic.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss,replyLines,replyTag,replyURL,replyURLPrefix,replyURLName} if hooks["rrow_assign"] != nil { - replyList[currentID] = run_hook("rrow_assign", replyList[currentID]) + replyItem = run_hook("rrow_assign", replyItem).(Reply) } - currentID++ + replyList = append(replyList, replyItem) } err = rows.Err() if err != nil { @@ -444,11 +444,8 @@ func route_profile(w http.ResponseWriter, r *http.Request){ is_super_admin bool group int - currentID int - replyList map[int]interface{} + replyList []interface{} ) - replyList = make(map[int]interface{}) - currentID = 0 puser := User{0,"",0,false,false,false,false,false,false,"",false,"","","","",""} puser.ID, err = strconv.Atoi(r.URL.Path[len("/user/"):]) @@ -530,8 +527,7 @@ func route_profile(w http.ResponseWriter, r *http.Request){ replyTag = "" } - replyList[currentID] = Reply{rid,puser.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss,replyLines,replyTag,"","",""} - currentID++ + replyList = append(replyList, Reply{rid,puser.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss,replyLines,replyTag,"","",""}) } err = rows.Err() if err != nil { @@ -540,11 +536,14 @@ func route_profile(w http.ResponseWriter, r *http.Request){ } pi := Page{puser.Name + "'s Profile","profile",user,noticeList,replyList,puser} - //w.Write([]byte(template_profile(pi))) - err = templates.ExecuteTemplate(w,"profile.html", pi) - if err != nil { - InternalError(err, w, r, user) - } + if ctemplates["profile"] != nil { + ctemplates["profile"](pi,w) + } else { + err = templates.ExecuteTemplate(w,"profile.html", pi) + if err != nil { + InternalError(err, w, r, user) + } + } } func route_topic_create(w http.ResponseWriter, r *http.Request){ diff --git a/template_forum.go b/template_forum.go new file mode 100644 index 00000000..82df2d08 --- /dev/null +++ b/template_forum.go @@ -0,0 +1,104 @@ +package main +import "strconv" +import "io" + +func init() { +ctemplates["forum"] = template_forum +} + +func template_forum(tmpl_forum_vars Page, w io.Writer) { +w.Write([]byte(` + +
+