Added the plugin system.
Added the report system. Rebranded the project. Improved the installation instructions. Changed the open / close status CSS. Added the Hello World plugin. Added the Skeleton plugin.
This commit is contained in:
parent
b2e3591997
commit
ec2c02d7c9
38
README.md
38
README.md
|
@ -1,8 +1,10 @@
|
|||
# Grosolo
|
||||
# Gosora
|
||||
|
||||
A super fast forum software written in Go.
|
||||
|
||||
The initial code-base was forked from one of my side projects, and converted from the web framework it was using.
|
||||
The initial code-base was forked from one of my side projects, but has now gone far beyond that.
|
||||
|
||||
Discord Server: https://discord.gg/eyYvtTf
|
||||
|
||||
|
||||
# Features
|
||||
|
@ -16,6 +18,8 @@ In-memory static file, forum and group caches.
|
|||
|
||||
A profile system including profile comments and moderation tools for the profile owner.
|
||||
|
||||
A plugin system.
|
||||
|
||||
|
||||
# Dependencies
|
||||
|
||||
|
@ -46,10 +50,32 @@ Add -u after go get to update those libraries, if you've already got them instal
|
|||
|
||||
# Run the program
|
||||
|
||||
go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go forum.go group.go files.go config.go
|
||||
*Linux*
|
||||
|
||||
cd to the directory / folder the code is in.
|
||||
|
||||
go build
|
||||
|
||||
./gosora
|
||||
|
||||
|
||||
*Windows*
|
||||
|
||||
Open up cmd.exe
|
||||
|
||||
cd to the directory / folder the code is in. E.g. cd /Users/Blah/Documents/gosora
|
||||
|
||||
go build
|
||||
|
||||
./gosora.exe
|
||||
|
||||
|
||||
Alternatively, you could run the run.bat batch file on Windows.
|
||||
|
||||
We're also looking into ways to distribute ready made executables for Windows. While this is not a complicated endeavour, the configuration settings currently get built with the rest of the program for speed, and we will likely have to change this.
|
||||
|
||||
With the introduction of the new settings system, we will begin moving some of the less critical settings out of the configuration file, and will likely have a config.xml or config.ini in the future to store the critical settings in.
|
||||
|
||||
|
||||
# TO-DO
|
||||
|
||||
|
@ -64,12 +90,8 @@ Add emails as a requirement for registration and add a simple anti-spam measure.
|
|||
|
||||
Add an alert system.
|
||||
|
||||
Add a report feature.
|
||||
|
||||
Add a complex permissions system.
|
||||
|
||||
Add a settings system.
|
||||
|
||||
Add a plugin system.
|
||||
|
||||
Tweak the CSS to make it responsive.
|
||||
|
@ -77,3 +99,5 @@ Tweak the CSS to make it responsive.
|
|||
Nest the moderation routes to possibly speed routing up a little...?
|
||||
|
||||
Add a friend system.
|
||||
|
||||
Add more administration features.
|
||||
|
|
|
@ -4,7 +4,7 @@ package main
|
|||
var dbhost = "127.0.0.1"
|
||||
var dbuser = "root"
|
||||
var dbpassword = "password"
|
||||
var dbname = "grosolo"
|
||||
var dbname = "gosora"
|
||||
var dbport = "3306" // You probably won't need to change this
|
||||
|
||||
// Limiters
|
||||
|
|
7
data.sql
7
data.sql
|
@ -53,6 +53,7 @@ CREATE TABLE `topics`(
|
|||
`is_closed` tinyint DEFAULT 0 not null,
|
||||
`sticky` tinyint DEFAULT 0 not null,
|
||||
`parentID` int DEFAULT 1 not null,
|
||||
`data` varchar(200) DEFAULT '' not null,
|
||||
primary key(`tid`)
|
||||
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
|
||||
|
@ -87,6 +88,12 @@ CREATE TABLE `settings`(
|
|||
unique(`name`)
|
||||
);
|
||||
|
||||
CREATE TABLE `plugins`(
|
||||
`uname` varchar(200) not null,
|
||||
`active` tinyint DEFAULT 0 not null,
|
||||
unique(`uname`)
|
||||
);
|
||||
|
||||
INSERT INTO settings(`name`,`content`,`type`) VALUES ('url_tags','1','bool');
|
||||
|
||||
INSERT INTO users(`name`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`,`message`)
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/* Copyright Azareal 2016 - 2017 */
|
||||
package main
|
||||
|
||||
var plugins map[string]Plugin = make(map[string]Plugin)
|
||||
var hooks map[string]func(interface{})interface{} = make(map[string]func(interface{})interface{})
|
||||
|
||||
type Plugin struct
|
||||
{
|
||||
UName string
|
||||
Name string
|
||||
Author string
|
||||
URL string
|
||||
Settings string
|
||||
Active bool
|
||||
Type string
|
||||
Init func()
|
||||
}
|
||||
|
||||
func add_hook(name string, handler func(interface{})interface{}) {
|
||||
hooks[name] = handler
|
||||
}
|
||||
|
||||
func run_hook(name string, data interface{}) interface{} {
|
||||
return hooks[name](data)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
This file is here so that Git will include this folder in the repository.
|
|
@ -1,2 +1,2 @@
|
|||
go build
|
||||
./Grosolo
|
||||
./Gosora
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 88 KiB |
17
main.go
17
main.go
|
@ -1,8 +1,10 @@
|
|||
/* Copyright Azareal 2016 - 2017 */
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"log"
|
||||
//"fmt"
|
||||
"mime"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
|
@ -56,6 +58,17 @@ func main(){
|
|||
}
|
||||
|
||||
external_sites["YT"] = "https://www.youtube.com/"
|
||||
hooks["trow_assign"] = nil
|
||||
hooks["rrow_assign"] = nil
|
||||
//fmt.Println(plugins)
|
||||
|
||||
for name, body := range plugins {
|
||||
log.Print("Added plugin " + name)
|
||||
if body.Active {
|
||||
log.Print("Initialised plugin " + name)
|
||||
plugins[name].Init()
|
||||
}
|
||||
}
|
||||
|
||||
// In a directory to stop it clashing with the other paths
|
||||
http.HandleFunc("/static/", route_static)
|
||||
|
@ -77,6 +90,7 @@ func main(){
|
|||
//http.HandleFunc("/reply/delete/", route_reply_delete)
|
||||
http.HandleFunc("/reply/edit/submit/", route_reply_edit_submit)
|
||||
http.HandleFunc("/reply/delete/submit/", route_reply_delete_submit)
|
||||
http.HandleFunc("/report/submit/", route_report_submit)
|
||||
http.HandleFunc("/topic/edit/submit/", route_edit_topic)
|
||||
http.HandleFunc("/topic/delete/submit/", route_delete_topic)
|
||||
http.HandleFunc("/topic/stick/submit/", route_stick_topic)
|
||||
|
@ -119,6 +133,9 @@ func main(){
|
|||
http.HandleFunc("/panel/settings/", route_panel_settings)
|
||||
http.HandleFunc("/panel/settings/edit/", route_panel_setting)
|
||||
http.HandleFunc("/panel/settings/edit/submit/", route_panel_setting_edit)
|
||||
http.HandleFunc("/panel/plugins/", route_panel_plugins)
|
||||
http.HandleFunc("/panel/plugins/activate/", route_panel_plugins_activate)
|
||||
//http.HandleFunc("/panel/plugins/deactivate/", route_panel_plugins_deactivate)
|
||||
|
||||
http.HandleFunc("/", default_route)
|
||||
|
||||
|
|
|
@ -694,4 +694,66 @@ func route_panel_setting_edit(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
http.Redirect(w,r,"/panel/settings/",http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func route_panel_plugins(w http.ResponseWriter, r *http.Request){
|
||||
user := SessionCheck(w,r)
|
||||
if !user.Is_Admin {
|
||||
NoPermissions(w,r,user)
|
||||
return
|
||||
}
|
||||
|
||||
var pluginList map[int]interface{} = make(map[int]interface{})
|
||||
currentID := 0
|
||||
|
||||
for _, plugin := range plugins {
|
||||
pluginList[currentID] = plugin
|
||||
currentID++
|
||||
}
|
||||
|
||||
pi := Page{"Plugin Manager","panel-plugins",user,pluginList,0}
|
||||
templates.ExecuteTemplate(w,"panel-plugins.html", pi)
|
||||
}
|
||||
|
||||
func route_panel_plugins_activate(w http.ResponseWriter, r *http.Request){
|
||||
user := SessionCheck(w,r)
|
||||
if !user.Is_Admin {
|
||||
NoPermissions(w,r,user)
|
||||
return
|
||||
}
|
||||
|
||||
uname := r.URL.Path[len("/panel/plugins/activate/"):]
|
||||
|
||||
plugin, ok := plugins[uname]
|
||||
if !ok {
|
||||
LocalError("The plugin isn't registered in the system",w,r,user)
|
||||
return
|
||||
}
|
||||
|
||||
var active bool
|
||||
err := db.QueryRow("SELECT active from plugins where uname = ?", uname).Scan(&active)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
InternalError(err,w,r,user)
|
||||
return
|
||||
}
|
||||
|
||||
has_plugin := err != sql.ErrNoRows
|
||||
if has_plugin {
|
||||
if active {
|
||||
LocalError("The plugin is already active",w,r,user)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
_, err := add_plugin_stmt.Exec(uname,1)
|
||||
if err != nil {
|
||||
InternalError(err,w,r,user)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
plugin.Active = true
|
||||
plugins[uname] = plugin
|
||||
plugins[uname].Init()
|
||||
|
||||
http.Redirect(w,r,"/panel/plugins/",http.StatusSeeOther)
|
||||
}
|
38
mysql.go
38
mysql.go
|
@ -1,3 +1,4 @@
|
|||
/* Copyright Azareal 2016 - 2017 */
|
||||
package main
|
||||
|
||||
import "database/sql"
|
||||
|
@ -8,6 +9,7 @@ import "log"
|
|||
var db *sql.DB
|
||||
var get_session_stmt *sql.Stmt
|
||||
var create_topic_stmt *sql.Stmt
|
||||
var create_report_stmt *sql.Stmt
|
||||
var create_reply_stmt *sql.Stmt
|
||||
var update_forum_cache_stmt *sql.Stmt
|
||||
var edit_topic_stmt *sql.Stmt
|
||||
|
@ -34,6 +36,7 @@ var create_forum_stmt *sql.Stmt
|
|||
var delete_forum_stmt *sql.Stmt
|
||||
var update_forum_stmt *sql.Stmt
|
||||
var update_setting_stmt *sql.Stmt
|
||||
var add_plugin_stmt *sql.Stmt
|
||||
|
||||
func init_database(err error) {
|
||||
if(dbpassword != ""){
|
||||
|
@ -62,6 +65,12 @@ func init_database(err error) {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
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)")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Print("Preparing create_reply statement.")
|
||||
create_reply_stmt, err = db.Prepare("INSERT INTO replies(tid,content,parsed_content,createdAt,createdBy) VALUES(?,?,?,NOW(),?)")
|
||||
if err != nil {
|
||||
|
@ -215,6 +224,12 @@ func init_database(err error) {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Print("Preparing add_plugin statement.")
|
||||
add_plugin_stmt, err = db.Prepare("INSERT INTO plugins(uname,active) VALUES(?,?)")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Print("Loading the usergroups.")
|
||||
rows, err := db.Query("SELECT gid,name,permissions,is_mod,is_admin,is_banned,tag FROM users_groups")
|
||||
if err != nil {
|
||||
|
@ -295,4 +310,27 @@ func init_database(err error) {
|
|||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Print("Loading the plugins.")
|
||||
rows, err = db.Query("SELECT uname, active FROM plugins")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var uname string
|
||||
var active bool
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&uname, &active)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
plugin := plugins[uname]
|
||||
plugin.Active = active
|
||||
plugins[uname] = plugin
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
import "html/template"
|
||||
|
||||
func init() {
|
||||
plugins["helloworld"] = Plugin{"helloworld","Hello World","Azareal","http://github.com/Azareal","",false,"",init_helloworld}
|
||||
}
|
||||
|
||||
// init_helloworld is separate from init() as we don't want the plugin to run if the plugin is disabled
|
||||
func init_helloworld() {
|
||||
add_hook("rrow_assign", helloworld_reply)
|
||||
}
|
||||
|
||||
func helloworld_reply(data interface{}) interface{} {
|
||||
reply := data.(Reply)
|
||||
reply.Content = "Hello World!"
|
||||
reply.ContentHtml = template.HTML("Hello World!")
|
||||
reply.Tag = "Automated"
|
||||
return reply
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package main
|
||||
|
||||
func init() {
|
||||
/*
|
||||
The UName field should match the name in the URL minus plugin_ and the file extension. The same name as the map index. Please choose a unique name which won't clash with any other plugins.
|
||||
The Name field is for the friendly name of the plugin shown to the end-user.
|
||||
The Author field is the author of this plugin. The one who created it.
|
||||
The URL field is for the URL pointing to the location where you can download this plugin.
|
||||
The Settings field points to the route for managing the settings for this plugin. Coming soon.
|
||||
The Active field should always be set to false in the init() function of a plugin. It's used internally by the software to determine whether an admin has enabled a plugin or not and whether to run it. This will be overwritten by the user's preference.
|
||||
The Type field is for the type of the plugin. This gets changed to "go" automatically and we would suggest leaving "".
|
||||
The Init field is for the initialisation handler which is called by the software to run this plugin. This expects a function. You should add your hooks, init logic, initial queries, etc. in said function.
|
||||
*/
|
||||
plugins["skeleton"] = Plugin{"skeleton","Skeleton","Azareal","","",false,"",init_test}
|
||||
}
|
||||
|
||||
func init_test() {}
|
1
reply.go
1
reply.go
|
@ -1,3 +1,4 @@
|
|||
/* Copyright Azareal 2016 - 2017 */
|
||||
package main
|
||||
import "html/template"
|
||||
|
||||
|
|
135
routes.go
135
routes.go
|
@ -1,3 +1,4 @@
|
|||
/* Copyright Azareal 2016 - 2017 */
|
||||
package main
|
||||
|
||||
import "errors"
|
||||
|
@ -110,6 +111,10 @@ func route_topics(w http.ResponseWriter, r *http.Request){
|
|||
}
|
||||
|
||||
topicList[currentID] = TopicUser{tid,title,content,createdBy,is_closed,sticky, createdAt,parentID,status,name,avatar,"",0,"","","",""}
|
||||
|
||||
if hooks["trow_assign"] != nil {
|
||||
topicList[currentID] = run_hook("trow_assign", topicList[currentID])
|
||||
}
|
||||
currentID++
|
||||
}
|
||||
err = rows.Err()
|
||||
|
@ -193,6 +198,10 @@ func route_forum(w http.ResponseWriter, r *http.Request){
|
|||
}
|
||||
|
||||
topicList[currentID] = TopicUser{tid,title,content,createdBy,is_closed,sticky,createdAt,parentID,status,name,avatar,"",0,"","","",""}
|
||||
|
||||
if hooks["trow_assign"] != nil {
|
||||
topicList[currentID] = run_hook("trow_assign", topicList[currentID])
|
||||
}
|
||||
currentID++
|
||||
}
|
||||
err = rows.Err()
|
||||
|
@ -364,6 +373,10 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
|
|||
}
|
||||
|
||||
replyList[currentID] = Reply{rid,topic.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss,replyLines,replyTag,replyURL,replyURLPrefix,replyURLName}
|
||||
|
||||
if hooks["rrow_assign"] != nil {
|
||||
replyList[currentID] = run_hook("rrow_assign", replyList[currentID])
|
||||
}
|
||||
currentID++
|
||||
}
|
||||
err = rows.Err()
|
||||
|
@ -692,6 +705,128 @@ func route_profile_reply_create(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func route_report_submit(w http.ResponseWriter, r *http.Request) {
|
||||
user := SessionCheck(w,r)
|
||||
if !user.Loggedin {
|
||||
LoginRequired(w,r,user)
|
||||
return
|
||||
}
|
||||
if user.Is_Banned {
|
||||
Banned(w,r,user)
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
LocalError("Bad Form", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
if r.FormValue("session") != user.Session {
|
||||
SecurityError(w,r,user)
|
||||
return
|
||||
}
|
||||
item_id, err := strconv.Atoi(r.URL.Path[len("/report/submit/"):])
|
||||
if err != nil {
|
||||
LocalError("Bad ID", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
item_type := r.FormValue("type")
|
||||
success := 1
|
||||
|
||||
var tid int
|
||||
var title string
|
||||
var content string
|
||||
var data string
|
||||
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)
|
||||
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)
|
||||
if err == sql.ErrNoRows {
|
||||
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)
|
||||
return
|
||||
}
|
||||
content = content + "<br><br>Original Post: <a href='/topic/" + strconv.Itoa(tid) + "'>" + title + "</a>"
|
||||
} else if item_type == "topic" {
|
||||
err = db.QueryRow("select title, content from topics where tid = ?", item_id).Scan(&title,&content)
|
||||
if err == sql.ErrNoRows {
|
||||
NotFound(w,r,user)
|
||||
return
|
||||
} else if err != nil {
|
||||
InternalError(err,w,r,user)
|
||||
return
|
||||
}
|
||||
content = content + "<br><br>Original Post: <a href='/topic/" + strconv.Itoa(item_id) + "'>" + title + "</a>"
|
||||
} else {
|
||||
// Don't try to guess the type
|
||||
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))
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
InternalError(err,w,r,user)
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&count)
|
||||
if err != nil {
|
||||
InternalError(err,w,r,user)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if count != 0 {
|
||||
LocalError("Someone has already reported this!", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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","error",user,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)
|
||||
}
|
||||
}
|
||||
|
||||
func route_account_own_edit_critical(w http.ResponseWriter, r *http.Request) {
|
||||
user := SessionCheck(w,r)
|
||||
if !user.Loggedin {
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
<div class="rowitem"><a>Control Panel</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/forums/">Forums</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/settings/">Settings</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/plugins/">Plugins</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a href="/forum/-1">Reports</a></div>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{{template "header.html" . }}
|
||||
<div class="colblock_left">
|
||||
<div class="rowitem"><a>Control Panel</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/forums/">Forums</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/settings/">Settings</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/plugins/">Plugins</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a href="/forum/-1">Reports</a></div>
|
||||
</div>
|
||||
<div class="colblock_right">
|
||||
<div class="rowitem"><a>Plugins</a></div>
|
||||
</div>
|
||||
<div class="colblock_right">
|
||||
{{range .ItemList}}
|
||||
<div class="rowitem editable_parent" style="font-weight: normal;text-transform: none;">
|
||||
<a {{if .URL}}href="{{.URL}}" {{end}}class="editable_block" style="font-size: 20px;position:relative;top: -2px;">{{.Name}}</a><br />
|
||||
<small style="margin-left: 2px;">Author: {{.Author}}</small>
|
||||
<span style="float: right;">
|
||||
{{if .Settings}}<a href="/panel/settings/" class="username">Settings</a>{{end}}
|
||||
{{if .Active}}<a href="/panel/plugins/deactivate/{{.UName}}?session={{$.CurrentUser.Session}}" class="username">Deactivate</a>
|
||||
{{else}}<a href="/panel/plugins/activate/{{.UName}}?session={{$.CurrentUser.Session}}" class="username">Activate</a>{{end}}
|
||||
</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{template "footer.html" . }}
|
|
@ -3,6 +3,8 @@
|
|||
<div class="rowitem"><a>Control Panel</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/forums/">Forums</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/settings/">Settings</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/plugins/">Plugins</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a href="/forum/-1">Reports</a></div>
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
<div class="rowitem"><a>Control Panel</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/forums/">Forums</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/settings/">Settings</a></div>
|
||||
<div class="rowitem passive"><a href="/panel/plugins/">Plugins</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a>Coming Soon</a></div>
|
||||
<div class="rowitem passive"><a href="/forum/-1">Reports</a></div>
|
||||
|
|
|
@ -3,20 +3,21 @@
|
|||
<form action='/topic/edit/submit/{{.Something.ID}}' method="post">
|
||||
<div class="rowitem"{{ if .Something.Sticky }} style="background-color: #FFFFEA;"{{end}}>
|
||||
<a class='topic_name hide_on_edit'>{{.Something.Title}}</a>
|
||||
<span class='topic_status topic_status_e topic_status_{{.Something.Status}} hide_on_edit'>{{.Something.Status}}</span>
|
||||
<span class='username topic_status_e topic_status_{{.Something.Status}} hide_on_edit' style="font-weight:normal;float: right;">{{.Something.Status}}</span>
|
||||
<span class="username" style="border-right: 0;font-weight: normal;float: right;">Status</span>
|
||||
{{if .CurrentUser.Is_Mod}}
|
||||
<a href='/topic/edit/{{.Something.ID}}' class="username hide_on_edit open_edit" style="font-weight: normal;">Edit</a>
|
||||
<a href='/topic/edit/{{.Something.ID}}' class="username hide_on_edit open_edit" style="font-weight: normal;margin-left: 6px;">Edit</a>
|
||||
<a href='/topic/delete/submit/{{.Something.ID}}' class="username" style="font-weight: normal;">Delete</a>
|
||||
{{ if .Something.Sticky }}<a href='/topic/unstick/submit/{{.Something.ID}}' class="username" style="font-weight: normal;">Unpin</a>{{else}}<a href='/topic/stick/submit/{{.Something.ID}}' class="username" style="font-weight: normal;">Pin</a>{{end}}
|
||||
|
||||
<input class='show_on_edit topic_name_input' name="topic_name" value='{{.Something.Title}}' type="text" />
|
||||
<select name="topic_status" class='show_on_edit topic_status_input'>
|
||||
<select name="topic_status" class='show_on_edit topic_status_input' style='float: right;'>
|
||||
<option>open</option>
|
||||
<option>closed</option>
|
||||
</select>
|
||||
<button name="topic-button" class="formbutton show_on_edit submit_edit">Update</button>
|
||||
{{end}}
|
||||
<a href='/topic/report/submit/{{.Something.ID}}' class="username" style="font-weight: normal;">Report</a>
|
||||
<a href="/report/submit/{{.Something.ID}}?session={{.CurrentUser.Session}}&type=topic" class="username report_item" style="font-weight: normal;">Report</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -38,7 +39,7 @@
|
|||
<a href="/user/{{$element.CreatedBy}}" class="username">{{$element.CreatedByName}}</a>
|
||||
{{if $.CurrentUser.Is_Mod}}<a href="/reply/edit/submit/{{$element.ID}}"><button class="username edit_item">Edit</button></a>
|
||||
<a href="/reply/delete/submit/{{$element.ID}}"><button class="username delete_item">Delete</button></a>{{end}}
|
||||
<a href="/reply/report/submit/{{$element.ID}}"><button class="username report_item">Report</button></a>
|
||||
<a href="/report/submit/{{$element.ID}}?session={{$.CurrentUser.Session}}&type=reply"><button class="username report_item">Report</button></a>
|
||||
{{if $element.Tag}}<a class="username" style="float: right;">{{$element.Tag}}</a>{{else if $element.URLName}}<a href="{{$element.URL}}" class="username" style="color: #505050;float: right;" rel="nofollow">{{$element.URLName}}</a>
|
||||
<a class="username" style="color: #505050;float: right;border-right: 0;">{{$element.URLPrefix}}</a>{{end}}
|
||||
</div>{{end}}
|
||||
|
|
Loading…
Reference in New Issue