From 5e3b61d910ee85f0f235d98fe6c23fd8240b8e97 Mon Sep 17 00:00:00 2001 From: Azareal Date: Thu, 26 Jan 2017 13:37:50 +0000 Subject: [PATCH] Added pagination for forums. You can now use the left and right keyboard keys to go to the next and previous page. Making the shell files a little friendlier. Needs testing. The main executable will always be named gosora.exe or the Linux equivalent ./Gosora Reports is now a physical forum and not a "virtual" one. Added create_forum() and delete_forum() for creating and deleting forums. Synced the installer's config.go with the main one. Forums are now stored in slices rather than maps for improved performance and concurrency. You can now set a forum as hidden when initially creating it. BBCode tag names may only be seven characters long now. This is part of a new anti-overflow measure that's much faster and simpler than the previous one. Updated the last block of code in routes.go which uses the antiquated "success = 0" error detection method. Fixed a visual bug in the Control Panel CSS. Seperated the system forums from the normal ones in the Forum Manager. You can now see if a forum is marked as Hidden in the Forum Manager. Fixed the position of the lock status indicator. IP Addresses now have a simple title attribute explaining that this long incomprehensible string is in fact an IP Address. Textareas look a little better now. Next / Previous buttons are now visible on mobile. .bat files always say what they're doing now. --- README.md | 6 +- TODO.md | 2 +- build-gosora-linux | 6 +- build.bat | 2 +- config.go | 3 +- data.sql | 2 + forum.go | 42 ++++ images/forum_manager.PNG | Bin 0 -> 38935 bytes install-gosora-linux | 7 +- install.bat | 5 + install/install.go | 3 +- main.go | 7 +- mod_routes.go | 53 ++--- mysql.go | 75 +++++-- pages.go | 3 + plugin_bbcode.go | 29 +-- public/global.js | 9 + routes.go | 89 +++++---- run-gosora-linux | 7 +- run.bat | 2 +- template_forum.go | 64 +++--- template_list.go | 269 ++++++++++++++------------ template_topic.go | 122 ++++++------ template_topic_alt.go | 120 ++++++------ templates/forum.html | 7 +- templates/panel-forums.html | 18 +- templates/panel-menu.html | 4 +- templates/profile.html | 2 +- templates/topic.html | 7 +- templates/topic_alt.html | 11 +- templates/topics.html | 2 +- themes/cosmo-conflux/public/main.css | 9 +- themes/cosmo/public/main.css | 7 +- themes/tempra-conflux/public/main.css | 14 +- themes/tempra-simple/public/main.css | 45 ++--- update-deps-linux | 2 + update-deps.bat | 4 + utils.go | 7 + 38 files changed, 626 insertions(+), 440 deletions(-) create mode 100644 images/forum_manager.PNG diff --git a/README.md b/README.md index a2c3a265..51f30cee 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A super fast forum software written in Go. -The initial code-base was forked from one of my side projects, but has now gone far beyond that. +The initial code-base was forked from one of my side projects, but has now gone far beyond that. We're still fairly early in development, so the code-base might change at an incredible rate. We plan to start stabilising it somewhat once we enter alpha. Azareal's Discord Chat: https://discord.gg/eyYvtTf @@ -106,7 +106,7 @@ We're looking for ways to clean-up the plugin system so that all of them (except # Dependencies -Go 1.7 +* Go 1.7 * MariaDB @@ -120,6 +120,6 @@ There are several plugins which are bundled with the software by default. These * Hello World / Skeleton - Example plugins for helping you learn how to develop plugins. -* BBCode - A plugin in early development for converting BBCode Tags into HTML. Don't use this in production yet. +* BBCode - A plugin in early development for converting BBCode Tags into HTML. * Markdown - An extremely simple plugin for converting Markdown into HTML. diff --git a/TODO.md b/TODO.md index 791693b0..2a863243 100644 --- a/TODO.md +++ b/TODO.md @@ -37,4 +37,4 @@ Add support for multi-factor authentication. Add support for secondary emails for users. -Improve the shell scripts and possibly add support for Make? +Improve the shell scripts and possibly add support for Make? A make.go might be a good solution? diff --git a/build-gosora-linux b/build-gosora-linux index e99c2676..29b369fd 100644 --- a/build-gosora-linux +++ b/build-gosora-linux @@ -1,2 +1,4 @@ -go build -go build ./install \ No newline at end of file +echo "Building Gosora" +go build -o Gosora +echo "Building the installer" +go build ./install diff --git a/build.bat b/build.bat index 4fe85921..27d28e3f 100644 --- a/build.bat +++ b/build.bat @@ -7,7 +7,7 @@ if %errorlevel% neq 0 ( ) echo Building the executable -go build +go build -o gosora.exe if %errorlevel% neq 0 ( pause exit /b %errorlevel% diff --git a/config.go b/config.go index a9c51960..26cf01d6 100644 --- a/config.go +++ b/config.go @@ -30,6 +30,7 @@ var enable_ssl = false var ssl_privkey = "" var ssl_fullchain = "" -// Developer flag +// Developer flags var debug = false var profiling = false + diff --git a/data.sql b/data.sql index d948784d..1306fcd5 100644 --- a/data.sql +++ b/data.sql @@ -47,6 +47,7 @@ CREATE TABLE `forums`( `fid` int not null AUTO_INCREMENT, `name` varchar(100) not null, `active` tinyint DEFAULT 1 not null, + `topicCount` int DEFAULT 0 not null, `lastTopic` varchar(100) DEFAULT '' not null, `lastTopicID` int DEFAULT 0 not null, `lastReplyer` varchar(100) DEFAULT '' not null, @@ -169,6 +170,7 @@ INSERT INTO users_groups(`name`,`permissions`) VALUES ('Member','{"BanUsers":fal INSERT INTO users_groups(`name`,`permissions`,`is_banned`) VALUES ('Banned','{"BanUsers":false,"ActivateUsers":false,"EditUser":false,"EditUserEmail":false,"EditUserPassword":false,"EditUserGroup":false,"EditUserGroupSuperMod":false,"EditUserGroupAdmin":false,"ManageForums":false,"EditSettings":false,"ManageThemes":false,"ManagePlugins":false,"ViewIPs":false,"ViewTopic":true,"CreateTopic":false,"EditTopic":false,"DeleteTopic":false,"CreateReply":false,"EditReply":false,"DeleteReply":false,"PinTopic":false,"CloseTopic":false}',1); INSERT INTO users_groups(`name`,`permissions`) VALUES ('Awaiting Activation','{"BanUsers":false,"ActivateUsers":false,"EditUser":false,"EditUserEmail":false,"EditUserPassword":false,"EditUserGroup":false,"EditUserGroupSuperMod":false,"EditUserGroupAdmin":false,"ManageForums":false,"EditSettings":false,"ManageThemes":false,"ManagePlugins":false,"ViewIPs":false,"ViewTopic":true,"CreateTopic":false,"EditTopic":false,"DeleteTopic":false,"CreateReply":false,"EditReply":false,"DeleteReply":false,"PinTopic":false,"CloseTopic":false}'); +INSERT INTO forums(`name`,`active`) VALUES ('Reports',0); INSERT INTO forums(`name`,`lastTopicTime`) VALUES ('General',NOW()); INSERT INTO topics(`title`,`content`,`createdAt`,`lastReplyAt`,`createdBy`,`parentID`) VALUES ('Test Topic','A topic automatically generated by the software.',NOW(),NOW(),1,1); diff --git a/forum.go b/forum.go index 45d56670..3b712aa0 100644 --- a/forum.go +++ b/forum.go @@ -1,10 +1,13 @@ package main +import "database/sql" +import _ "github.com/go-sql-driver/mysql" type Forum struct { ID int Name string Active bool + TopicCount int LastTopic string LastTopicID int LastReplyer string @@ -18,3 +21,42 @@ type ForumSimple struct Name string Active bool } + +func create_forum(forum_name string, active bool) (int, error) { + var fid int + err := forum_entry_exists_stmt.QueryRow().Scan(&fid) + if err != nil && err != sql.ErrNoRows { + return 0, err + } + if err != sql.ErrNoRows { + _, err = update_forum_stmt.Exec(forum_name, active, fid) + if err != nil { + return fid, err + } + forums[fid].Name = forum_name + forums[fid].Active = active + return fid, nil + } + + res, err := create_forum_stmt.Exec(forum_name, active) + if err != nil { + return 0, err + } + + fid64, err := res.LastInsertId() + if err != nil { + return 0, err + } + + forums = append(forums, Forum{int(fid64),forum_name,active,0,"",0,"",0,""}) + return fid, nil +} + +func delete_forum(fid int) error { + _, err := delete_forum_stmt.Exec(fid) + if err != nil { + return err + } + forums[fid].Name = "" + return nil +} diff --git a/images/forum_manager.PNG b/images/forum_manager.PNG new file mode 100644 index 0000000000000000000000000000000000000000..3bb160c0fbc92e12076f56ffdfeee62c131abdc9 GIT binary patch literal 38935 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 B0|o#9 literal 0 HcmV?d00001 diff --git a/install-gosora-linux b/install-gosora-linux index 11b4883b..19372ad5 100644 --- a/install-gosora-linux +++ b/install-gosora-linux @@ -1,5 +1,10 @@ +echo "Installing the MySQL Driver" go get -u github.com/go-sql-driver/mysql +echo "Installing bcrypt" go get -u golang.org/x/crypto/bcrypt -go build + +echo "Preparing the installer" +go generate +go build -o Gosora go build ./install ./Install \ No newline at end of file diff --git a/install.bat b/install.bat index ccd38783..be08c151 100644 --- a/install.bat +++ b/install.bat @@ -12,6 +12,11 @@ if %errorlevel% neq 0 ( ) echo Preparing the installer +go generate +if %errorlevel% neq 0 ( + pause + exit /b %errorlevel% +) go build if %errorlevel% neq 0 ( pause diff --git a/install/install.go b/install/install.go index 32bd80e8..4ef0bba2 100644 --- a/install/install.go +++ b/install/install.go @@ -160,7 +160,7 @@ var site_email = "" // Should be a setting var smtp_server = "" //var noavatar = "https://api.adorable.io/avatars/{width}/{id}@{site_url}.png" var noavatar = "https://api.adorable.io/avatars/285/{id}@" + site_url + ".png" -var items_per_page = 40 // Should be a setting +var items_per_page = 25 var site_url = "` + site_url + `" var server_port = "` + server_port + `" @@ -170,6 +170,7 @@ var ssl_fullchain = "" // Developer flag var debug = false +var profiling = false `) fmt.Println("Opening the configuration file") diff --git a/main.go b/main.go index 721487d5..c4e567e4 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,9 @@ var staff_css_tmpl = template.CSS(staff_css) var settings map[string]interface{} = make(map[string]interface{}) var external_sites map[string]string = make(map[string]string) var groups map[int]Group = make(map[int]Group) -var forums map[int]Forum = make(map[int]Forum) +var forums []Forum // The IDs for a forum tend to be low and sequential for the most part, so we can get more performance out of using a slice instead of a map AND it has better concurrency +var groupCapCount int +var forumCapCount int var static_files map[string]SFile = make(map[string]SFile) var template_topic_handle func(TopicPage,io.Writer) = nil @@ -75,7 +77,8 @@ func compile_templates() { topics_page := TopicsPage{"Topic List",user,noticeList,topicList,""} topics_tmpl := c.compile_template("topics.html","templates/","TopicsPage", topics_page, varList) - forum_page := ForumPage{"General Forum",user,noticeList,topicList,"There aren't any topics in this forum yet."} + forum_item := Forum{1,"General Forum",true,0,"",0,"",0,""} + forum_page := ForumPage{"General Forum",user,noticeList,topicList,forum_item,1,1,nil} forum_tmpl := c.compile_template("forum.html","templates/","ForumPage", forum_page, varList) log.Print("Writing the templates") diff --git a/mod_routes.go b/mod_routes.go index 15de7eb6..f156e624 100644 --- a/mod_routes.go +++ b/mod_routes.go @@ -72,7 +72,8 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request) { var content string var createdBy int - err = db.QueryRow("select tid, content, createdBy from topics where tid = ?", tid).Scan(&tid, &content, &createdBy) + var fid int + err = db.QueryRow("select tid, content, createdBy, parentID from topics where tid = ?", tid).Scan(&tid, &content, &createdBy, &fid) if err == sql.ErrNoRows { LocalError("The topic you tried to delete doesn't exist.",w,r,user) return @@ -95,6 +96,18 @@ func route_delete_topic(w http.ResponseWriter, r *http.Request) { InternalError(err,w,r,user) return } + + if (fid > forumCapCount) || (fid < 0) || forums[fid].Name=="" { + LocalError("The topic's parent forum doesn't exist.",w,r,user) + return + } + _, err = remove_topics_from_forum_stmt.Exec(1, fid) + if err != nil { + InternalError(err,w,r,user) + return + } + + forums[fid].TopicCount -= 1 } func route_stick_topic(w http.ResponseWriter, r *http.Request) { @@ -571,12 +584,12 @@ func route_panel_forums(w http.ResponseWriter, r *http.Request){ var forumList []interface{} for _, forum := range forums { - if forum.ID > -1 { + if forum.Name != "" { forumList = append(forumList, forum) } } - pi := Page{"Forum Manager",user,noticeList,forumList,0} + pi := Page{"Forum Manager",user,noticeList,forumList,nil} templates.ExecuteTemplate(w,"panel-forums.html", pi) } @@ -600,20 +613,20 @@ func route_panel_forums_create_submit(w http.ResponseWriter, r *http.Request){ return } + var active bool fname := r.PostFormValue("forum-name") - res, err := create_forum_stmt.Exec(fname) + factive := r.PostFormValue("forum-name") + if factive == "on" || factive == "1" { + active = true + } else { + active = false + } + + _, err = create_forum(fname, active) if err != nil { InternalError(err,w,r,user) return } - - lastId, err := res.LastInsertId() - if err != nil { - InternalError(err,w,r,user) - return - } - - forums[int(lastId)] = Forum{int(lastId),fname,true,"",0,"",0,""} http.Redirect(w,r,"/panel/forums/",http.StatusSeeOther) } @@ -637,8 +650,7 @@ func route_panel_forums_delete(w http.ResponseWriter, r *http.Request){ return } - _, ok = forums[fid]; - if !ok { + if (fid > forumCapCount) || (fid < 0) || forums[fid].Name=="" { LocalError("The forum you're trying to delete doesn't exist.",w,r,user) return } @@ -671,20 +683,17 @@ func route_panel_forums_delete_submit(w http.ResponseWriter, r *http.Request) { return } - _, ok = forums[fid]; - if !ok { + if (fid > forumCapCount) || (fid < 0) || forums[fid].Name=="" { LocalError("The forum you're trying to delete doesn't exist.",w,r,user) return } - _, err = delete_forum_stmt.Exec(fid) + err = delete_forum(fid) if err != nil { InternalError(err,w,r,user) return } - // Remove this forum from the forum cache - delete(forums,fid); http.Redirect(w,r,"/panel/forums/",http.StatusSeeOther) } @@ -715,8 +724,7 @@ func route_panel_forums_edit_submit(w http.ResponseWriter, r *http.Request) { } forum_name := r.PostFormValue("edit_item") - forum, ok := forums[fid]; - if !ok { + if (fid > forumCapCount) || (fid < 0) || forums[fid].Name=="" { LocalError("The forum you're trying to edit doesn't exist.",w,r,user) return } @@ -726,8 +734,7 @@ func route_panel_forums_edit_submit(w http.ResponseWriter, r *http.Request) { InternalError(err,w,r,user) return } - forum.Name = forum_name - forums[fid] = forum + forums[fid].Name = forum_name http.Redirect(w,r,"/panel/forums/",http.StatusSeeOther) } diff --git a/mysql.go b/mysql.go index b19c56ce..dd63dd2c 100644 --- a/mysql.go +++ b/mysql.go @@ -15,11 +15,14 @@ var get_topic_user_stmt *sql.Stmt var get_topic_replies_stmt *sql.Stmt var get_topic_replies_offset_stmt *sql.Stmt var get_forum_topics_stmt *sql.Stmt +var get_forum_topics_offset_stmt *sql.Stmt var create_topic_stmt *sql.Stmt var create_report_stmt *sql.Stmt var create_reply_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 +var remove_topics_from_forum_stmt *sql.Stmt var update_forum_cache_stmt *sql.Stmt var edit_topic_stmt *sql.Stmt var edit_reply_stmt *sql.Stmt @@ -55,6 +58,7 @@ var delete_profile_reply_stmt *sql.Stmt var create_forum_stmt *sql.Stmt var delete_forum_stmt *sql.Stmt var update_forum_stmt *sql.Stmt +var forum_entry_exists_stmt *sql.Stmt var update_setting_stmt *sql.Stmt var add_plugin_stmt *sql.Stmt var update_plugin_stmt *sql.Stmt @@ -102,13 +106,19 @@ func init_database(err error) { } 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 from replies left join users ON replies.createdBy = users.uid where tid = ? limit ? , " + strconv.Itoa(items_per_page)) + 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 from replies left join users on replies.createdBy = users.uid where tid = ? limit ?, " + strconv.Itoa(items_per_page)) if err != nil { log.Fatal(err) } log.Print("Preparing get_forum_topics statement.") - get_forum_topics_stmt, err = db.Prepare("select topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.parentID, users.name, users.avatar from topics left join users ON topics.createdBy = users.uid WHERE topics.parentID = ? order by topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC") + get_forum_topics_stmt, err = db.Prepare("select topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.parentID, users.name, users.avatar from topics left join users ON topics.createdBy = users.uid where topics.parentID = ? order by topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy desc") + if err != nil { + log.Fatal(err) + } + + log.Print("Preparing get_forum_topics_offset statement.") + get_forum_topics_offset_stmt, err = db.Prepare("select topics.tid, topics.title, topics.content, topics.createdBy, topics.is_closed, topics.sticky, topics.createdAt, topics.parentID, users.name, users.avatar from topics left join users ON topics.createdBy = users.uid WHERE topics.parentID = ? order by topics.sticky DESC, topics.lastReplyAt DESC, topics.createdBy DESC limit ?, " + strconv.Itoa(items_per_page)) if err != nil { log.Fatal(err) } @@ -120,7 +130,7 @@ func init_database(err error) { } log.Print("Preparing create_report statement.") - create_report_stmt, err = db.Prepare("INSERT INTO topics(title,content,parsed_content,createdAt,createdBy,data,parentID) VALUES(?,?,?,NOW(),?,?,-1)") + create_report_stmt, err = db.Prepare("INSERT INTO topics(title,content,parsed_content,createdAt,createdBy,data,parentID) VALUES(?,?,?,NOW(),?,?,1)") if err != nil { log.Fatal(err) } @@ -143,6 +153,18 @@ func init_database(err error) { log.Fatal(err) } + log.Print("Preparing add_topics_to_forum statement.") + add_topics_to_forum_stmt, err = db.Prepare("UPDATE forums SET topicCount = topicCount + ? WHERE fid = ?") + if err != nil { + log.Fatal(err) + } + + log.Print("Preparing remove_topics_from_forum statement.") + remove_topics_from_forum_stmt, err = db.Prepare("UPDATE forums SET topicCount = topicCount - ? WHERE fid = ?") + if err != nil { + log.Fatal(err) + } + log.Print("Preparing update_forum_cache statement.") update_forum_cache_stmt, err = db.Prepare("UPDATE forums SET lastTopic = ?, lastTopicID = ?, lastReplyer = ?, lastReplyerID = ?, lastTopicTime = NOW() WHERE fid = ?") if err != nil { @@ -249,7 +271,7 @@ func init_database(err error) { } log.Print("Preparing change_group statement.") - change_group_stmt, err = db.Prepare("UPDATE `users` SET `group` = ? WHERE `uid` = ?") + change_group_stmt, err = db.Prepare("update `users` set `group` = ? where `uid` = ?") if err != nil { log.Fatal(err) } @@ -333,19 +355,26 @@ func init_database(err error) { } log.Print("Preparing create_forum statement.") - create_forum_stmt, err = db.Prepare("INSERT INTO forums(name) VALUES(?)") + create_forum_stmt, err = db.Prepare("INSERT INTO forums(name,active) VALUES(?,?)") if err != nil { log.Fatal(err) } log.Print("Preparing delete_forum statement.") - delete_forum_stmt, err = db.Prepare("DELETE FROM forums WHERE fid = ?") + //delete_forum_stmt, err = db.Prepare("DELETE FROM forums WHERE fid = ?") + delete_forum_stmt, err = db.Prepare("update forums set name= '', active = 0 where fid = ?") if err != nil { log.Fatal(err) } log.Print("Preparing update_forum statement.") - update_forum_stmt, err = db.Prepare("UPDATE forums SET name = ? WHERE fid = ?") + update_forum_stmt, err = db.Prepare("update forums set name = ?, active = ? where fid = ?") + if err != nil { + log.Fatal(err) + } + + log.Print("Preparing forum_entry_exists statement.") + forum_entry_exists_stmt, err = db.Prepare("SELECT `fid` FROM `forums` WHERE `name` = '' order by fid asc limit 1") if err != nil { log.Fatal(err) } @@ -418,19 +447,30 @@ func init_database(err error) { } log.Print("Loading the forums.") - rows, err = db.Query("SELECT fid, name, active, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime FROM forums") + log.Print("Adding the uncategorised forum") + forums = append(forums, Forum{0,"Uncategorised",uncategorised_forum_visible,0,"",0,"",0,""}) + + //rows, err = db.Query("SELECT fid, name, active, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime FROM forums") + rows, err = db.Query("SELECT fid, name, active, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime FROM forums ORDER BY fid ASC") if err != nil { log.Fatal(err) } defer rows.Close() - for rows.Next() { - forum := Forum{0,"",true,"",0,"",0,""} - err := rows.Scan(&forum.ID, &forum.Name, &forum.Active, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime) + i := 1 + for ;rows.Next();i++ { + forum := Forum{0,"",true,0,"",0,"",0,""} + err := rows.Scan(&forum.ID, &forum.Name, &forum.Active, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime) if err != nil { log.Fatal(err) } + // Ugh, you really shouldn't physically delete these items, it makes a big mess of things + if forum.ID != i { + fmt.Println("Stop physically deleting forums. You are messing up the IDs. Use the Forum Manager or delete_forums() instead x.x") + fill_forum_id_gap(i, forum.ID) + } + if forum.LastTopicID != 0 { forum.LastTopicTime, err = relative_time(forum.LastTopicTime) if err != nil { @@ -440,17 +480,18 @@ func init_database(err error) { forum.LastTopic = "None" forum.LastTopicTime = "" } - forums[forum.ID] = forum + + log.Print("Adding the " + forum.Name + " forum") + forums = append(forums,forum) } err = rows.Err() if err != nil { log.Fatal(err) } + forumCapCount = i - log.Print("Adding the uncategorised forum") - forums[0] = Forum{0,"Uncategorised",uncategorised_forum_visible,"",0,"",0,""} - log.Print("Adding the reports forum") - forums[-1] = Forum{-1,"Reports",false,"",0,"",0,""} + //log.Print("Adding the reports forum") + //forums[-1] = Forum{-1,"Reports",false,0,"",0,"",0,""} log.Print("Loading the settings.") rows, err = db.Query("SELECT name, content, type, constraints FROM settings") @@ -541,6 +582,4 @@ func init_database(err error) { if err != nil { log.Fatal(err) } - - } diff --git a/pages.go b/pages.go index 1b452ba3..4ced1fec 100644 --- a/pages.go +++ b/pages.go @@ -38,6 +38,9 @@ type ForumPage struct CurrentUser User NoticeList []string ItemList []TopicUser + Forum Forum + Page int + LastPage int ExtData interface{} } diff --git a/plugin_bbcode.go b/plugin_bbcode.go index 0b555200..4ab800a3 100644 --- a/plugin_bbcode.go +++ b/plugin_bbcode.go @@ -1,6 +1,6 @@ package main //import "log" -import "fmt" +//import "fmt" import "bytes" //import "strings" import "strconv" @@ -15,6 +15,8 @@ var bbcode_missing_tag []byte var bbcode_url_open []byte var bbcode_url_open2 []byte var bbcode_url_close []byte +var bbcode_space_gap []byte + var bbcode_bold *regexp.Regexp var bbcode_italic *regexp.Regexp var bbcode_underline *regexp.Regexp @@ -36,6 +38,7 @@ func init_bbcode() { bbcode_url_open = []byte("") bbcode_url_close = []byte("") + bbcode_space_gap = []byte(" ") bbcode_bold = regexp.MustCompile(`(?s)\[b\](.*)\[/b\]`) bbcode_italic = regexp.MustCompile(`(?s)\[i\](.*)\[/i\]`) @@ -188,8 +191,8 @@ func bbcode_full_parse(data interface{}) interface{} { has_s := false has_c := false complex_bbc := false - msglen := len(msgbytes) - for i := 0; (i + 3) < msglen; i++ { + msgbytes = append(msgbytes,bbcode_space_gap...) + for i := 0; i < len(msgbytes); i++ { if msgbytes[i] == '[' { if msgbytes[i + 2] != ']' { if msgbytes[i + 1] == '/' { @@ -215,7 +218,7 @@ func bbcode_full_parse(data interface{}) interface{} { i += 3 } } else { - if msglen >= (i+6) && msgbytes[i+2] == 'c' && msgbytes[i+3] == 'o' && msgbytes[i+4] == 'd' && msgbytes[i+5] == 'e' && msgbytes[i+6] == ']' { + if msgbytes[i+2] == 'c' && msgbytes[i+3] == 'o' && msgbytes[i+4] == 'd' && msgbytes[i+5] == 'e' && msgbytes[i+6] == ']' { has_c = false i += 7 } @@ -228,7 +231,7 @@ func bbcode_full_parse(data interface{}) interface{} { complex_bbc = true } } else { - if msglen >= (i+5) && msgbytes[i+1] == 'c' && msgbytes[i+2] == 'o' && msgbytes[i+3] == 'd' && msgbytes[i+4] == 'e' && msgbytes[i+5] == ']' { + if msgbytes[i+1] == 'c' && msgbytes[i+2] == 'o' && msgbytes[i+3] == 'd' && msgbytes[i+4] == 'e' && msgbytes[i+5] == ']' { has_c = true i += 6 } @@ -271,14 +274,14 @@ func bbcode_full_parse(data interface{}) interface{} { i := 0 var start int var lastTag int - outbytes := make([]byte, msglen) - fmt.Println(string(msgbytes)) - for ; (i+3) < msglen; i++ { + outbytes := make([]byte, len(msgbytes)) + //fmt.Println(string(msgbytes)) + for ; i < len(msgbytes); i++ { MainLoop: if msgbytes[i] == '[' { OuterComplex: if msgbytes[i + 1] == 'u' { - if (msglen-1) >= (i+6) && msgbytes[i+2] == 'r' && msgbytes[i+3] == 'l' && msgbytes[i+4] == ']' { + if msgbytes[i+2] == 'r' && msgbytes[i+3] == 'l' && msgbytes[i+4] == ']' { outbytes = append(outbytes, msgbytes[lastTag:i]...) start = i + 5 i = start @@ -300,7 +303,7 @@ func bbcode_full_parse(data interface{}) interface{} { } for ;; i++ { - if msglen < (i + 6) { + if len(msgbytes) < (i + 10) { //fmt.Println(msglen) //fmt.Println(i+6) outbytes = append(outbytes, bbcode_missing_tag...) @@ -329,7 +332,7 @@ func bbcode_full_parse(data interface{}) interface{} { lastTag = i } } else if msgbytes[i + 1] == 'r' { - if msglen >= (i+6) && bytes.Equal(msgbytes[i+2:i+6],[]byte("and]")) { + if bytes.Equal(msgbytes[i+2:i+6],[]byte("and]")) { outbytes = append(outbytes, msgbytes[lastTag:i]...) start = i + 6 i = start @@ -340,7 +343,7 @@ func bbcode_full_parse(data interface{}) interface{} { goto OuterComplex } break - } else if (len(msgbytes) - 1) < (i + 7) { + } else if (len(msgbytes) - 1) < (i + 10) { outbytes = append(outbytes, bbcode_missing_tag...) goto OuterComplex } @@ -364,7 +367,7 @@ func bbcode_full_parse(data interface{}) interface{} { //fmt.Println(outbytes) //fmt.Println(string(outbytes)) if lastTag != i { - outbytes = append(outbytes, msgbytes[lastTag:]...) + outbytes = append(outbytes, msgbytes[lastTag:len(msgbytes) - 10]...) } if len(outbytes) != 0 { return string(outbytes) diff --git a/public/global.js b/public/global.js index 923087c9..49e904d5 100644 --- a/public/global.js +++ b/public/global.js @@ -121,4 +121,13 @@ $(document).ready(function(){ }); } }); + + $(this).keyup(function(event){ + if(event.which == 37) { + $("#prevFloat a")[0].click(); + } + if(event.which == 39) { + $("#nextFloat a")[0].click(); + } + }); }); \ No newline at end of file diff --git a/routes.go b/routes.go index 9aa57d43..56782e2d 100644 --- a/routes.go +++ b/routes.go @@ -2,7 +2,7 @@ package main import "log" -import "fmt" +//import "fmt" import "strconv" import "bytes" import "regexp" @@ -150,15 +150,14 @@ func route_forum(w http.ResponseWriter, r *http.Request){ return } - var topicList []TopicUser + page, _ := strconv.Atoi(r.FormValue("page")) fid, err := strconv.Atoi(r.URL.Path[len("/forum/"):]) if err != nil { LocalError("The provided ForumID is not a valid number.",w,r,user) return } - _, ok = forums[fid] - if !ok { + if (fid > forumCapCount) || (fid < 0) || forums[fid].Name=="" { NotFound(w,r,user) return } @@ -167,12 +166,24 @@ func route_forum(w http.ResponseWriter, r *http.Request){ return } - rows, err := get_forum_topics_stmt.Query(fid) + // Calculate the offset + var offset int + last_page := int(forums[fid].TopicCount / items_per_page) + 1 + if page > 1 { + offset = (items_per_page * page) - items_per_page + } else if page == -1 { + page = last_page + offset = (items_per_page * page) - items_per_page + } else { + page = 1 + } + rows, err := get_forum_topics_offset_stmt.Query(fid, offset) if err != nil { InternalError(err,w,r,user) return } + var topicList []TopicUser topicItem := TopicUser{ID: 0} for rows.Next() { err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.Is_Closed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.ParentID, &topicItem.CreatedByName, &topicItem.Avatar) @@ -201,7 +212,7 @@ func route_forum(w http.ResponseWriter, r *http.Request){ } rows.Close() - pi := ForumPage{forums[fid].Name,user,noticeList,topicList,nil} + pi := ForumPage{forums[fid].Name,user,noticeList,topicList,forums[fid],page,last_page,nil} if template_forum_handle != nil { template_forum_handle(pi,w) } else { @@ -541,6 +552,8 @@ func route_create_topic(w http.ResponseWriter, r *http.Request) { LocalError("Bad Form", w, r, user) return } + + fid := 2 topic_name := html.EscapeString(r.PostFormValue("topic-name")) content := html.EscapeString(preparse_message(r.PostFormValue("topic-content"))) ipaddress, _, err := net.SplitHostPort(r.RemoteAddr) @@ -549,6 +562,11 @@ func route_create_topic(w http.ResponseWriter, r *http.Request) { return } + if (fid > forumCapCount) || (fid < 0) || forums[fid].Name=="" { + LocalError("The topic's parent forum doesn't exist.",w,r,user) + return + } + res, err := create_topic_stmt.Exec(topic_name,content,parse_message(content),ipaddress,user.ID) if err != nil { InternalError(err,w,r,user) @@ -560,7 +578,15 @@ func route_create_topic(w http.ResponseWriter, r *http.Request) { InternalError(err,w,r,user) return } - _, err = update_forum_cache_stmt.Exec(topic_name, lastId, user.Name, user.ID, 1) + + _, err = add_topics_to_forum_stmt.Exec(1, fid) + if err != nil { + InternalError(err,w,r,user) + return + } + forums[fid].TopicCount -= 1 + + _, err = update_forum_cache_stmt.Exec(topic_name, lastId, user.Name, user.ID, fid) if err != nil { InternalError(err,w,r,user) return @@ -710,8 +736,8 @@ func route_report_submit(w http.ResponseWriter, r *http.Request) { } item_type := r.FormValue("type") - success := 1 + fid := 1 var tid int var title string var content string @@ -719,16 +745,16 @@ func route_report_submit(w http.ResponseWriter, r *http.Request) { if item_type == "reply" { err = db.QueryRow("select tid, content from replies where rid = ?", item_id).Scan(&tid, &content) if err == sql.ErrNoRows { - LocalError("We were unable to find the reported post", w, r, user) + LocalError("We were unable to find the reported post",w,r,user) return } else if err != nil { InternalError(err,w,r,user) return } - err = db.QueryRow("select title, data from topics where tid = ?", tid).Scan(&title,&data) + err = db.QueryRow("select title, data from topics where tid = ?",tid).Scan(&title,&data) if err == sql.ErrNoRows { - LocalError("We were unable to find the topic which the reported post is supposed to be in", w, r, user) + LocalError("We were unable to find the topic which the reported post is supposed to be in",w,r,user) return } else if err != nil { InternalError(err,w,r,user) @@ -738,7 +764,7 @@ func route_report_submit(w http.ResponseWriter, r *http.Request) { } else if item_type == "user-reply" { err = db.QueryRow("select uid, content from users_replies where rid = ?", item_id).Scan(&tid, &content) if err == sql.ErrNoRows { - LocalError("We were unable to find the reported post", w, r, user) + LocalError("We were unable to find the reported post",w,r,user) return } else if err != nil { InternalError(err,w,r,user) @@ -747,7 +773,7 @@ func route_report_submit(w http.ResponseWriter, r *http.Request) { err = db.QueryRow("select name from users where uid = ?", tid).Scan(&title) if err == sql.ErrNoRows { - LocalError("We were unable to find the profile which the reported post is supposed to be on", w, r, user) + LocalError("We were unable to find the profile which the reported post is supposed to be on",w,r,user) return } else if err != nil { InternalError(err,w,r,user) @@ -769,14 +795,13 @@ func route_report_submit(w http.ResponseWriter, r *http.Request) { run_vhook_noreturn("report_preassign", &item_id, &item_type) return } - // Don't try to guess the type - LocalError("Unknown type", w, r, user) + LocalError("Unknown type",w,r,user) return } var count int - rows, err := db.Query("select count(*) as count from topics where data = ? and data != '' and parentID = -1", item_type + "_" + strconv.Itoa(item_id)) + rows, err := db.Query("select count(*) as count from topics where data = ? and data != '' and parentID = 1", item_type + "_" + strconv.Itoa(item_id)) if err != nil && err != sql.ErrNoRows { InternalError(err,w,r,user) return @@ -797,34 +822,28 @@ func route_report_submit(w http.ResponseWriter, r *http.Request) { title = "Report: " + title res, err := create_report_stmt.Exec(title,content,content,user.ID,item_type + "_" + strconv.Itoa(item_id)) if err != nil { - log.Print(err) - success = 0 + InternalError(err,w,r,user) + return } lastId, err := res.LastInsertId() - if err != nil { - log.Print(err) - success = 0 - } - - _, err = update_forum_cache_stmt.Exec(title, lastId, user.Name, user.ID, 1) if err != nil { InternalError(err,w,r,user) return } - if success != 1 { - errmsg := "Unable to create the report" - pi := Page{"Error",user,nList,tList,errmsg} - - var b bytes.Buffer - templates.ExecuteTemplate(&b,"error.html", pi) - errpage := b.String() - w.WriteHeader(500) - fmt.Fprintln(w,errpage) - } else { - http.Redirect(w, r, "/topic/" + strconv.FormatInt(lastId, 10), http.StatusSeeOther) + _, err = add_topics_to_forum_stmt.Exec(1, fid) + if err != nil { + InternalError(err,w,r,user) + return } + _, err = update_forum_cache_stmt.Exec(title, lastId, user.Name, user.ID, fid) + if err != nil { + InternalError(err,w,r,user) + return + } + + http.Redirect(w, r, "/topic/" + strconv.FormatInt(lastId, 10), http.StatusSeeOther) } func route_account_own_edit_critical(w http.ResponseWriter, r *http.Request) { diff --git a/run-gosora-linux b/run-gosora-linux index 89bde232..aa81c8ba 100644 --- a/run-gosora-linux +++ b/run-gosora-linux @@ -1,3 +1,6 @@ -go build -go build ./install +echo "Generating the dynamic code" +go generate +echo "Building Gosora" +go build -o Gosora +echo "Running Gosora" ./Gosora \ No newline at end of file diff --git a/run.bat b/run.bat index 90be3557..17fb6881 100644 --- a/run.bat +++ b/run.bat @@ -7,7 +7,7 @@ if %errorlevel% neq 0 ( ) echo Building the executable -go build +go build -o gosora.exe if %errorlevel% neq 0 ( pause exit /b %errorlevel% diff --git a/template_forum.go b/template_forum.go index 089a934f..be038155 100644 --- a/template_forum.go +++ b/template_forum.go @@ -40,37 +40,55 @@ w.Write([]byte(item)) w.Write(header_5) } } +if tmpl_forum_vars.Page > 1 { w.Write(forum_0) -w.Write([]byte(tmpl_forum_vars.Title)) +w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) w.Write(forum_1) +w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page - 1))) +w.Write(forum_2) +} +if tmpl_forum_vars.LastPage != tmpl_forum_vars.Page { +w.Write(forum_3) +w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) +w.Write(forum_4) +w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1))) +w.Write(forum_5) +w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID))) +w.Write(forum_6) +w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Page + 1))) +w.Write(forum_7) +} +w.Write(forum_8) +w.Write([]byte(tmpl_forum_vars.Title)) +w.Write(forum_9) if len(tmpl_forum_vars.ItemList) != 0 { for _, item := range tmpl_forum_vars.ItemList { -w.Write(forum_2) -if item.Avatar != "" { -w.Write(forum_3) -w.Write([]byte(item.Avatar)) -w.Write(forum_4) -} -if item.Sticky { -w.Write(forum_5) -} else { -if item.Is_Closed { -w.Write(forum_6) -} -} -w.Write(forum_7) -w.Write([]byte(strconv.Itoa(item.ID))) -w.Write(forum_8) -w.Write([]byte(item.Title)) -w.Write(forum_9) -if item.Is_Closed { w.Write(forum_10) -} +if item.Avatar != "" { w.Write(forum_11) -} -} else { +w.Write([]byte(item.Avatar)) w.Write(forum_12) } +if item.Sticky { w.Write(forum_13) +} else { +if item.Is_Closed { +w.Write(forum_14) +} +} +w.Write(forum_15) +w.Write([]byte(strconv.Itoa(item.ID))) +w.Write(forum_16) +w.Write([]byte(item.Title)) +w.Write(forum_17) +if item.Is_Closed { +w.Write(forum_18) +} +w.Write(forum_19) +} +} else { +w.Write(forum_20) +} +w.Write(forum_21) w.Write(footer_0) } diff --git a/template_list.go b/template_list.go index c94ee179..1855f388 100644 --- a/template_list.go +++ b/template_list.go @@ -50,106 +50,109 @@ var header_3 []byte = []byte(`
`) var header_4 []byte = []byte(`
`) var header_5 []byte = []byte(`
`) -var topic_0 []byte = []byte(``) -var topic_3 []byte = []byte(``) -var topic_6 []byte = []byte(` +var topic_5 []byte = []byte(`" /> +`) +var topic_8 []byte = []byte(`
+var topic_9 []byte = []byte(`' method="post">
+var topic_10 []byte = []byte(` style="background-color: #FFFFEA;"`) +var topic_11 []byte = []byte(` style="background-color: #eaeaea;"`) +var topic_12 []byte = []byte(`> `) -var topic_11 []byte = []byte(` +var topic_13 []byte = []byte(` `) -var topic_12 []byte = []byte(`🔒︎`) -var topic_13 []byte = []byte(` +var topic_14 []byte = []byte(`🔒︎`) +var topic_15 []byte = []byte(` Edit +var topic_16 []byte = []byte(`' class="username hide_on_edit open_edit" style="font-weight: normal;margin-left: 6px;">Edit Delete +var topic_17 []byte = []byte(`' class="username" style="font-weight: normal;">Delete `) -var topic_16 []byte = []byte(`Unpin`) -var topic_18 []byte = []byte(`Pin`) -var topic_20 []byte = []byte(` +var topic_18 []byte = []byte(`Unpin`) +var topic_20 []byte = []byte(`Pin`) +var topic_22 []byte = []byte(` +var topic_23 []byte = []byte(`' type="text" /> `) -var topic_22 []byte = []byte(` +var topic_24 []byte = []byte(` Report +var topic_25 []byte = []byte(`?session=`) +var topic_26 []byte = []byte(`&type=topic" class="username report_item" style="font-weight: normal;">Report
+var topic_27 []byte = []byte(`background-image: url(`) +var topic_28 []byte = []byte(`), url(/static/white-dot.jpg);background-position: 0px `) +var topic_29 []byte = []byte(`-1`) +var topic_30 []byte = []byte(`0px;background-repeat: no-repeat, repeat-y;background-size: 128px;padding-left: 136px;`) +var topic_31 []byte = []byte(`">

`) -var topic_30 []byte = []byte(`

+var topic_32 []byte = []byte(`



+var topic_33 []byte = []byte(`

`) -var topic_33 []byte = []byte(` +var topic_34 []byte = []byte(`" class="username real_username">`) +var topic_35 []byte = []byte(` `) -var topic_35 []byte = []byte(`style="color: #505050;float: right;">Level `) -var topic_36 []byte = []byte(` +var topic_36 []byte = []byte(`style="float: right;">`) +var topic_37 []byte = []byte(`style="color: #505050;float: right;">Level `) +var topic_38 []byte = []byte(`

`) -var topic_37 []byte = []byte(` +var topic_39 []byte = []byte(`
+var topic_40 []byte = []byte(`background-image: url(`) +var topic_41 []byte = []byte(`), url(/static/white-dot.jpg);background-position: 0px `) +var topic_42 []byte = []byte(`-1`) +var topic_43 []byte = []byte(`0px;background-repeat: no-repeat, repeat-y;background-size: 128px;padding-left: 136px;`) +var topic_44 []byte = []byte(`">

`) -var topic_43 []byte = []byte(`



+var topic_45 []byte = []byte(`



`) -var topic_45 []byte = []byte(` +var topic_46 []byte = []byte(`" class="username real_username">`) +var topic_47 []byte = []byte(` `) -var topic_46 []byte = []byte(` `) -var topic_48 []byte = []byte(` `) -var topic_50 []byte = []byte(` +var topic_48 []byte = []byte(` `) +var topic_50 []byte = []byte(` `) +var topic_52 []byte = []byte(` +var topic_53 []byte = []byte(`?session=`) +var topic_54 []byte = []byte(`&type=reply" class="mod_button"> `) -var topic_54 []byte = []byte(`style="color: #505050;float: right;">Level `) -var topic_55 []byte = []byte(` +var topic_55 []byte = []byte(`style="float: right;">`) +var topic_56 []byte = []byte(`style="color: #505050;float: right;">Level `) +var topic_57 []byte = []byte(`
`) -var topic_56 []byte = []byte(`
+var topic_58 []byte = []byte(`
`) -var topic_57 []byte = []byte(` +var topic_59 []byte = []byte(`
+var topic_60 []byte = []byte(`' type="hidden" />
@@ -163,48 +166,51 @@ var footer_0 []byte = []byte(`