Added support for word filters.

Added support for temporary bans.
Moved the Gosora specific template logic out of main.go and into template_init.go

Added an internal temporary group API.
We now use h1s in the theme headers. There may be some issues with the themes other than Shadow which we may need to resolve.
Added ARIA attributes in a few places for improved accessibility.
Added support for inputs in links for the .edit_field API.
Removed a few unneccesary prepared statements.
This commit is contained in:
Azareal 2017-08-27 10:33:45 +01:00
parent 34b02336ed
commit 99da1fcbaa
62 changed files with 1154 additions and 469 deletions

View File

@ -17,7 +17,7 @@ var error_notfound []byte
func init_errors() error {
var b bytes.Buffer
user := User{0,"guest","Guest","",0,false,false,false,false,false,false,GuestPerms,nil,"",false,"","","","","",0,0,"0.0.0.0.0"}
user := User{0,"guest","Guest","",0,false,false,false,false,false,false,GuestPerms,nil,"",false,"","","","","",0,0,"0.0.0.0.0",0}
pi := Page{"Internal Server Error",user,hvars,tList,"A problem has occurred in the system."}
err := templates.ExecuteTemplate(&b,"error.html", pi)
if err != nil {

View File

@ -57,6 +57,8 @@ var pre_render_hooks map[string][]func(http.ResponseWriter, *http.Request, *User
"pre_render_panel_edit_forum": nil,
"pre_render_panel_settings": nil,
"pre_render_panel_setting": nil,
"pre_render_panel_word_filters": nil,
"pre_render_panel_word_filters_edit": nil,
"pre_render_panel_plugins": nil,
"pre_render_panel_users": nil,
"pre_render_panel_edit_user": nil,

View File

@ -24,6 +24,7 @@ var get_widgets_stmt *sql.Stmt
var is_plugin_active_stmt *sql.Stmt
var get_users_stmt *sql.Stmt
var get_users_offset_stmt *sql.Stmt
var get_word_filters_stmt *sql.Stmt
var is_theme_default_stmt *sql.Stmt
var get_modlogs_stmt *sql.Stmt
var get_modlogs_offset_stmt *sql.Stmt
@ -33,15 +34,14 @@ var get_user_reply_uid_stmt *sql.Stmt
var has_liked_topic_stmt *sql.Stmt
var has_liked_reply_stmt *sql.Stmt
var get_user_name_stmt *sql.Stmt
var get_user_rank_stmt *sql.Stmt
var get_user_active_stmt *sql.Stmt
var get_user_group_stmt *sql.Stmt
var get_emails_by_user_stmt *sql.Stmt
var get_topic_basic_stmt *sql.Stmt
var get_activity_entry_stmt *sql.Stmt
var forum_entry_exists_stmt *sql.Stmt
var group_entry_exists_stmt *sql.Stmt
var get_forum_topics_offset_stmt *sql.Stmt
var get_expired_scheduled_groups_stmt *sql.Stmt
var get_topic_replies_offset_stmt *sql.Stmt
var get_topic_list_stmt *sql.Stmt
var get_topic_user_stmt *sql.Stmt
@ -67,7 +67,9 @@ var add_theme_stmt *sql.Stmt
var create_group_stmt *sql.Stmt
var add_modlog_entry_stmt *sql.Stmt
var add_adminlog_entry_stmt *sql.Stmt
var create_word_filter_stmt *sql.Stmt
var add_forum_perms_to_group_stmt *sql.Stmt
var replace_schedule_group_stmt *sql.Stmt
var add_replies_to_topic_stmt *sql.Stmt
var remove_replies_from_topic_stmt *sql.Stmt
var add_topics_to_forum_stmt *sql.Stmt
@ -105,11 +107,14 @@ var update_group_rank_stmt *sql.Stmt
var update_group_stmt *sql.Stmt
var update_email_stmt *sql.Stmt
var verify_email_stmt *sql.Stmt
var set_temp_group_stmt *sql.Stmt
var update_word_filter_stmt *sql.Stmt
var delete_reply_stmt *sql.Stmt
var delete_topic_stmt *sql.Stmt
var delete_profile_reply_stmt *sql.Stmt
var delete_forum_perms_by_forum_stmt *sql.Stmt
var delete_activity_stream_match_stmt *sql.Stmt
var delete_word_filter_stmt *sql.Stmt
var report_exists_stmt *sql.Stmt
var group_count_stmt *sql.Stmt
var modlog_count_stmt *sql.Stmt
@ -225,6 +230,12 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing get_word_filters statement.")
get_word_filters_stmt, err = db.Prepare("SELECT `wfid`,`find`,`replacement` FROM `word_filters`")
if err != nil {
return err
}
log.Print("Preparing is_theme_default statement.")
is_theme_default_stmt, err = db.Prepare("SELECT `default` FROM `themes` WHERE `uname` = ?")
if err != nil {
@ -279,24 +290,12 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing get_user_rank statement.")
get_user_rank_stmt, err = db.Prepare("SELECT `group`,`is_super_admin` FROM `users` WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing get_user_active statement.")
get_user_active_stmt, err = db.Prepare("SELECT `active` FROM `users` WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing get_user_group statement.")
get_user_group_stmt, err = db.Prepare("SELECT `group` FROM `users` WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing get_emails_by_user statement.")
get_emails_by_user_stmt, err = db.Prepare("SELECT `email`,`validated`,`token` FROM `emails` WHERE `uid` = ?")
if err != nil {
@ -333,6 +332,12 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing get_expired_scheduled_groups statement.")
get_expired_scheduled_groups_stmt, err = db.Prepare("SELECT `uid` FROM `users_groups_scheduler` WHERE UTC_TIMESTAMP() > `revert_at` AND `temporary` = 1")
if err != nil {
return err
}
log.Print("Preparing get_topic_replies_offset statement.")
get_topic_replies_offset_stmt, err = db.Prepare("SELECT `replies`.`rid`,`replies`.`content`,`replies`.`createdBy`,`replies`.`createdAt`,`replies`.`lastEdit`,`replies`.`lastEditBy`,`users`.`avatar`,`users`.`name`,`users`.`group`,`users`.`url_prefix`,`users`.`url_name`,`users`.`level`,`replies`.`ipaddress`,`replies`.`likeCount`,`replies`.`actionType` FROM `replies` LEFT JOIN `users` ON `replies`.`createdBy` = `users`.`uid` WHERE `tid` = ? LIMIT ?,?")
if err != nil {
@ -483,12 +488,24 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing create_word_filter statement.")
create_word_filter_stmt, err = db.Prepare("INSERT INTO `word_filters`(`find`,`replacement`) VALUES (?,?)")
if err != nil {
return err
}
log.Print("Preparing add_forum_perms_to_group statement.")
add_forum_perms_to_group_stmt, err = db.Prepare("REPLACE INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) VALUES (?,?,?,?)")
if err != nil {
return err
}
log.Print("Preparing replace_schedule_group statement.")
replace_schedule_group_stmt, err = db.Prepare("REPLACE INTO `users_groups_scheduler`(`uid`,`set_group`,`issued_by`,`issued_at`,`revert_at`,`temporary`) VALUES (?,?,?,UTC_TIMESTAMP(),?,?)")
if err != nil {
return err
}
log.Print("Preparing add_replies_to_topic statement.")
add_replies_to_topic_stmt, err = db.Prepare("UPDATE `topics` SET `postCount` = `postCount` + ?,`lastReplyBy` = ?,`lastReplyAt` = UTC_TIMESTAMP() WHERE `tid` = ?")
if err != nil {
@ -711,6 +728,18 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing set_temp_group statement.")
set_temp_group_stmt, err = db.Prepare("UPDATE `users` SET `temp_group` = ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_word_filter statement.")
update_word_filter_stmt, err = db.Prepare("UPDATE `word_filters` SET `find` = ?,`replacement` = ? WHERE `wfid` = ?")
if err != nil {
return err
}
log.Print("Preparing delete_reply statement.")
delete_reply_stmt, err = db.Prepare("DELETE FROM `replies` WHERE `rid` = ?")
if err != nil {
@ -741,6 +770,12 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing delete_word_filter statement.")
delete_word_filter_stmt, err = db.Prepare("DELETE FROM `word_filters` WHERE `wfid` = ?")
if err != nil {
return err
}
log.Print("Preparing report_exists statement.")
report_exists_stmt, err = db.Prepare("SELECT COUNT(*) AS `count` FROM `topics` WHERE `data` = ? AND `data` != '' AND `parentID` = 1")
if err != nil {

View File

@ -43,6 +43,8 @@ var update_group_rank_stmt *sql.Stmt
var update_group_stmt *sql.Stmt
var update_email_stmt *sql.Stmt
var verify_email_stmt *sql.Stmt
var set_temp_group_stmt *sql.Stmt
var update_word_filter_stmt *sql.Stmt
func _gen_pgsql() (err error) {
if dev.DebugMode {
@ -270,6 +272,18 @@ func _gen_pgsql() (err error) {
if err != nil {
return err
}
log.Print("Preparing set_temp_group statement.")
set_temp_group_stmt, err = db.Prepare("UPDATE `users` SET `temp_group` = ? WHERE `uid` = ?")
if err != nil {
return err
}
log.Print("Preparing update_word_filter statement.")
update_word_filter_stmt, err = db.Prepare("UPDATE `word_filters` SET `find` = ?,`replacement` = ? WHERE `wfid` = ?")
if err != nil {
return err
}
return nil
}

View File

@ -150,6 +150,21 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
case "/panel/settings/edit/submit/":
route_panel_setting_edit(w,req,user,extra_data)
return
case "/panel/settings/word-filters/":
route_panel_word_filters(w,req,user)
return
case "/panel/settings/word-filters/create/":
route_panel_word_filters_create(w,req,user)
return
case "/panel/settings/word-filters/edit/":
route_panel_word_filters_edit(w,req,user,extra_data)
return
case "/panel/settings/word-filters/edit/submit/":
route_panel_word_filters_edit_submit(w,req,user,extra_data)
return
case "/panel/settings/word-filters/delete/submit/":
route_panel_word_filters_delete_submit(w,req,user,extra_data)
return
case "/panel/themes/":
route_panel_themes(w,req,user)
return

View File

@ -25,7 +25,7 @@ var db_test *sql.DB
var db_prod *sql.DB
var gloinited bool
func gloinit() {
func gloinit() error {
dev.DebugMode = false
//nogrouplog = true
@ -40,20 +40,20 @@ func gloinit() {
init_themes()
err := init_database()
if err != nil {
log.Fatal(err)
return err
}
db_prod = db
//db_test, err = sql.Open("testdb","")
//if err != nil {
// log.Fatal(err)
// return err
//}
init_templates()
db_prod.SetMaxOpenConns(64)
err = init_errors()
if err != nil {
log.Fatal(err)
return err
}
if config.CacheTopicUser == CACHE_STATIC {
@ -68,8 +68,14 @@ func gloinit() {
//log.SetOutput(os.Stdout)
auth = NewDefaultAuth()
err = init_word_filters()
if err != nil {
return err
}
router = NewGenRouter(http.FileServer(http.Dir("./uploads")))
gloinited = true
return nil
}
func init() {

271
main.go
View File

@ -8,8 +8,8 @@ import (
"time"
"io"
"os"
"sync/atomic"
"net/http"
"html/template"
//"runtime/pprof"
)
@ -17,6 +17,7 @@ var version Version = Version{Major:0,Minor:1,Patch:0,Tag:"dev"}
const hour int = 60 * 60
const day int = hour * 24
const week int = day * 7
const month int = day * 30
const year int = day * 365
const kilobyte int = 1024
@ -29,9 +30,6 @@ var enable_websockets bool = false // Don't change this, the value is overwritte
var router *GenRouter
var startTime time.Time
//var timeLocation *time.Location
var templates = template.New("")
//var no_css_tmpl template.CSS = template.CSS("")
var external_sites map[string]string = map[string]string{
"YT":"https://www.youtube.com/",
}
@ -40,236 +38,45 @@ var groupCapCount int
var static_files map[string]SFile = make(map[string]SFile)
var logWriter io.Writer = io.MultiWriter(os.Stderr)
func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["topic"]
if !ok {
mapping = "topic"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
type WordFilter struct {
ID int
Find string
Replacement string
}
type WordFilterBox map[int]WordFilter
var wordFilterBox atomic.Value // An atomic value holding a WordFilterBox
func init() {
wordFilterBox.Store(WordFilterBox(make(map[int]WordFilter)))
}
var template_topic_handle func(TopicPage,http.ResponseWriter) = interpreted_topic_template
var template_topic_alt_handle func(TopicPage,http.ResponseWriter) = interpreted_topic_template
var template_topics_handle func(TopicsPage,http.ResponseWriter) = func(pi TopicsPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["topics"]
if !ok {
mapping = "topics"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
}
var template_forum_handle func(ForumPage,http.ResponseWriter) = func(pi ForumPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["forum"]
if !ok {
mapping = "forum"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
}
var template_forums_handle func(ForumsPage,http.ResponseWriter) = func(pi ForumsPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["forums"]
if !ok {
mapping = "forums"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
}
var template_profile_handle func(ProfilePage,http.ResponseWriter) = func(pi ProfilePage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["profile"]
if !ok {
mapping = "profile"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
}
var template_create_topic_handle func(CreateTopicPage,http.ResponseWriter) = func(pi CreateTopicPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["create-topic"]
if !ok {
mapping = "create-topic"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
}
func compile_templates() error {
var c CTemplateSet
user := User{62,build_profile_url("fake-user",62),"Fake User","compiler@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"0.0.0.0.0"}
// TO-DO: Do a more accurate level calculation for this?
user2 := User{1,build_profile_url("admin-alice",1),"Admin Alice","alice@localhost",1,true,true,true,true,false,false,AllPerms,make(map[string]bool),"",true,"","","","","",58,1000,"127.0.0.1"}
user3 := User{2,build_profile_url("admin-fred",62),"Admin Fred","fred@localhost",1,true,true,true,true,false,false,AllPerms,make(map[string]bool),"",true,"","","","","",42,900,"::1"}
headerVars := HeaderVars{
Site:site,
NoticeList:[]string{"test"},
Stylesheets:[]string{"panel"},
Scripts:[]string{"whatever"},
Widgets:PageWidgets{
LeftSidebar: template.HTML("lalala"),
},
}
log.Print("Compiling the templates")
topic := TopicUser{1,"blah","Blah","Hey there!",0,false,false,"Date","Date",0,"","127.0.0.1",0,1,"classname","weird-data",build_profile_url("fake-user",62),"Fake User",config.DefaultGroup,"",0,"","","","",58,false}
var replyList []Reply
replyList = append(replyList, Reply{0,0,"Yo!","Yo!",0,"alice","Alice",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
var varList map[string]VarItem = make(map[string]VarItem)
tpage := TopicPage{"Title",user,headerVars,replyList,topic,1,1,extData}
topic_id_tmpl, err := c.compile_template("topic.html","templates/","TopicPage", tpage, varList)
if err != nil {
return err
}
topic_id_alt_tmpl, err := c.compile_template("topic_alt.html","templates/","TopicPage", tpage, varList)
func init_word_filters() error {
rows, err := get_word_filters_stmt.Query()
if err != nil {
return err
}
defer rows.Close()
varList = make(map[string]VarItem)
ppage := ProfilePage{"User 526",user,headerVars,replyList,user,extData}
profile_tmpl, err := c.compile_template("profile.html","templates/","ProfilePage", ppage, varList)
if err != nil {
return err
}
var wordFilters WordFilterBox = wordFilterBox.Load().(WordFilterBox)
var wfid int
var find string
var replacement string
var forumList []Forum
forums, err := fstore.GetAll()
if err != nil {
return err
}
for _, forum := range forums {
if forum.Active {
forumList = append(forumList,*forum)
}
}
varList = make(map[string]VarItem)
forums_page := ForumsPage{"Forum List",user,headerVars,forumList,extData}
forums_tmpl, err := c.compile_template("forums.html","templates/","ForumsPage",forums_page,varList)
if err != nil {
return err
}
var topicsList []*TopicsRow
topicsList = append(topicsList,&TopicsRow{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",user3.ID,1,"","127.0.0.1",0,1,"classname","",&user2,"",0,&user3,"General","/forum/general.2"})
topics_page := TopicsPage{"Topic List",user,headerVars,topicsList,extData}
topics_tmpl, err := c.compile_template("topics.html","templates/","TopicsPage",topics_page,varList)
if err != nil {
return err
}
//var topicList []TopicUser
//topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false})
forum_item := Forum{1,"general","General Forum","Where the general stuff happens",true,"all",0,"",0,"","",0,"",0,""}
forum_page := ForumPage{"General Forum",user,headerVars,topicsList,forum_item,1,1,extData}
forum_tmpl, err := c.compile_template("forum.html","templates/","ForumPage",forum_page,varList)
if err != nil {
return err
}
log.Print("Writing the templates")
go write_template("topic", topic_id_tmpl)
go write_template("topic_alt", topic_id_alt_tmpl)
go write_template("profile", profile_tmpl)
go write_template("forums", forums_tmpl)
go write_template("topics", topics_tmpl)
go write_template("forum", forum_tmpl)
go func() {
err := write_file("./template_list.go","package main\n\n" + c.FragOut)
for rows.Next() {
err := rows.Scan(&wfid, &find, &replacement)
if err != nil {
log.Fatal(err)
return err
}
}()
return nil
wordFilters[wfid] = WordFilter{ID:wfid,Find:find,Replacement:replacement}
}
wordFilterBox.Store(wordFilters)
return rows.Err()
}
func write_template(name string, content string) {
err := write_file("./template_" + name + ".go", content)
if err != nil {
log.Fatal(err)
}
}
func init_templates() {
if dev.DebugMode {
log.Print("Initialising the template system")
}
compile_templates()
// TO-DO: Add support for 64-bit integers
// TO-DO: Add support for floats
fmap := make(map[string]interface{})
fmap["add"] = func(left interface{}, right interface{})interface{} {
var left_int int
var right_int int
switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
return left_int + right_int
}
fmap["subtract"] = func(left interface{}, right interface{})interface{} {
var left_int int
var right_int int
switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
return left_int - right_int
}
fmap["multiply"] = func(left interface{}, right interface{})interface{} {
var left_int int
var right_int int
switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
return left_int * right_int
}
fmap["divide"] = func(left interface{}, right interface{})interface{} {
var left_int int
var right_int int
switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
if left_int == 0 || right_int == 0 {
return 0
}
return left_int / right_int
}
// The interpreted templates...
if dev.DebugMode {
log.Print("Loading the template files...")
}
templates.Funcs(fmap)
template.Must(templates.ParseGlob("templates/*"))
template.Must(templates.ParseGlob("pages/*"))
func add_word_filter(id int, find string, replacement string) {
wordFilters := wordFilterBox.Load().(WordFilterBox)
wordFilters[id] = WordFilter{ID:id,Find:find,Replacement:replacement}
wordFilterBox.Store(wordFilters)
}
func process_config() {
@ -339,6 +146,11 @@ func main(){
log.Print("Initialising the authentication system")
auth = NewDefaultAuth()
err = init_word_filters()
if err != nil {
log.Fatal(err)
}
// Run this goroutine once a second
second_ticker := time.NewTicker(1 * time.Second)
fifteen_minute_ticker := time.NewTicker(15 * time.Minute)
@ -347,6 +159,11 @@ func main(){
for {
select {
case <- second_ticker.C:
//log.Print("Running the second ticker")
err := handle_expired_scheduled_groups()
if err != nil {
LogError(err)
}
// TO-DO: Handle delayed moderation tasks
// TO-DO: Handle the daily clean-up. Move this to a 24 hour task?
// TO-DO: Sync with the database, if there are any changes
@ -354,9 +171,9 @@ func main(){
// TO-DO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high
// TO-DO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task?
case <- fifteen_minute_ticker.C:
// TO-DO: Handle temporary bans.
// TO-DO: Automatically lock topics, if they're really old, and the associated setting is enabled.
// TO-DO: Publish scheduled posts. Move this to a 15 minute task?
// TO-DO: Publish scheduled posts.
// TO-DO: Delete the empty users_groups_scheduler entries
}
}
}()
@ -409,8 +226,8 @@ func main(){
router.HandleFunc("/profile/reply/create/", route_profile_reply_create)
router.HandleFunc("/profile/reply/edit/submit/", route_profile_reply_edit_submit)
router.HandleFunc("/profile/reply/delete/submit/", route_profile_reply_delete_submit)
//router.HandleFunc("/user/edit/submit/", route_logout)
router.HandleFunc("/users/ban/", route_ban)
//router.HandleFunc("/user/edit/submit/", route_logout) // route_logout? what on earth? o.o
//router.HandleFunc("/users/ban/", route_ban)
router.HandleFunc("/users/ban/submit/", route_ban_submit)
router.HandleFunc("/users/unban/", route_unban)
router.HandleFunc("/users/activate/", route_activate)

View File

@ -521,7 +521,8 @@ func route_profile_reply_delete_submit(w http.ResponseWriter, r *http.Request, u
}
}
func route_ban(w http.ResponseWriter, r *http.Request, user User) {
// TO-DO: This is being replaced with the new ban route system
/*func route_ban(w http.ResponseWriter, r *http.Request, user User) {
headerVars, ok := SessionCheck(w,r,&user)
if !ok {
return
@ -557,7 +558,7 @@ func route_ban(w http.ResponseWriter, r *http.Request, user User) {
}
}
templates.ExecuteTemplate(w,"areyousure.html",pi)
}
}*/
func route_ban_submit(w http.ResponseWriter, r *http.Request, user User) {
if !user.Perms.BanUsers {
@ -575,13 +576,11 @@ func route_ban_submit(w http.ResponseWriter, r *http.Request, user User) {
return
}
/*if uid == -2 {
LocalError("Sigh, are you really trying to ban me? Do you despise so much? Despite all of our adventures over at /arcane-tower/...?",w,r,user)
LocalError("Stop trying to ban Merlin! Ban admin! Bad! No!",w,r,user)
return
}*/
var group int
var is_super_admin bool
err = get_user_rank_stmt.QueryRow(uid).Scan(&group, &is_super_admin)
targetUser, err := users.CascadeGet(uid)
if err == ErrNoRows {
LocalError("The user you're trying to ban no longer exists.",w,r,user)
return
@ -590,7 +589,7 @@ func route_ban_submit(w http.ResponseWriter, r *http.Request, user User) {
return
}
if is_super_admin || groups[group].Is_Admin || groups[group].Is_Mod {
if targetUser.Is_Super_Admin || targetUser.Is_Admin || targetUser.Is_Mod {
LocalError("You may not ban another staff member.",w,r,user)
return
}
@ -599,13 +598,45 @@ func route_ban_submit(w http.ResponseWriter, r *http.Request, user User) {
return
}
if groups[group].Is_Banned {
if targetUser.Is_Banned {
LocalError("The user you're trying to unban is already banned.",w,r,user)
return
}
_, err = change_group_stmt.Exec(4, uid)
duration_days, err := strconv.Atoi(r.FormValue("ban-duration-days"))
if err != nil {
LocalError("You can only use whole numbers for the number of days",w,r,user)
return
}
duration_weeks, err := strconv.Atoi(r.FormValue("ban-duration-weeks"))
if err != nil {
LocalError("You can only use whole numbers for the number of weeks",w,r,user)
return
}
duration_months, err := strconv.Atoi(r.FormValue("ban-duration-months"))
if err != nil {
LocalError("You can only use whole numbers for the number of months",w,r,user)
return
}
var duration time.Duration
if duration_days > 1 && duration_weeks > 1 && duration_months > 1 {
duration, _ = time.ParseDuration("0")
} else {
var seconds int
seconds += duration_days * day
seconds += duration_weeks * week
seconds += duration_months * month
duration, _ = time.ParseDuration(strconv.Itoa(seconds) + "s")
}
err = targetUser.Ban(duration,user.ID)
if err == ErrNoRows {
LocalError("The user you're trying to ban no longer exists.",w,r,user)
return
} else if err != nil {
InternalError(err,w)
return
}
@ -621,11 +652,6 @@ func route_ban_submit(w http.ResponseWriter, r *http.Request, user User) {
return
}
err = users.Load(uid)
if err != nil {
LocalError("This user no longer exists!",w,r,user)
return
}
http.Redirect(w,r,"/user/" + strconv.Itoa(uid),http.StatusSeeOther)
}
@ -645,8 +671,7 @@ func route_unban(w http.ResponseWriter, r *http.Request, user User) {
return
}
var group int
err = get_user_group_stmt.QueryRow(uid).Scan(&group)
targetUser, err := users.CascadeGet(uid)
if err == ErrNoRows {
LocalError("The user you're trying to unban no longer exists.",w,r,user)
return
@ -655,13 +680,16 @@ func route_unban(w http.ResponseWriter, r *http.Request, user User) {
return
}
if !groups[group].Is_Banned {
if !targetUser.Is_Banned {
LocalError("The user you're trying to unban isn't banned.",w,r,user)
return
}
_, err = change_group_stmt.Exec(config.DefaultGroup, uid)
if err != nil {
err = targetUser.Unban()
if err == ErrNoRows {
LocalError("The user you're trying to unban no longer exists.",w,r,user)
return
} else if err != nil {
InternalError(err,w)
return
}
@ -677,15 +705,6 @@ func route_unban(w http.ResponseWriter, r *http.Request, user User) {
return
}
err = users.Load(uid)
if err != nil && err == ErrNoRows {
LocalError("This user no longer exists!",w,r,user)
return
} else if err != nil {
InternalError(err,w)
return
}
http.Redirect(w,r,"/user/" + strconv.Itoa(uid),http.StatusSeeOther)
}

View File

@ -120,6 +120,7 @@ type PanelStats struct
Groups int
Forums int
Settings int
WordFilters int
Themes int
Reports int
}
@ -463,6 +464,12 @@ func parse_message(msg string/*, user User*/) string {
msg = strings.Replace(msg,":o","😲",-1)
//msg = url_reg.ReplaceAllString(msg,"<a href=\"$2$3//$4\" rel=\"nofollow\">$2$3//$4</a>")
// Word filter list. E.g. Swear words and other things the admins don't like
wordFilters := wordFilterBox.Load().(WordFilterBox)
for _, filter := range wordFilters {
msg = strings.Replace(msg,filter.Find,filter.Replacement,-1)
}
// Search for URLs, mentions and hashlinks in the messages...
//log.Print("Parser Loop!")
var msgbytes = []byte(msg)

View File

@ -722,6 +722,184 @@ func route_panel_setting_edit(w http.ResponseWriter, r *http.Request, user User,
http.Redirect(w,r,"/panel/settings/",http.StatusSeeOther)
}
func route_panel_word_filters(w http.ResponseWriter, r *http.Request, user User){
headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
if !user.Perms.EditSettings {
NoPermissions(w,r,user)
return
}
var filterList WordFilterBox = wordFilterBox.Load().(WordFilterBox)
pi := PanelPage{"Word Filter Manager",user,headerVars,stats,tList,filterList}
if pre_render_hooks["pre_render_panel_word_filters"] != nil {
if run_pre_render_hook("pre_render_panel_word_filters", w, r, &user, &pi) {
return
}
}
err := templates.ExecuteTemplate(w,"panel-word-filters.html",pi)
if err != nil {
InternalError(err,w)
}
}
func route_panel_word_filters_create(w http.ResponseWriter, r *http.Request, user User){
_, ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
if !user.Perms.EditSettings {
NoPermissions(w,r,user)
return
}
err := r.ParseForm()
if err != nil {
PreError("Bad Form",w,r)
return
}
is_js := r.PostFormValue("js")
if is_js == "" {
is_js = "0"
}
find := strings.TrimSpace(r.PostFormValue("find"))
if find == "" {
LocalErrorJSQ("You need to specify what word you want to match",w,r,user,is_js)
return
}
// Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement
replacement := strings.TrimSpace(r.PostFormValue("replacement"))
res, err := create_word_filter_stmt.Exec(find,replacement)
if err != nil {
InternalErrorJSQ(err,w,r,is_js)
return
}
lastId, err := res.LastInsertId()
if err != nil {
InternalErrorJSQ(err,w,r,is_js)
return
}
add_word_filter(int(lastId),find,replacement)
http.Redirect(w,r,"/panel/settings/word-filters/",http.StatusSeeOther)
}
func route_panel_word_filters_edit(w http.ResponseWriter, r *http.Request, user User, wfid string){
headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok {
return
}
if !user.Perms.EditSettings {
NoPermissions(w,r,user)
return
}
_ = wfid
pi := PanelPage{"Edit Word Filter",user,headerVars,stats,tList,nil}
if pre_render_hooks["pre_render_panel_word_filters_edit"] != nil {
if run_pre_render_hook("pre_render_panel_word_filters_edit", w, r, &user, &pi) {
return
}
}
err := templates.ExecuteTemplate(w,"panel-word-filters-edit.html",pi)
if err != nil {
InternalError(err,w)
}
}
func route_panel_word_filters_edit_submit(w http.ResponseWriter, r *http.Request, user User, wfid string){
_, ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
err := r.ParseForm()
if err != nil {
PreError("Bad Form",w,r)
return
}
is_js := r.PostFormValue("is_js")
if is_js == "" {
is_js = "0"
}
if !user.Perms.EditSettings {
NoPermissionsJSQ(w,r,user,is_js)
return
}
id, err := strconv.Atoi(wfid)
if err != nil {
LocalErrorJSQ("The word filter ID must be an integer.",w,r,user,is_js)
return
}
find := strings.TrimSpace(r.PostFormValue("find"))
if find == "" {
LocalErrorJSQ("You need to specify what word you want to match",w,r,user,is_js)
return
}
// Unlike with find, it's okay if we leave this blank, as this means that the admin wants to remove the word entirely with no replacement
replacement := strings.TrimSpace(r.PostFormValue("replacement"))
_, err = update_word_filter_stmt.Exec(find,replacement,id)
if err != nil {
InternalErrorJSQ(err,w,r,is_js)
return
}
wordFilters := wordFilterBox.Load().(WordFilterBox)
wordFilters[id] = WordFilter{ID:id,Find:find,Replacement:replacement}
wordFilterBox.Store(wordFilters)
http.Redirect(w,r,"/panel/settings/word-filters/",http.StatusSeeOther)
}
func route_panel_word_filters_delete_submit(w http.ResponseWriter, r *http.Request, user User, wfid string){
_, ok := SimplePanelSessionCheck(w,r,&user)
if !ok {
return
}
err := r.ParseForm()
if err != nil {
PreError("Bad Form",w,r)
return
}
is_js := r.PostFormValue("is_js")
if is_js == "" {
is_js = "0"
}
if !user.Perms.EditSettings {
NoPermissionsJSQ(w,r,user,is_js)
return
}
id, err := strconv.Atoi(wfid)
if err != nil {
LocalErrorJSQ("The word filter ID must be an integer.",w,r,user,is_js)
return
}
_, err = delete_word_filter_stmt.Exec(id)
if err != nil {
InternalErrorJSQ(err,w,r,is_js)
return
}
wordFilters := wordFilterBox.Load().(WordFilterBox)
delete(wordFilters,id)
wordFilterBox.Store(wordFilters)
http.Redirect(w,r,"/panel/settings/word-filters/",http.StatusSeeOther)
}
func route_panel_plugins(w http.ResponseWriter, r *http.Request, user User){
headerVars, stats, ok := PanelSessionCheck(w,r,&user)
if !ok {

View File

@ -284,6 +284,7 @@ $(document).ready(function(){
$(".edit_fields").click(function(event)
{
event.preventDefault();
if($(this).find("input").length !== 0) return;
//console.log("clicked .edit_fields");
var block_parent = $(this).closest('.editable_parent');
//console.log(block_parent);
@ -353,6 +354,7 @@ $(document).ready(function(){
});
// This one's for Tempra Conflux
// TO-DO: We might want to use pure JS here
$(".ip_item").each(function(){
var ip = this.textContent;
if(ip.length > 10){

View File

@ -110,6 +110,7 @@ func create_tables(adapter qgen.DB_Adapter) error {
qgen.DB_Table_Column{"megaposts","int",0,false,false,"0"},
qgen.DB_Table_Column{"topics","int",0,false,false,"0"},
//qgen.DB_Table_Column{"penalty_count","int",0,false,false,"0"},
qgen.DB_Table_Column{"temp_group","int",0,false,false,"0"}, // For temporary groups, set this to zero when a temporary group isn't in effect
},
[]qgen.DB_Table_Key{
qgen.DB_Table_Key{"uid","primary"},
@ -117,35 +118,63 @@ func create_tables(adapter qgen.DB_Adapter) error {
},
)
// Coming Soon!
// What should we do about global penalties? Put them on the users table for speed? Or keep them here?
// Should we add IP Penalties?
// Should we add IP Penalties? No, that's a stupid idea, just implement IP Bans properly. What about shadowbans?
// TO-DO: Perm overrides
// TO-DO: Add a mod-queue and other basic auto-mod features. This is needed for awaiting activation and the mod_queue penalty flag
// TO-DO: Add a penalty type where a user is stopped from creating plugin_socialgroups social groups
// TO-DO: Shadow bans. We will probably have a CanShadowBan permission for this, as we *really* don't want people using this lightly.
/*qgen.Install.CreateTable("users_penalties","","",
[]qgen.DB_Table_Column{
qgen.DB_Table_Column{"uid","int",0,false,false,""},
qgen.DB_Table_Column{"element_id","int",0,false,false,""},
qgen.DB_Table_Column{"element_type","varchar",50,false,false,""}, //global,forum,profile?,social_group
qgen.DB_Table_Column{"overrides","text",0,false,false,"{}"}, // Perm overrides. Coming Soon
qgen.DB_Table_Column{"mod_queue","boolean",0,false,false,"0"}, // All of this user's posts will go through the mod_queue. Coming Soon
// TO-DO: Add a mod-queue and other basic auto-mod features. This is needed for awaiting activation and the mod_queue penalty flag
// TO-DO: Add a penalty type where a user is stopped from creating plugin_socialgroups social groups
qgen.DB_Table_Column{"element_type","varchar",50,false,false,""}, //forum, profile?, and social_group. Leave blank for global.
qgen.DB_Table_Column{"overrides","text",0,false,false,"{}"},
qgen.DB_Table_Column{"shadow_ban","boolean",0,false,false,"0"}, // Coming Soon. CanShadowBan permission.
qgen.DB_Table_Column{"mod_queue","boolean",0,false,false,"0"},
qgen.DB_Table_Column{"shadow_ban","boolean",0,false,false,"0"},
qgen.DB_Table_Column{"no_avatar","boolean",0,false,false,"0"}, // Coming Soon. Should this be a perm override instead?
//qgen.DB_Table_Column{"posts_per_hour","int",0,false,false,"0"}, // Rate-limit penalty type. Coming soon
//qgen.DB_Table_Column{"topics_per_hour","int",0,false,false,"0"}, // Coming Soon
//qgen.DB_Table_Column{"posts_count","int",0,false,false,"0"}, // Coming soon
//qgen.DB_Table_Column{"topic_count","int",0,false,false,"0"}, // Coming Soon
// Do we *really* need rate-limit penalty types? Are we going to be allowing bots or something?
//qgen.DB_Table_Column{"posts_per_hour","int",0,false,false,"0"},
//qgen.DB_Table_Column{"topics_per_hour","int",0,false,false,"0"},
//qgen.DB_Table_Column{"posts_count","int",0,false,false,"0"},
//qgen.DB_Table_Column{"topic_count","int",0,false,false,"0"},
//qgen.DB_Table_Column{"last_hour","int",0,false,false,"0"}, // UNIX Time, as we don't need to do anything too fancy here. When an hour has elapsed since that time, reset the hourly penalty counters.
qgen.DB_Table_Column{"issued_by","int",0,false,false,""},
qgen.DB_Table_Column{"issued_at","createdAt",0,false,false,""},
qgen.DB_Table_Column{"expiry","duration",0,false,false,""}, // TO-DO: Implement the duration parsing code on the adapter side
qgen.DB_Table_Column{"expires_at","datetime",0,false,false,""},
},
[]qgen.DB_Table_Key{},
)*/
qgen.Install.CreateTable("users_groups_scheduler","","",
[]qgen.DB_Table_Column{
qgen.DB_Table_Column{"uid","int",0,false,false,""},
qgen.DB_Table_Column{"set_group","int",0,false,false,""},
qgen.DB_Table_Column{"issued_by","int",0,false,false,""},
qgen.DB_Table_Column{"issued_at","createdAt",0,false,false,""},
qgen.DB_Table_Column{"revert_at","datetime",0,false,false,""},
qgen.DB_Table_Column{"temporary","boolean",0,false,false,""}, // special case for permanent bans to do the necessary bookkeeping, might be removed in the future
},
[]qgen.DB_Table_Key{
qgen.DB_Table_Key{"uid","primary"},
},
)
qgen.Install.CreateTable("word_filters","","",
[]qgen.DB_Table_Column{
qgen.DB_Table_Column{"wfid","int",0,false,true,""},
qgen.DB_Table_Column{"find","varchar",200,false,false,""},
qgen.DB_Table_Column{"replacement","varchar",200,false,false,""},
},
[]qgen.DB_Table_Key{
qgen.DB_Table_Key{"wfid","primary"},
},
)
return nil
}
@ -194,6 +223,8 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("get_users_offset","users","uid, name, group, active, is_super_admin, avatar","","","?,?")
adapter.SimpleSelect("get_word_filters","word_filters","wfid, find, replacement","","","")
adapter.SimpleSelect("is_theme_default","themes","default","uname = ?","","")
adapter.SimpleSelect("get_modlogs","moderation_logs","action, elementID, elementType, ipaddress, actorID, doneAt","","","")
@ -212,12 +243,8 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("get_user_name","users","name","uid = ?","","")
adapter.SimpleSelect("get_user_rank","users","group, is_super_admin","uid = ?","","")
adapter.SimpleSelect("get_user_active","users","active","uid = ?","","")
adapter.SimpleSelect("get_user_group","users","group","uid = ?","","")
adapter.SimpleSelect("get_emails_by_user","emails","email, validated, token","uid = ?","","")
adapter.SimpleSelect("get_topic_basic","topics","title, content","tid = ?","","")
@ -230,6 +257,8 @@ func write_selects(adapter qgen.DB_Adapter) error {
adapter.SimpleSelect("get_forum_topics_offset","topics","tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount","parentID = ?","sticky DESC, lastReplyAt DESC, createdBy DESC","?,?")
adapter.SimpleSelect("get_expired_scheduled_groups","users_groups_scheduler","uid","UTC_TIMESTAMP() > revert_at AND temporary = 1","","")
return nil
}
@ -293,12 +322,16 @@ func write_inserts(adapter qgen.DB_Adapter) error {
adapter.SimpleInsert("add_adminlog_entry","administration_logs","action, elementID, elementType, ipaddress, actorID, doneAt","?,?,?,?,?,UTC_TIMESTAMP()")
adapter.SimpleInsert("create_word_filter","word_filters","find, replacement","?,?")
return nil
}
func write_replaces(adapter qgen.DB_Adapter) error {
adapter.SimpleReplace("add_forum_perms_to_group","forums_permissions","gid,fid,preset,permissions","?,?,?,?")
adapter.SimpleReplace("replace_schedule_group","users_groups_scheduler","uid, set_group, issued_by, issued_at, revert_at, temporary","?,?,?,UTC_TIMESTAMP(),?,?")
return nil
}
@ -376,7 +409,11 @@ func write_updates(adapter qgen.DB_Adapter) error {
adapter.SimpleUpdate("update_email","emails","email = ?, uid = ?, validated = ?, token = ?","email = ?")
adapter.SimpleUpdate("verify_email","emails","validated = 1, token = ''","email = ?") // Need to fix this: Empty string isn't working, it gets set to 1 instead x.x
adapter.SimpleUpdate("verify_email","emails","validated = 1, token = ''","email = ?") // Need to fix this: Empty string isn't working, it gets set to 1 instead x.x -- Has this been fixed?
adapter.SimpleUpdate("set_temp_group","users","temp_group = ?","uid = ?")
adapter.SimpleUpdate("update_word_filter","word_filters","find = ?, replacement = ?","wfid = ?")
return nil
}
@ -392,9 +429,10 @@ func write_deletes(adapter qgen.DB_Adapter) error {
adapter.SimpleDelete("delete_forum_perms_by_forum","forums_permissions","fid = ?")
adapter.SimpleDelete("delete_activity_stream_match","activity_stream_matches","watcher = ? AND asid = ?")
//adapter.SimpleDelete("delete_activity_stream_matches_by_watcher","activity_stream_matches","watcher = ?")
adapter.SimpleDelete("delete_word_filter","word_filters","wfid = ?")
return nil
}

View File

@ -54,6 +54,12 @@ func routes() {
Route{"route_panel_settings","/panel/settings/","",[]string{}},
Route{"route_panel_setting","/panel/settings/edit/","",[]string{"extra_data"}},
Route{"route_panel_setting_edit","/panel/settings/edit/submit/","",[]string{"extra_data"}},
Route{"route_panel_word_filters","/panel/settings/word-filters/","",[]string{}},
Route{"route_panel_word_filters_create","/panel/settings/word-filters/create/","",[]string{}},
Route{"route_panel_word_filters_edit","/panel/settings/word-filters/edit/","",[]string{"extra_data"}},
Route{"route_panel_word_filters_edit_submit","/panel/settings/word-filters/edit/submit/","",[]string{"extra_data"}},
Route{"route_panel_word_filters_delete_submit","/panel/settings/word-filters/delete/submit/","",[]string{"extra_data"}},
Route{"route_panel_themes","/panel/themes/","",[]string{}},
Route{"route_panel_themes_default","/panel/themes/default/","",[]string{"extra_data"}},

View File

@ -21,6 +21,7 @@ CREATE TABLE `users` (
`bigposts` int DEFAULT 0 not null,
`megaposts` int DEFAULT 0 not null,
`topics` int DEFAULT 0 not null,
`temp_group` int DEFAULT 0 not null,
primary key(`uid`),
unique(`name`)
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;

View File

@ -0,0 +1,9 @@
CREATE TABLE `users_groups_scheduler` (
`uid` int not null,
`set_group` int not null,
`issued_by` int not null,
`issued_at` datetime not null,
`revert_at` datetime not null,
`temporary` boolean not null,
primary key(`uid`)
);

View File

@ -0,0 +1,6 @@
CREATE TABLE `word_filters` (
`wfid` int not null AUTO_INCREMENT,
`find` varchar(200) not null,
`replacement` varchar(200) not null,
primary key(`wfid`)
);

View File

@ -21,6 +21,7 @@ CREATE TABLE `users` (
`bigposts` int DEFAULT 0 not null,
`megaposts` int DEFAULT 0 not null,
`topics` int DEFAULT 0 not null,
`temp_group` int DEFAULT 0 not null,
primary key(`uid`),
unique(`name`)
);

View File

@ -0,0 +1,9 @@
CREATE TABLE `users_groups_scheduler` (
`uid` int not null,
`set_group` int not null,
`issued_by` int not null,
`issued_at` timestamp not null,
`revert_at` timestamp not null,
`temporary` boolean not null,
primary key(`uid`)
);

View File

@ -0,0 +1,6 @@
CREATE TABLE `word_filters` (
`wfid` serial not null,
`find` varchar (200) not null,
`replacement` varchar (200) not null,
primary key(`wfid`)
);

29
tasks.go Normal file
View File

@ -0,0 +1,29 @@
package main
import "time"
func handle_expired_scheduled_groups() error {
rows, err := get_expired_scheduled_groups_stmt.Query()
if err != nil {
return err
}
defer rows.Close()
var uid int
for rows.Next() {
err := rows.Scan(&uid)
if err != nil {
return err
}
_, err = replace_schedule_group_stmt.Exec(uid, 0, 0, time.Now(), false)
if err != nil {
return err
}
_, err = set_temp_group_stmt.Exec(0,uid)
if err != nil {
return err
}
_ = users.Load(uid)
}
return rows.Err()
}

244
template_init.go Normal file
View File

@ -0,0 +1,244 @@
package main
import "log"
import "html/template"
import "net/http"
var templates = template.New("")
func interpreted_topic_template(pi TopicPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["topic"]
if !ok {
mapping = "topic"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
}
var template_topic_handle func(TopicPage,http.ResponseWriter) = interpreted_topic_template
var template_topic_alt_handle func(TopicPage,http.ResponseWriter) = interpreted_topic_template
var template_topics_handle func(TopicsPage,http.ResponseWriter) = func(pi TopicsPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["topics"]
if !ok {
mapping = "topics"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
}
var template_forum_handle func(ForumPage,http.ResponseWriter) = func(pi ForumPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["forum"]
if !ok {
mapping = "forum"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
}
var template_forums_handle func(ForumsPage,http.ResponseWriter) = func(pi ForumsPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["forums"]
if !ok {
mapping = "forums"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
}
var template_profile_handle func(ProfilePage,http.ResponseWriter) = func(pi ProfilePage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["profile"]
if !ok {
mapping = "profile"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
}
var template_create_topic_handle func(CreateTopicPage,http.ResponseWriter) = func(pi CreateTopicPage, w http.ResponseWriter) {
mapping, ok := themes[defaultTheme].TemplatesMap["create-topic"]
if !ok {
mapping = "create-topic"
}
err := templates.ExecuteTemplate(w,mapping + ".html", pi)
if err != nil {
InternalError(err,w)
}
}
func compile_templates() error {
var c CTemplateSet
user := User{62,build_profile_url("fake-user",62),"Fake User","compiler@localhost",0,false,false,false,false,false,false,GuestPerms,make(map[string]bool),"",false,"","","","","",0,0,"0.0.0.0.0",0}
// TO-DO: Do a more accurate level calculation for this?
user2 := User{1,build_profile_url("admin-alice",1),"Admin Alice","alice@localhost",1,true,true,true,true,false,false,AllPerms,make(map[string]bool),"",true,"","","","","",58,1000,"127.0.0.1",0}
user3 := User{2,build_profile_url("admin-fred",62),"Admin Fred","fred@localhost",1,true,true,true,true,false,false,AllPerms,make(map[string]bool),"",true,"","","","","",42,900,"::1",0}
headerVars := HeaderVars{
Site: site,
NoticeList: []string{"test"},
Stylesheets: []string{"panel"},
Scripts: []string{"whatever"},
Widgets: PageWidgets{
LeftSidebar: template.HTML("lalala"),
},
}
log.Print("Compiling the templates")
topic := TopicUser{1,"blah","Blah","Hey there!",0,false,false,"Date","Date",0,"","127.0.0.1",0,1,"classname","weird-data",build_profile_url("fake-user",62),"Fake User",config.DefaultGroup,"",0,"","","","",58,false}
var replyList []Reply
replyList = append(replyList, Reply{0,0,"Yo!","Yo!",0,"alice","Alice",config.DefaultGroup,"",0,0,"","",0,"","","","",0,"127.0.0.1",false,1,"",""})
var varList map[string]VarItem = make(map[string]VarItem)
tpage := TopicPage{"Title",user,headerVars,replyList,topic,1,1,extData}
topic_id_tmpl, err := c.compile_template("topic.html","templates/","TopicPage", tpage, varList)
if err != nil {
return err
}
topic_id_alt_tmpl, err := c.compile_template("topic_alt.html","templates/","TopicPage", tpage, varList)
if err != nil {
return err
}
varList = make(map[string]VarItem)
ppage := ProfilePage{"User 526",user,headerVars,replyList,user,extData}
profile_tmpl, err := c.compile_template("profile.html","templates/","ProfilePage", ppage, varList)
if err != nil {
return err
}
var forumList []Forum
forums, err := fstore.GetAll()
if err != nil {
return err
}
for _, forum := range forums {
if forum.Active {
forumList = append(forumList,*forum)
}
}
varList = make(map[string]VarItem)
forums_page := ForumsPage{"Forum List",user,headerVars,forumList,extData}
forums_tmpl, err := c.compile_template("forums.html","templates/","ForumsPage",forums_page,varList)
if err != nil {
return err
}
var topicsList []*TopicsRow
topicsList = append(topicsList,&TopicsRow{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",user3.ID,1,"","127.0.0.1",0,1,"classname","",&user2,"",0,&user3,"General","/forum/general.2"})
topics_page := TopicsPage{"Topic List",user,headerVars,topicsList,extData}
topics_tmpl, err := c.compile_template("topics.html","templates/","TopicsPage",topics_page,varList)
if err != nil {
return err
}
//var topicList []TopicUser
//topicList = append(topicList,TopicUser{1,"topic-title","Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"classname","","admin-fred","Admin Fred",config.DefaultGroup,"",0,"","","","",58,false})
forum_item := Forum{1,"general","General Forum","Where the general stuff happens",true,"all",0,"",0,"","",0,"",0,""}
forum_page := ForumPage{"General Forum",user,headerVars,topicsList,forum_item,1,1,extData}
forum_tmpl, err := c.compile_template("forum.html","templates/","ForumPage",forum_page,varList)
if err != nil {
return err
}
log.Print("Writing the templates")
go write_template("topic", topic_id_tmpl)
go write_template("topic_alt", topic_id_alt_tmpl)
go write_template("profile", profile_tmpl)
go write_template("forums", forums_tmpl)
go write_template("topics", topics_tmpl)
go write_template("forum", forum_tmpl)
go func() {
err := write_file("./template_list.go","package main\n\n" + c.FragOut)
if err != nil {
log.Fatal(err)
}
}()
return nil
}
func write_template(name string, content string) {
err := write_file("./template_" + name + ".go", content)
if err != nil {
log.Fatal(err)
}
}
func init_templates() {
if dev.DebugMode {
log.Print("Initialising the template system")
}
compile_templates()
// TO-DO: Add support for 64-bit integers
// TO-DO: Add support for floats
fmap := make(map[string]interface{})
fmap["add"] = func(left interface{}, right interface{})interface{} {
var left_int int
var right_int int
switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
return left_int + right_int
}
fmap["subtract"] = func(left interface{}, right interface{})interface{} {
var left_int int
var right_int int
switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
return left_int - right_int
}
fmap["multiply"] = func(left interface{}, right interface{})interface{} {
var left_int int
var right_int int
switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
return left_int * right_int
}
fmap["divide"] = func(left interface{}, right interface{})interface{} {
var left_int int
var right_int int
switch left := left.(type) {
case uint, uint8, uint16, int, int32: left_int = left.(int)
}
switch right := right.(type) {
case uint, uint8, uint16, int, int32: right_int = right.(int)
}
if left_int == 0 || right_int == 0 {
return 0
}
return left_int / right_int
}
// The interpreted templates...
if dev.DebugMode {
log.Print("Loading the template files...")
}
templates.Funcs(fmap)
template.Must(templates.ParseGlob("templates/*"))
template.Must(templates.ParseGlob("pages/*"))
}

View File

@ -34,7 +34,7 @@ var menu_0 []byte = []byte(`<nav class="nav">
<div class="move_left">
<div class="move_right">
<ul>
<li class="menu_left menu_overview"><a href="/">`)
<li class="menu_left menu_overview"><a href="/" rel="home">`)
var menu_1 []byte = []byte(`</a></li>
<li class="menu_left menu_forums"><a href="/forums/">Forums</a></li>
<li class="menu_left menu_topics"><a href="/">Topics</a></li>
@ -77,14 +77,14 @@ var topic_0 []byte = []byte(`
<form id="edit_topic_form" action='/topic/edit/submit/`)
var topic_1 []byte = []byte(`' method="post"></form>
`)
var topic_2 []byte = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" href="/topic/`)
var topic_2 []byte = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/topic/`)
var topic_3 []byte = []byte(`?page=`)
var topic_4 []byte = []byte(`">&lt;</a></div>`)
var topic_5 []byte = []byte(`<link rel="prerender" href="/topic/`)
var topic_6 []byte = []byte(`?page=`)
var topic_7 []byte = []byte(`" />
<div id="nextFloat" class="next_button">
<a class="next_link" href="/topic/`)
<a class="next_link" aria-label="Go to the next page" rel="next" href="/topic/`)
var topic_8 []byte = []byte(`?page=`)
var topic_9 []byte = []byte(`">&gt;</a>
</div>`)
@ -97,8 +97,8 @@ var topic_10 []byte = []byte(`
var topic_11 []byte = []byte(` topic_sticky_head`)
var topic_12 []byte = []byte(` topic_closed_head`)
var topic_13 []byte = []byte(`">
<a class='topic_name hide_on_edit'>`)
var topic_14 []byte = []byte(`</a>
<h1 class='topic_name hide_on_edit'>`)
var topic_14 []byte = []byte(`</h1>
`)
var topic_15 []byte = []byte(`<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>`)
var topic_16 []byte = []byte(`
@ -240,8 +240,8 @@ var topic_87 []byte = []byte(`
`)
var footer_0 []byte = []byte(` </div>
`)
var footer_1 []byte = []byte(`<div class="sidebar">`)
var footer_2 []byte = []byte(`</div>`)
var footer_1 []byte = []byte(`<aside class="sidebar">`)
var footer_2 []byte = []byte(`</aside>`)
var footer_3 []byte = []byte(`
<div style="clear: both;"></div>
</div>
@ -249,13 +249,13 @@ var footer_3 []byte = []byte(`
</body>
</html>
`)
var topic_alt_0 []byte = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" href="/topic/`)
var topic_alt_0 []byte = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/topic/`)
var topic_alt_1 []byte = []byte(`?page=`)
var topic_alt_2 []byte = []byte(`">&lt;</a></div>`)
var topic_alt_3 []byte = []byte(`<link rel="prerender" href="/topic/`)
var topic_alt_4 []byte = []byte(`?page=`)
var topic_alt_5 []byte = []byte(`" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/topic/`)
<div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/topic/`)
var topic_alt_6 []byte = []byte(`?page=`)
var topic_alt_7 []byte = []byte(`">&gt;</a></div>`)
var topic_alt_8 []byte = []byte(`
@ -269,8 +269,8 @@ var topic_alt_9 []byte = []byte(`' method="post">
var topic_alt_10 []byte = []byte(` topic_sticky_head`)
var topic_alt_11 []byte = []byte(` topic_closed_head`)
var topic_alt_12 []byte = []byte(`">
<a class='topic_name hide_on_edit'>`)
var topic_alt_13 []byte = []byte(`</a>
<h1 class='topic_name hide_on_edit'>`)
var topic_alt_13 []byte = []byte(`</h1>
`)
var topic_alt_14 []byte = []byte(`<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>`)
var topic_alt_15 []byte = []byte(`
@ -424,9 +424,9 @@ var topic_alt_88 []byte = []byte(`
var profile_0 []byte = []byte(`
<div id="profile_left_lane" class="colstack_left">
<!--<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>Profile</a></div>
</div>-->
<!--<header class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>Profile</h1></div>
</header>-->
<div id="profile_left_pane" class="rowmenu">
<div class="rowitem avatarRow" style="padding: 0;">
<img src="`)
@ -449,67 +449,108 @@ var profile_7 []byte = []byte(`<a href="/users/unban/`)
var profile_8 []byte = []byte(`?session=`)
var profile_9 []byte = []byte(`" class="profile_menu_item">Unban</a>
`)
var profile_10 []byte = []byte(`<a href="/users/ban/`)
var profile_11 []byte = []byte(`?session=`)
var profile_12 []byte = []byte(`" class="profile_menu_item">Ban</a>`)
var profile_13 []byte = []byte(`
var profile_10 []byte = []byte(`<a href="#ban_user" class="profile_menu_item">Ban</a>`)
var profile_11 []byte = []byte(`
</div>`)
var profile_14 []byte = []byte(`
var profile_12 []byte = []byte(`
<div class="rowitem passive">
<a href="/report/submit/`)
var profile_15 []byte = []byte(`?session=`)
var profile_16 []byte = []byte(`&type=user" class="profile_menu_item report_item">Report</a>
var profile_13 []byte = []byte(`?session=`)
var profile_14 []byte = []byte(`&type=user" class="profile_menu_item report_item">Report</a>
</div>
</div>
</div>
<div id="profile_right_lane" class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Comments</a></div>
`)
var profile_15 []byte = []byte(`
<!-- TO-DO: Inline the display: none; CSS -->
<div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display: none;">
<div class="rowitem"><h1>Ban User</h1></div>
</div>
<div id="profile_comments" class="colstack_item" style="overflow: hidden;border-top: none;">`)
var profile_17 []byte = []byte(`
<form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/`)
var profile_16 []byte = []byte(`?session=`)
var profile_17 []byte = []byte(`" method="post" style="display: none;">
`)
var profile_18 []byte = []byte(`
<div class="colline">If all the fields are left blank, the ban will be permanent.</div>
<div class="colstack_item">
<div class="formrow real_first_child">
<div class="formitem formlabel"><a>Days</a></div>
<div class="formitem">
<input name="ban-duration-days" type="number" value="0" min="0" />
</div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>Weeks</a></div>
<div class="formitem">
<input name="ban-duration-weeks" type="number" value="0" min="0" />
</div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>Months</a></div>
<div class="formitem">
<input name="ban-duration-months" type="number" value="0" min="0" />
</div>
</div>
<!--<div class="formrow">
<div class="formitem formlabel"><a>Reason</a></div>
<div class="formitem"><textarea name="ban-reason" placeholder="A really horrible person" required></textarea></div>
</div>-->
<div class="formrow">
<div class="formitem"><button name="ban-button" class="formbutton form_middle_button">Ban User</button></div>
</div>
</div>
</form>
`)
var profile_19 []byte = []byte(`
<div id="profile_comments_head" class="colstack_item colstack_head hash_hide">
<div class="rowitem"><h1>Comments</h1></div>
</div>
<div id="profile_comments" class="colstack_item hash_hide" style="overflow: hidden;border-top: none;">`)
var profile_20 []byte = []byte(`
<div class="rowitem passive deletable_block editable_parent simple `)
var profile_18 []byte = []byte(`" style="`)
var profile_19 []byte = []byte(`background-image: url(`)
var profile_20 []byte = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `)
var profile_21 []byte = []byte(`-1`)
var profile_22 []byte = []byte(`0px;`)
var profile_23 []byte = []byte(`">
var profile_21 []byte = []byte(`" style="`)
var profile_22 []byte = []byte(`background-image: url(`)
var profile_23 []byte = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `)
var profile_24 []byte = []byte(`-1`)
var profile_25 []byte = []byte(`0px;`)
var profile_26 []byte = []byte(`">
<span class="editable_block user_content simple">`)
var profile_24 []byte = []byte(`</span>
var profile_27 []byte = []byte(`</span>
<span class="controls">
<a href="`)
var profile_25 []byte = []byte(`" class="real_username username">`)
var profile_26 []byte = []byte(`</a>&nbsp;&nbsp;
var profile_28 []byte = []byte(`" class="real_username username">`)
var profile_29 []byte = []byte(`</a>&nbsp;&nbsp;
`)
var profile_27 []byte = []byte(`<a href="/profile/reply/edit/submit/`)
var profile_28 []byte = []byte(`" class="mod_button" title="Edit Item"><button class="username edit_item edit_label"></button></a>
var profile_30 []byte = []byte(`<a href="/profile/reply/edit/submit/`)
var profile_31 []byte = []byte(`" class="mod_button" title="Edit Item"><button class="username edit_item edit_label"></button></a>
<a href="/profile/reply/delete/submit/`)
var profile_29 []byte = []byte(`" class="mod_button" title="Delete Item"><button class="username delete_item trash_label"></button></a>`)
var profile_30 []byte = []byte(`
var profile_32 []byte = []byte(`" class="mod_button" title="Delete Item"><button class="username delete_item trash_label"></button></a>`)
var profile_33 []byte = []byte(`
<a class="mod_button" href="/report/submit/`)
var profile_31 []byte = []byte(`?session=`)
var profile_32 []byte = []byte(`&type=user-reply"><button class="username report_item flag_label"></button></a>
var profile_34 []byte = []byte(`?session=`)
var profile_35 []byte = []byte(`&type=user-reply"><button class="username report_item flag_label"></button></a>
`)
var profile_33 []byte = []byte(`<a class="username hide_on_mobile user_tag" style="float: right;">`)
var profile_34 []byte = []byte(`</a>`)
var profile_35 []byte = []byte(`
var profile_36 []byte = []byte(`<a class="username hide_on_mobile user_tag" style="float: right;">`)
var profile_37 []byte = []byte(`</a>`)
var profile_38 []byte = []byte(`
</span>
</div>
`)
var profile_36 []byte = []byte(`</div>
var profile_39 []byte = []byte(`</div>
`)
var profile_37 []byte = []byte(`
<form action="/profile/reply/create/" method="post">
var profile_40 []byte = []byte(`
<form id="profile_comments_form" class="hash_hide" action="/profile/reply/create/" method="post">
<input name="uid" value='`)
var profile_38 []byte = []byte(`' type="hidden" />
var profile_41 []byte = []byte(`' type="hidden" />
<div class="colstack_item topic_reply_form" style="border-top: none;">
<div class="formrow">
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here"></textarea></div>
@ -520,9 +561,29 @@ var profile_38 []byte = []byte(`' type="hidden" />
</div>
</form>
`)
var profile_39 []byte = []byte(`
var profile_42 []byte = []byte(`
</div>
`)
var profile_43 []byte = []byte(`
<script type="text/javascript">
function handle_profile_hashbit() {
var hash_class = ""
switch(window.location.hash.substr(1)) {
case "ban_user":
hash_class = "ban_user_hash"
break
default:
console.log("Unknown hashbit")
return
}
$(".hash_hide").hide()
$("." + hash_class).show()
}
if(window.location.hash) handle_profile_hashbit()
window.addEventListener("hashchange", handle_profile_hashbit, false)
</script>
`)
var forums_0 []byte = []byte(`
<main>
@ -573,7 +634,7 @@ var topics_0 []byte = []byte(`
<main>
<div class="rowblock rowhead">
<div class="rowitem"><a>Topic List</a></div>
<div class="rowitem"><h1>Topic List</h1></div>
</div>
<div id="topic_list" class="rowblock topic_list">
`)
@ -628,24 +689,24 @@ var topics_30 []byte = []byte(`
</main>
`)
var forum_0 []byte = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" href="/forum/`)
var forum_0 []byte = []byte(`<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/forum/`)
var forum_1 []byte = []byte(`?page=`)
var forum_2 []byte = []byte(`">&lt;</a></div>`)
var forum_3 []byte = []byte(`<link rel="prerender" href="/forum/`)
var forum_4 []byte = []byte(`?page=`)
var forum_5 []byte = []byte(`" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/forum/`)
<div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/forum/`)
var forum_6 []byte = []byte(`?page=`)
var forum_7 []byte = []byte(`">&gt;</a></div>`)
var forum_8 []byte = []byte(`
<main>
<div id="forum_head_block" class="rowblock rowhead">
<div class="rowitem forum_title`)
var forum_9 []byte = []byte(` has_opt`)
var forum_10 []byte = []byte(`"><a>`)
var forum_11 []byte = []byte(`</a>
var forum_10 []byte = []byte(`"><h1>`)
var forum_11 []byte = []byte(`</h1>
</div>
`)
var forum_12 []byte = []byte(`

View File

@ -86,66 +86,72 @@ w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_9)
} else {
w.Write(profile_10)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
}
w.Write(profile_11)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
}
w.Write(profile_12)
}
w.Write(profile_13)
}
w.Write(profile_14)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_15)
w.Write(profile_13)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_14)
if tmpl_profile_vars.CurrentUser.Perms.BanUsers {
w.Write(profile_15)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_16)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_17)
w.Write(profile_18)
}
w.Write(profile_19)
if len(tmpl_profile_vars.ItemList) != 0 {
for _, item := range tmpl_profile_vars.ItemList {
w.Write(profile_17)
w.Write([]byte(item.ClassName))
w.Write(profile_18)
if item.Avatar != "" {
w.Write(profile_19)
w.Write([]byte(item.Avatar))
w.Write(profile_20)
if item.ContentLines <= 5 {
w.Write([]byte(item.ClassName))
w.Write(profile_21)
}
if item.Avatar != "" {
w.Write(profile_22)
}
w.Write([]byte(item.Avatar))
w.Write(profile_23)
w.Write([]byte(item.ContentHtml))
if item.ContentLines <= 5 {
w.Write(profile_24)
w.Write([]byte(item.UserLink))
w.Write(profile_25)
w.Write([]byte(item.CreatedByName))
w.Write(profile_26)
if tmpl_profile_vars.CurrentUser.Is_Mod {
w.Write(profile_27)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_28)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_29)
}
w.Write(profile_25)
}
w.Write(profile_26)
w.Write([]byte(item.ContentHtml))
w.Write(profile_27)
w.Write([]byte(item.UserLink))
w.Write(profile_28)
w.Write([]byte(item.CreatedByName))
w.Write(profile_29)
if tmpl_profile_vars.CurrentUser.Is_Mod {
w.Write(profile_30)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_31)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_32)
if item.Tag != "" {
}
w.Write(profile_33)
w.Write([]byte(item.Tag))
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(profile_34)
}
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(profile_35)
}
}
if item.Tag != "" {
w.Write(profile_36)
if !tmpl_profile_vars.CurrentUser.Is_Banned {
w.Write([]byte(item.Tag))
w.Write(profile_37)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
}
w.Write(profile_38)
}
}
w.Write(profile_39)
if !tmpl_profile_vars.CurrentUser.Is_Banned {
w.Write(profile_40)
w.Write([]byte(strconv.Itoa(tmpl_profile_vars.ProfileOwner.ID)))
w.Write(profile_41)
}
w.Write(profile_42)
w.Write(profile_43)
w.Write(footer_0)
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" {
w.Write(footer_1)

View File

@ -1,6 +1,6 @@
<nav class="colstack_left">
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>My Account</a></div>
<div class="rowitem"><h1>My Account</h1></div>
</div>
<div class="colstack_item rowmenu">
<div class="rowitem passive"><a href="/user/edit/avatar/">Avatar</a></div>

View File

@ -2,7 +2,7 @@
{{template "account-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>Edit Avatar</a></div>
<div class="rowitem"><h1>Edit Avatar</h1></div>
</div>
{{if .CurrentUser.Avatar}}
<div class="colstack_item">

View File

@ -2,7 +2,7 @@
{{template "account-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>Emails</a></div>
<div class="rowitem"><h1>Emails</h1></div>
</div>
<div class="colstack_item">
<!-- TO-DO: Do we need this inline CSS? -->

View File

@ -2,7 +2,7 @@
{{template "account-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>Edit Username</a></div>
<div class="rowitem"><h1>Edit Username</h1></div>
</div>
<div class="colstack_item">
<form action="/user/edit/username/submit/" method="post">

View File

@ -2,7 +2,7 @@
{{template "account-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>Edit Password</a></div>
<div class="rowitem"><h1>Edit Password</h1></div>
</div>
<div class="colstack_item">
<form action="/user/edit/critical/submit/" method="post">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }}
<main>
<div class="rowblock rowhead">
<div class="rowitem"><a>Are you sure?</a></div>
<div class="rowitem"><h1>Are you sure?</h1></div>
</div>
<div class="rowblock">
<div class="rowitem passive">{{.Something.Message}}<br /><br />

View File

@ -1,7 +1,7 @@
{{template "header.html" . }}
<main>
<div class="rowblock rowhead">
<div class="rowitem"><a>Create Topic</a></div>
<div class="rowitem"><h1>Create Topic</h1></div>
</div>
<div class="rowblock">
<form action="/topic/create/submit/" method="post">

View File

@ -1,7 +1,7 @@
{{template "header.html" . }}
<main>
<div class="rowblock rowhead">
<div class="rowitem"><a>An error has occured</a></div>
<div class="rowitem"><h1>An error has occured</h1></div>
</div>
<div class="rowblock">
<div class="rowitem passive">{{.Something}}</div>

View File

@ -1,5 +1,5 @@
</div>
{{if .Header.Widgets.RightSidebar}}<div class="sidebar">{{.Header.Widgets.RightSidebar}}</div>{{end}}
{{if .Header.Widgets.RightSidebar}}<aside class="sidebar">{{.Header.Widgets.RightSidebar}}</aside>{{end}}
<div style="clear: both;"></div>
</div>
</div>

View File

@ -1,13 +1,13 @@
{{template "header.html" . }}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/forum/{{.Forum.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/forum/{{.Forum.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<main>
<div id="forum_head_block" class="rowblock rowhead">
<div class="rowitem forum_title{{if ne .CurrentUser.ID 0}} has_opt{{end}}"><a>{{.Title}}</a>
<div class="rowitem forum_title{{if ne .CurrentUser.ID 0}} has_opt{{end}}"><h1>{{.Title}}</h1>
</div>
{{if ne .CurrentUser.ID 0}}
{{if .CurrentUser.Perms.CreateTopic}}

View File

@ -1,6 +1,6 @@
{{template "header.html" . }}
<div class="rowblock rowhead">
<div class="rowitem"><a>Login</a></div>
<div class="rowitem"><h1>Login</h1></div>
</div>
<div class="rowblock">
<form action="/accounts/login/submit/" method="post">

View File

@ -2,7 +2,7 @@
<div class="move_left">
<div class="move_right">
<ul>
<li class="menu_left menu_overview"><a href="/">{{.Header.Site.Name}}</a></li>
<li class="menu_left menu_overview"><a href="/" rel="home">{{.Header.Site.Name}}</a></li>
<li class="menu_left menu_forums"><a href="/forums/">Forums</a></li>
<li class="menu_left menu_topics"><a href="/">Topics</a></li>
<li class="menu_left menu_create_topic"><a href="/topics/create/">Create Topic</a></li>

View File

@ -11,7 +11,7 @@
</nav>
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Administration Logs</a></div>
<div class="rowitem"><h1>Administration Logs</h1></div>
</div>
<div id="panel_adminlogs" class="colstack_item rowlist">
{{range .Logs}}

View File

@ -6,7 +6,7 @@ var form_vars = {'perm_preset': ['can_moderate','can_post','read_only','no_acces
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>{{.Name}} Forum</a></div>
<div class="rowitem"><h1>{{.Name}} Forum</h1></div>
</div>
<div id="panel_forum" class="colstack_item">
<form action="/panel/forums/edit/submit/{{.ID}}?session={{.CurrentUser.Session}}" method="post">

View File

@ -7,7 +7,7 @@
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Forums</a></div>
<div class="rowitem"><h1>Forums</h1></div>
</div>
<div id="panel_forums" class="colstack_item rowlist">
{{range .ItemList}}
@ -34,7 +34,7 @@
</div>
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Add Forum</a></div>
<div class="rowitem"><h1>Add Forum</h1></div>
</div>
<div class="colstack_item">
<form action="/panel/forums/create/?session={{.CurrentUser.Session}}" method="post">

View File

@ -12,7 +12,7 @@
</nav>
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>{{.Name}} Group</a></div>
<div class="rowitem"><h1>{{.Name}} Group</h1></div>
</div>
<form action="/panel/groups/edit/perms/submit/{{.ID}}?session={{.CurrentUser.Session}}" method="post">
<div id="panel_group" class="colstack_item rowlist">
@ -36,7 +36,7 @@
</div>
</div>
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Extended Permissions</a></div>
<div class="rowitem"><h1>Extended Permissions</h1></div>
</div>
<div class="colstack_item rowlist">
{{if .CurrentUser.Perms.EditGroupGlobalPerms}}

View File

@ -12,7 +12,7 @@
</nav>
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>{{.Name}} Group</a></div>
<div class="rowitem"><h1>{{.Name}} Group</h1></div>
</div>
<div id="panel_group" class="colstack_item">
<form action="/panel/groups/edit/submit/{{.ID}}?session={{.CurrentUser.Session}}" method="post">

View File

@ -3,7 +3,7 @@
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Groups</a></div>
<div class="rowitem"><h1>Groups</h1></div>
</div>
<div id="panel_groups" class="colstack_item rowlist">
{{range .ItemList}}
@ -28,7 +28,7 @@
</div>
{{end}}
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Create Group</a></div>
<div class="rowitem"><h1>Create Group</h1></div>
</div>
<div class="colstack_item">
<form action="/panel/groups/create/?session={{.CurrentUser.Session}}" method="post">

View File

@ -13,6 +13,9 @@
</div>{{end}}
{{if .CurrentUser.Perms.EditSettings}}<div class="rowitem passive">
<a href="/panel/settings/">Settings</a> <a class="menu_stats" href="#">({{.Stats.Settings}})</a>
</div>
<div class="rowitem passive">
<a href="/panel/settings/word-filters/">Word Filters</a> <a class="menu_stats" href="#">({{.Stats.WordFilters}})</a>
</div>{{end}}
{{if .CurrentUser.Perms.ManageThemes}}<div class="rowitem passive">
<a href="/panel/themes/">Themes</a> <a class="menu_stats" href="#">({{.Stats.Themes}})</a>

View File

@ -11,7 +11,7 @@
</nav>
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Moderation Logs</a></div>
<div class="rowitem"><h1>Moderation Logs</h1></div>
</div>
<div id="panel_modlogs" class="colstack_item rowlist">
{{range .Logs}}

View File

@ -2,7 +2,7 @@
{{template "panel-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Plugins</a></div>
<div class="rowitem"><h1>Plugins</h1></div>
</div>
<div id="panel_plugins" class="colstack_item">
{{range .ItemList}}

View File

@ -2,7 +2,7 @@
{{template "panel-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Edit Setting</a></div>
<div class="rowitem"><h1>Edit Setting</h1></div>
</div>
<div id="panel_setting" class="colstack_item">
<form action="/panel/settings/edit/submit/{{.Something.Name}}?session={{.CurrentUser.Session}}" method="post">

View File

@ -2,7 +2,7 @@
{{template "panel-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Settings</a></div>
<div class="rowitem"><h1>Settings</h1></div>
</div>
<div id="panel_settings" class="colstack_item rowlist">
{{range $key, $value := .Something}}

View File

@ -20,7 +20,7 @@
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Primary Themes</a></div>
<div class="rowitem"><h1>Primary Themes</h1></div>
</div>
<div id="panel_primary_themes" class="colstack_item panel_themes">
{{range .PrimaryThemes}}
@ -38,7 +38,7 @@
{{end}}
</div>
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Variant Themes</a></div>
<div class="rowitem"><h1>Variant Themes</h1></div>
</div>
<div id="panel_variant_themes" class="colstack_item panel_themes">
{{range .VariantThemes}}

View File

@ -2,7 +2,7 @@
{{template "panel-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>User Editor</a></div>
<div class="rowitem"><h1>User Editor</h1></div>
</div>
<div id="panel_user" class="colstack_item">
<form action="/panel/users/edit/submit/{{.Something.ID}}?session={{.CurrentUser.Session}}" method="post">

View File

@ -3,7 +3,7 @@
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Users</a></div>
<div class="rowitem"><h1>Users</h1></div>
</div>
<div id="panel_users" class="colstack_item rowlist bgavatars">
{{range .ItemList}}
@ -13,7 +13,7 @@
{{if (.Tag) and (.Is_Super_Mod)}}<span style="float: right;"><span class="panel_tag" style="margin-left 4px;">{{.Tag}}</span></span>{{end}}
<span class="panel_floater">
{{if .Is_Banned}}<a href="/users/unban/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button ban_button">Unban</a>{{else if not .Is_Super_Mod}}<a href="/users/ban/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button ban_button">Ban</a>{{end}}
{{if .Is_Banned}}<a href="/users/unban/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button ban_button">Unban</a>{{else if not .Is_Super_Mod}}<a href="/user/{{.ID}}#ban_user" class="panel_tag panel_right_button ban_button">Ban</a>{{end}}
{{if not .Active}}<a href="/users/activate/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button">Activate</a>{{end}}
</span>
</div>

View File

@ -0,0 +1,45 @@
{{template "header.html" . }}
{{template "panel-menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>Word Filters</h1></div>
</div>
<div id="panel_word_filters" class="colstack_item rowlist">
{{range .Something}}
<div class="rowitem panel_compactrow editable_parent">
<a data-field="find" data-type="text" href="/panel/settings/word-filters/edit/{{.ID}}" class="editable_block panel_upshift edit_fields">{{.Find}}</a>
<span style="padding-left: 2px;padding-right: 2px;"> || </span>
<a data-field="replacement" data-type="text" class="editable_block panel_compacttext">{{.Replacement}}</a>
<span class="panel_buttons">
<a class="panel_tag edit_fields hide_on_edit panel_right_button">Edit</a>
<a class="panel_right_button" href="/panel/settings/word-filters/edit/submit/{{.ID}}"><button class='panel_tag submit_edit show_on_edit' type='submit'>Update</button></a>
<a href="/panel/settings/word-filters/delete/submit/{{.ID}}?session={{$.CurrentUser.Session}}" class="panel_tag panel_right_button hide_on_edit">Delete</a>
</span>
</div>
{{else}}
<div class="rowitem editable_parent">
<a>You don't have any word filters yet.</a>
</div>
{{end}}
</div>
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>Add Filter</h1></div>
</div>
<div class="colstack_item">
<form action="/panel/settings/word-filters/create/?session={{.CurrentUser.Session}}" method="post">
<div class="formrow">
<div class="formitem formlabel"><a>Find</a></div>
<div class="formitem"><input name="find" type="text" placeholder="fuck" /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>Replacement</a></div>
<div class="formitem"><input name="replacement" type="text" placeholder="fudge" /></div>
</div>
<div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton form_middle_button">Add Filter</button></div>
</div>
</form>
</div>
</main>
{{template "footer.html" . }}

View File

@ -1,9 +1,9 @@
{{template "header.html" . }}
<div id="profile_left_lane" class="colstack_left">
<!--<div class="colstack_item colstack_head rowhead">
<div class="rowitem"><a>Profile</a></div>
</div>-->
<!--<header class="colstack_item colstack_head rowhead">
<div class="rowitem"><h1>Profile</h1></div>
</header>-->
<div id="profile_left_pane" class="rowmenu">
<div class="rowitem avatarRow" style="padding: 0;">
<img src="{{.ProfileOwner.Avatar}}" class="avatar" />
@ -16,7 +16,7 @@
</div>
{{if (.CurrentUser.Is_Super_Mod) and not (.ProfileOwner.Is_Super_Mod) }}<div class="rowitem passive">
{{if .ProfileOwner.Is_Banned }}<a href="/users/unban/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}" class="profile_menu_item">Unban</a>
{{else}}<a href="/users/ban/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}" class="profile_menu_item">Ban</a>{{end}}
{{else}}<a href="#ban_user" class="profile_menu_item">Ban</a>{{end}}
</div>{{end}}
<div class="rowitem passive">
<a href="/report/submit/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}&type=user" class="profile_menu_item report_item">Report</a>
@ -25,10 +25,48 @@
</div>
<div id="profile_right_lane" class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><a>Comments</a></div>
{{if .CurrentUser.Perms.BanUsers}}
<!-- TO-DO: Inline the display: none; CSS -->
<div id="ban_user_head" class="colstack_item colstack_head hash_hide ban_user_hash" style="display: none;">
<div class="rowitem"><h1>Ban User</h1></div>
</div>
<div id="profile_comments" class="colstack_item" style="overflow: hidden;border-top: none;">{{range .ItemList}}
<form id="ban_user_form" class="hash_hide ban_user_hash" action="/users/ban/submit/{{.ProfileOwner.ID}}?session={{.CurrentUser.Session}}" method="post" style="display: none;">
{{/** TO-DO: Put a JS duration calculator here instead of this text? **/}}
<div class="colline">If all the fields are left blank, the ban will be permanent.</div>
<div class="colstack_item">
<div class="formrow real_first_child">
<div class="formitem formlabel"><a>Days</a></div>
<div class="formitem">
<input name="ban-duration-days" type="number" value="0" min="0" />
</div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>Weeks</a></div>
<div class="formitem">
<input name="ban-duration-weeks" type="number" value="0" min="0" />
</div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>Months</a></div>
<div class="formitem">
<input name="ban-duration-months" type="number" value="0" min="0" />
</div>
</div>
<!--<div class="formrow">
<div class="formitem formlabel"><a>Reason</a></div>
<div class="formitem"><textarea name="ban-reason" placeholder="A really horrible person" required></textarea></div>
</div>-->
<div class="formrow">
<div class="formitem"><button name="ban-button" class="formbutton form_middle_button">Ban User</button></div>
</div>
</div>
</form>
{{end}}
<div id="profile_comments_head" class="colstack_item colstack_head hash_hide">
<div class="rowitem"><h1>Comments</h1></div>
</div>
<div id="profile_comments" class="colstack_item hash_hide" style="overflow: hidden;border-top: none;">{{range .ItemList}}
<div class="rowitem passive deletable_block editable_parent simple {{.ClassName}}" style="{{if .Avatar}}background-image: url({{.Avatar}}), url(/static/post-avatar-bg.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;{{end}}">
<span class="editable_block user_content simple">{{.ContentHtml}}</span>
@ -47,7 +85,7 @@
{{end}}</div>
{{if not .CurrentUser.Is_Banned}}
<form action="/profile/reply/create/" method="post">
<form id="profile_comments_form" class="hash_hide" action="/profile/reply/create/" method="post">
<input name="uid" value='{{.ProfileOwner.ID}}' type="hidden" />
<div class="colstack_item topic_reply_form" style="border-top: none;">
<div class="formrow">
@ -61,4 +99,24 @@
{{end}}
</div>
{{/** Quick subpage switcher **/}}
{{/** TO-DO: Stop inlining this **/}}
<script type="text/javascript">
function handle_profile_hashbit() {
var hash_class = ""
switch(window.location.hash.substr(1)) {
case "ban_user":
hash_class = "ban_user_hash"
break
default:
console.log("Unknown hashbit")
return
}
$(".hash_hide").hide()
$("." + hash_class).show()
}
if(window.location.hash) handle_profile_hashbit()
window.addEventListener("hashchange", handle_profile_hashbit, false)
</script>
{{template "footer.html" . }}

View File

@ -2,25 +2,25 @@
<main>
<div class="rowblock rowhead">
<div class="rowitem"><a>Create Account</a></div>
<div class="rowitem"><h1>Create Account</h1></div>
</div>
<div class="rowblock">
<form action="/accounts/create/submit/" method="post">
<div class="formrow">
<div class="formitem formlabel"><a>Account Name</a></div>
<div class="formitem"><input name="username" type="text" placeholder="Account Name" required /></div>
<div class="formitem formlabel"><a id="username_label">Account Name</a></div>
<div class="formitem"><input name="username" type="text" placeholder="Account Name" aria-labelledby="username_label" required /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>Email</a></div>
<div class="formitem"><input name="email" type="email" placeholder="joe.doe@example.com" required /></div>
<div class="formitem formlabel"><a id="email_label">Email</a></div>
<div class="formitem"><input name="email" type="email" placeholder="joe.doe@example.com" aria-labelledby="email_label" required /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>Password</a></div>
<div class="formitem"><input name="password" type="password" autocomplete="new-password" placeholder="*****" required /></div>
<div class="formitem formlabel"><a id="password_label">Password</a></div>
<div class="formitem"><input name="password" type="password" autocomplete="new-password" placeholder="*****" aria-labelledby="password_label" required /></div>
</div>
<div class="formrow">
<div class="formitem formlabel"><a>Confirm Password</a></div>
<div class="formitem"><input name="confirm_password" type="password" placeholder="*****" required /></div>
<div class="formitem formlabel"><a id="confirm_password_label">Confirm Password</a></div>
<div class="formitem"><input name="confirm_password" type="password" placeholder="*****" aria-labelledby="confirm_password_label" required /></div>
</div>
<div class="formrow">
<div class="formitem"><button name="register-button" class="formbutton">Create Account</div></div>

View File

@ -2,7 +2,7 @@
<main>
<div class="rowblock rowhead">
<div class="rowitem"><a>Create Group</a></div>
<div class="rowitem"><h1>Create Group</h1></div>
</div>
<div class="rowblock">
<form action="/group/create/submit/" method="post">

View File

@ -1,18 +1,18 @@
{{template "header.html" . }}
<form id="edit_topic_form" action='/topic/edit/submit/{{.Topic.ID}}' method="post"></form>
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/topic/{{.Topic.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/topic/{{.Topic.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender" href="/topic/{{.Topic.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button">
<a class="next_link" href="/topic/{{.Topic.ID}}?page={{add .Page 1}}">&gt;</a>
<a class="next_link" aria-label="Go to the next page" rel="next" href="/topic/{{.Topic.ID}}?page={{add .Page 1}}">&gt;</a>
</div>{{end}}
<main>
<div class="rowblock rowhead topic_block">
<div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.Is_Closed}} topic_closed_head{{end}}">
<a class='topic_name hide_on_edit'>{{.Topic.Title}}</a>
<h1 class='topic_name hide_on_edit'>{{.Topic.Title}}</h1>
{{if .Topic.Is_Closed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}}
{{if .CurrentUser.Perms.EditTopic}}
<input form='edit_topic_form' class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" />

View File

@ -1,14 +1,14 @@
{{template "header.html" . }}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" href="/topic/{{.Topic.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="Go to the previous page" rel="prev" href="/topic/{{.Topic.ID}}?page={{subtract .Page 1}}">&lt;</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender" href="/topic/{{.Topic.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/topic/{{.Topic.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<div id="nextFloat" class="next_button"><a class="next_link" aria-label="Go to the next page" rel="next" href="/topic/{{.Topic.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<main>
<div class="rowblock rowhead topic_block">
<form action='/topic/edit/submit/{{.Topic.ID}}' method="post">
<div class="rowitem topic_item{{if .Topic.Sticky}} topic_sticky_head{{else if .Topic.Is_Closed}} topic_closed_head{{end}}">
<a class='topic_name hide_on_edit'>{{.Topic.Title}}</a>
<h1 class='topic_name hide_on_edit'>{{.Topic.Title}}</h1>
{{if .Topic.Is_Closed}}<span class='username hide_on_micro topic_status_e topic_status_closed hide_on_edit' title='Status: Closed' style="font-weight:normal;float: right;position:relative;top:-5px;">&#x1F512;&#xFE0E</span>{{end}}
{{if .CurrentUser.Perms.EditTopic}}
<input class='show_on_edit topic_name_input' name="topic_name" value='{{.Topic.Title}}' type="text" />

View File

@ -2,7 +2,7 @@
<main>
<div class="rowblock rowhead">
<div class="rowitem"><a>Topic List</a></div>
<div class="rowitem"><h1>Topic List</h1></div>
</div>
<div id="topic_list" class="rowblock topic_list">
{{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .Is_Closed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">

View File

@ -1,5 +1,5 @@
<div class="rowblock rowhead">
<div class="rowitem">{{.Name}}</div>
<div class="rowitem"><h1>{{.Name}}</h1></div>
</div>
<nav class="rowblock">{{range .MenuList}}
<div class="rowitem {{if .Compact}}datarow{{end}}"><a href="{{.Location}}">{{.Text}}</a></div>

View File

@ -1,5 +1,5 @@
<div class="rowblock rowhead">
<div class="rowitem">{{.Name}}</div>
<div class="rowitem"><h1>{{.Name}}</h1></div>
</div>
<div class="rowblock">
<div class="rowitem">{{.Text}}</div>

View File

@ -147,6 +147,14 @@ a {
padding: 12px;
}
.rowitem h1 {
font-size: 16px;
font-weight: normal;
-webkit-margin-before: 0;
-webkit-margin-after: 0;
display: inline;
}
.rowsmall {
font-size: 12px;
}
@ -166,6 +174,13 @@ a {
display: none;
}
.colline {
font-size: 14px;
background-color: rgb(61,61,61);
margin-top: 5px;
padding: 10px;
}
/* Topic View */
/* TO-DO: How should we handle the sticky headers? */

View File

@ -38,9 +38,9 @@ func NewMemoryTopicStore(capacity int) *MemoryTopicStore {
log.Fatal(err)
}
return &MemoryTopicStore{
items:make(map[int]*Topic),
capacity:capacity,
get:stmt,
items: make(map[int]*Topic),
capacity: capacity,
get: stmt,
}
}

56
user.go
View File

@ -1,9 +1,11 @@
package main
import (
//"log"
//"fmt"
"strings"
"strconv"
"time"
"net"
"net/http"
"html/template"
@ -51,6 +53,7 @@ type User struct
Level int
Score int
Last_IP string
TempGroup int
}
type Email struct
@ -62,6 +65,52 @@ type Email struct
Token string
}
// duration in seconds
func (user *User) Ban(duration time.Duration, issuedBy int) error {
return user.ScheduleGroupUpdate(4,issuedBy,duration)
}
func (user *User) Unban() error {
err := user.RevertGroupUpdate()
if err != nil {
return err
}
return users.Load(user.ID)
}
// TO-DO: Use a transaction to avoid race conditions
// Make this more stateless?
func (user *User) ScheduleGroupUpdate(gid int, issuedBy int, duration time.Duration) error {
var temporary bool
if duration.Nanoseconds() != 0 {
temporary = true
}
revertAt := time.Now().Add(duration)
_, err := replace_schedule_group_stmt.Exec(user.ID, gid, issuedBy, revertAt, temporary)
if err != nil {
return err
}
_, err = set_temp_group_stmt.Exec(gid,user.ID)
if err != nil {
return err
}
return users.Load(user.ID)
}
// TO-DO: Use a transaction to avoid race conditions
func (user *User) RevertGroupUpdate() error {
_, err := replace_schedule_group_stmt.Exec(user.ID, 0, 0, time.Now(), false)
if err != nil {
return err
}
_, err = set_temp_group_stmt.Exec(0,user.ID)
if err != nil {
return err
}
return users.Load(user.ID)
}
func BcryptCheckPassword(real_password string, password string, salt string) (err error) {
return bcrypt.CompareHashAndPassword([]byte(real_password), []byte(password + salt))
}
@ -245,7 +294,8 @@ func _panel_session_check(w http.ResponseWriter, r *http.Request, user *User) (h
stats.Users = users.GetGlobalCount()
stats.Forums = fstore.GetGlobalCount() // TO-DO: Stop it from showing the blanked forums
stats.Settings = len(headerVars.Settings) // TO-DO: IS this racey?
stats.Settings = len(headerVars.Settings)
stats.WordFilters = len(wordFilterBox.Load().(WordFilterBox))
stats.Themes = len(themes)
stats.Reports = 0 // TO-DO: Do the report count. Only show open threads?
@ -447,6 +497,10 @@ func init_user_perms(user *User) {
user.PluginPerms = groups[user.Group].PluginPerms
}
if user.TempGroup != 0 {
user.Group = user.TempGroup
}
user.Is_Admin = user.Is_Super_Admin || groups[user.Group].Is_Admin
user.Is_Super_Mod = user.Is_Admin || groups[user.Group].Is_Mod
user.Is_Mod = user.Is_Super_Mod

View File

@ -47,7 +47,7 @@ type MemoryUserStore struct {
}
func NewMemoryUserStore(capacity int) *MemoryUserStore {
get_stmt, err := qgen.Builder.SimpleSelect("users","name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip","uid = ?","","")
get_stmt, err := qgen.Builder.SimpleSelect("users","name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group","uid = ?","","")
if err != nil {
log.Fatal(err)
}
@ -70,12 +70,12 @@ func NewMemoryUserStore(capacity int) *MemoryUserStore {
}
return &MemoryUserStore{
items:make(map[int]*User),
capacity:capacity,
get:get_stmt,
register:register_stmt,
username_exists:username_exists_stmt,
user_count:user_count_stmt,
items: make(map[int]*User),
capacity: capacity,
get: get_stmt,
register: register_stmt,
username_exists: username_exists_stmt,
user_count: user_count_stmt,
}
}
@ -106,7 +106,7 @@ func (sus *MemoryUserStore) CascadeGet(id int) (*User, error) {
}
user = &User{ID:id,Loggedin:true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup)
if user.Avatar != "" {
if user.Avatar[0] == '.' {
@ -168,7 +168,7 @@ func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, er
}
qlist = qlist[0:len(qlist) - 1]
stmt, err := qgen.Builder.SimpleSelect("users","uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip","uid IN("+qlist+")","","")
stmt, err := qgen.Builder.SimpleSelect("users","uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group","uid IN("+qlist+")","","")
if err != nil {
return nil, err
}
@ -180,7 +180,7 @@ func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, er
for rows.Next() {
user := &User{Loggedin:true}
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup)
if err != nil {
return nil, err
}
@ -234,7 +234,7 @@ func (sus *MemoryUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, er
func (sus *MemoryUserStore) BypassGet(id int) (*User, error) {
user := &User{ID:id,Loggedin:true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup)
if user.Avatar != "" {
if user.Avatar[0] == '.' {
@ -251,7 +251,7 @@ func (sus *MemoryUserStore) BypassGet(id int) (*User, error) {
func (sus *MemoryUserStore) Load(id int) error {
user := &User{ID:id,Loggedin:true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup)
if err != nil {
sus.Remove(id)
return err
@ -378,7 +378,7 @@ type SqlUserStore struct {
}
func NewSqlUserStore() *SqlUserStore {
get_stmt, err := qgen.Builder.SimpleSelect("users","name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip","uid = ?","","")
get_stmt, err := qgen.Builder.SimpleSelect("users","name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group","uid = ?","","")
if err != nil {
log.Fatal(err)
}
@ -410,7 +410,7 @@ func NewSqlUserStore() *SqlUserStore {
func (sus *SqlUserStore) Get(id int) (*User, error) {
user := User{ID:id,Loggedin:true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup)
if user.Avatar != "" {
if user.Avatar[0] == '.' {
@ -427,7 +427,7 @@ func (sus *SqlUserStore) Get(id int) (*User, error) {
func (sus *SqlUserStore) GetUnsafe(id int) (*User, error) {
user := User{ID:id,Loggedin:true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup)
if user.Avatar != "" {
if user.Avatar[0] == '.' {
@ -444,7 +444,7 @@ func (sus *SqlUserStore) GetUnsafe(id int) (*User, error) {
func (sus *SqlUserStore) CascadeGet(id int) (*User, error) {
user := User{ID:id,Loggedin:true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup)
if user.Avatar != "" {
if user.Avatar[0] == '.' {
@ -469,7 +469,7 @@ func (sus *SqlUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err e
}
qlist = qlist[0:len(qlist) - 1]
stmt, err := qgen.Builder.SimpleSelect("users","uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip","uid IN("+qlist+")","","")
stmt, err := qgen.Builder.SimpleSelect("users","uid, name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group","uid IN("+qlist+")","","")
if err != nil {
return nil, err
}
@ -482,7 +482,7 @@ func (sus *SqlUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err e
list = make(map[int]*User)
for rows.Next() {
user := &User{Loggedin:true}
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
err := rows.Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup)
if err != nil {
return nil, err
}
@ -508,7 +508,7 @@ func (sus *SqlUserStore) BulkCascadeGetMap(ids []int) (list map[int]*User, err e
func (sus *SqlUserStore) BypassGet(id int) (*User, error) {
user := User{ID:id,Loggedin:true}
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup)
if user.Avatar != "" {
if user.Avatar[0] == '.' {
@ -525,9 +525,8 @@ func (sus *SqlUserStore) BypassGet(id int) (*User, error) {
func (sus *SqlUserStore) Load(id int) error {
user := &User{ID:id}
// Simplify this into a quick check whether the user exists
err := sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
return err
// Simplify this into a quick check to see whether the user exists. Add an Exists method to facilitate this?
return sus.get.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP, &user.TempGroup)
}
func (sus *SqlUserStore) CreateUser(username string, password string, email string, group int, active int) (int, error) {