Admins now show up in purple on posts.
Added a .gitignore file. Added group caching. Improved the forum list. Added the Member group. Opening posts are now parsed properly. Fixed a bug where the avatar of the opening poster was applied to everyone. Only admins can see moderation options now.
|
@ -0,0 +1,2 @@
|
||||||
|
src/uploads/*
|
||||||
|
bin/*
|
|
@ -33,7 +33,7 @@ Set the password column of your user account in the database to what you want yo
|
||||||
|
|
||||||
# Run the program
|
# Run the program
|
||||||
|
|
||||||
go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go forum.go config.go
|
go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go forum.go group.go config.go
|
||||||
|
|
||||||
Alternatively, you could run the run.bat batch file on Windows.
|
Alternatively, you could run the run.bat batch file on Windows.
|
||||||
|
|
||||||
|
@ -65,4 +65,4 @@ Tweak the CSS to make it responsive.
|
||||||
|
|
||||||
Add a forum cache.
|
Add a forum cache.
|
||||||
|
|
||||||
Add a group cache.
|
Cache the static files in memory.
|
||||||
|
|
After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
Before Width: | Height: | Size: 312 KiB After Width: | Height: | Size: 312 KiB |
|
@ -12,3 +12,4 @@ var max_request_size = 5 * megabyte
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
var default_route = route_topics
|
var default_route = route_topics
|
||||||
|
var staff_css = "background-color: #ffeaff;background-position: left;"
|
|
@ -64,6 +64,7 @@ CREATE TABLE `replies`(
|
||||||
INSERT INTO users(`name`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`)
|
INSERT INTO users(`name`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`)
|
||||||
VALUES ('Admin',1,1,NOW(),NOW());
|
VALUES ('Admin',1,1,NOW(),NOW());
|
||||||
INSERT INTO users_groups(`name`,`permissions`,`is_admin`) VALUES ('Administrator','{}',1);
|
INSERT INTO users_groups(`name`,`permissions`,`is_admin`) VALUES ('Administrator','{}',1);
|
||||||
|
INSERT INTO users_groups(`name`,`permissions`) VALUES ('Member','{}');
|
||||||
INSERT INTO forums(`name`,`lastTopicTime`) VALUES ('General',NOW());
|
INSERT INTO forums(`name`,`lastTopicTime`) VALUES ('General',NOW());
|
||||||
INSERT INTO topics(`title`,`content`,`createdAt`,`lastReplyAt`,`createdBy`,`parentID`)
|
INSERT INTO topics(`title`,`content`,`createdAt`,`lastReplyAt`,`createdBy`,`parentID`)
|
||||||
VALUES ('Test Topic','A topic automatically generated by the software.',NOW(),NOW(),1,1);
|
VALUES ('Test Topic','A topic automatically generated by the software.',NOW(),NOW(),1,1);
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
type Group struct
|
||||||
|
{
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
Permissions string
|
||||||
|
Is_Admin bool
|
||||||
|
Is_Banned bool
|
||||||
|
}
|
27
src/main.go
|
@ -17,6 +17,7 @@ const kilobyte int = 1024
|
||||||
const megabyte int = 1024 * 1024
|
const megabyte int = 1024 * 1024
|
||||||
const saltLength int = 32
|
const saltLength int = 32
|
||||||
const sessionLength int = 80
|
const sessionLength int = 80
|
||||||
|
|
||||||
var db *sql.DB
|
var db *sql.DB
|
||||||
var get_session_stmt *sql.Stmt
|
var get_session_stmt *sql.Stmt
|
||||||
var create_topic_stmt *sql.Stmt
|
var create_topic_stmt *sql.Stmt
|
||||||
|
@ -37,8 +38,12 @@ var set_avatar_stmt *sql.Stmt
|
||||||
var set_username_stmt *sql.Stmt
|
var set_username_stmt *sql.Stmt
|
||||||
var register_stmt *sql.Stmt
|
var register_stmt *sql.Stmt
|
||||||
var username_exists_stmt *sql.Stmt
|
var username_exists_stmt *sql.Stmt
|
||||||
|
|
||||||
var custom_pages map[string]string = make(map[string]string)
|
var custom_pages map[string]string = make(map[string]string)
|
||||||
var templates = template.Must(template.ParseGlob("templates/*"))
|
var templates = template.Must(template.ParseGlob("templates/*"))
|
||||||
|
var no_css_tmpl = template.CSS("")
|
||||||
|
var staff_css_tmpl = template.CSS(staff_css)
|
||||||
|
var groups map[int]Group = make(map[int]Group)
|
||||||
|
|
||||||
func init_database(err error) {
|
func init_database(err error) {
|
||||||
if(dbpassword != ""){
|
if(dbpassword != ""){
|
||||||
|
@ -161,7 +166,7 @@ func init_database(err error) {
|
||||||
// create_account_stmt, err = db.Prepare("INSERT INTO
|
// create_account_stmt, err = db.Prepare("INSERT INTO
|
||||||
|
|
||||||
log.Print("Preparing register statement.")
|
log.Print("Preparing register statement.")
|
||||||
register_stmt, err = db.Prepare("INSERT INTO users(`name`,`password`,`salt`,`group`,`is_super_admin`,`session`) VALUES(?,?,?,0,0,?)")
|
register_stmt, err = db.Prepare("INSERT INTO users(`name`,`password`,`salt`,`group`,`is_super_admin`,`session`) VALUES(?,?,?,2,0,?)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -171,6 +176,26 @@ func init_database(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Print("Loading the usergroups.")
|
||||||
|
rows, err := db.Query("select gid,name,permissions,is_admin,is_banned from users_groups")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
group := Group{0,"","",false,false}
|
||||||
|
err := rows.Scan(&group.ID, &group.Name, &group.Permissions, &group.Is_Admin, &group.Is_Banned)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
groups[group.ID] = group
|
||||||
|
}
|
||||||
|
err = rows.Err()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main(){
|
func main(){
|
||||||
|
|
|
@ -251,11 +251,17 @@ button
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
color: #505050; /* 80,80,80 */
|
color: #505050; /* 80,80,80 */
|
||||||
|
background-color: #FFFFFF;
|
||||||
border-style: dotted;
|
border-style: dotted;
|
||||||
border-color: #505050; /* 232,232,232. All three RGB colours being the same seems to create a shade of gray */
|
border-color: #505050; /* 232,232,232. All three RGB colours being the same seems to create a shade of gray */
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
button.username
|
||||||
|
{
|
||||||
|
position: relative;
|
||||||
|
top: -0.25px;
|
||||||
|
}
|
||||||
|
|
||||||
.show_on_edit
|
.show_on_edit
|
||||||
{
|
{
|
||||||
|
|
After Width: | Height: | Size: 539 B |
|
@ -13,4 +13,5 @@ type Reply struct
|
||||||
LastEdit int
|
LastEdit int
|
||||||
LastEditBy int
|
LastEditBy int
|
||||||
Avatar string
|
Avatar string
|
||||||
|
Css template.CSS
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ func route_topics(w http.ResponseWriter, r *http.Request){
|
||||||
avatar = "/uploads/avatar_" + strconv.Itoa(createdBy) + avatar
|
avatar = "/uploads/avatar_" + strconv.Itoa(createdBy) + avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
topicList[currentID] = TopicUser{tid, title, content, createdBy, is_closed, sticky, createdAt,parentID, status, name, avatar}
|
topicList[currentID] = TopicUser{tid, title, content, createdBy, is_closed, sticky, createdAt,parentID, status, name, avatar, ""}
|
||||||
currentID++
|
currentID++
|
||||||
}
|
}
|
||||||
err = rows.Err()
|
err = rows.Err()
|
||||||
|
@ -173,7 +173,7 @@ func route_forum(w http.ResponseWriter, r *http.Request){
|
||||||
avatar = "/uploads/avatar_" + strconv.Itoa(createdBy) + avatar
|
avatar = "/uploads/avatar_" + strconv.Itoa(createdBy) + avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
topicList[currentID] = TopicUser{tid, title, content, createdBy, is_closed, sticky, createdAt,parentID, status, name, avatar}
|
topicList[currentID] = TopicUser{tid, title, content, createdBy, is_closed, sticky, createdAt,parentID, status, name, avatar, ""}
|
||||||
currentID++
|
currentID++
|
||||||
}
|
}
|
||||||
err = rows.Err()
|
err = rows.Err()
|
||||||
|
@ -243,13 +243,16 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
|
||||||
replyLastEdit int
|
replyLastEdit int
|
||||||
replyLastEditBy int
|
replyLastEditBy int
|
||||||
replyAvatar string
|
replyAvatar string
|
||||||
|
replyCss template.CSS
|
||||||
|
is_super_admin bool
|
||||||
|
group int
|
||||||
|
|
||||||
currentID int
|
currentID int
|
||||||
replyList map[int]interface{}
|
replyList map[int]interface{}
|
||||||
)
|
)
|
||||||
replyList = make(map[int]interface{})
|
replyList = make(map[int]interface{})
|
||||||
currentID = 0
|
currentID = 0
|
||||||
topic := TopicUser{0,"","",0,false,false,"",0,"","",""}
|
topic := TopicUser{0,"","",0,false,false,"",0,"","","",no_css_tmpl}
|
||||||
|
|
||||||
topic.ID, err = strconv.Atoi(r.URL.Path[len("/topic/"):])
|
topic.ID, err = strconv.Atoi(r.URL.Path[len("/topic/"):])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -259,7 +262,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
|
||||||
|
|
||||||
// Get the topic..
|
// Get the topic..
|
||||||
//err = db.QueryRow("select title, content, createdBy, status, is_closed from topics where tid = ?", tid).Scan(&title, &content, &createdBy, &status, &is_closed)
|
//err = db.QueryRow("select title, content, createdBy, status, is_closed from topics where tid = ?", tid).Scan(&title, &content, &createdBy, &status, &is_closed)
|
||||||
err = db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, users.name, users.avatar from topics left join users ON topics.createdBy = users.uid where tid = ?", topic.ID).Scan(&topic.Title, &content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.CreatedByName, &topic.Avatar)
|
err = db.QueryRow("select topics.title, topics.content, topics.createdBy, topics.createdAt, topics.is_closed, topics.sticky, topics.parentID, users.name, users.avatar, users.is_super_admin, users.group from topics left join users ON topics.createdBy = users.uid where tid = ?", topic.ID).Scan(&topic.Title, &content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.CreatedByName, &topic.Avatar, &is_super_admin, &group)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
errmsg := "The requested topic doesn't exist."
|
errmsg := "The requested topic doesn't exist."
|
||||||
pi := Page{"Error","error",user,tList,errmsg}
|
pi := Page{"Error","error",user,tList,errmsg}
|
||||||
|
@ -275,7 +278,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
topic.Content = template.HTML(content)
|
topic.Content = template.HTML(parse_message(content))
|
||||||
if topic.Is_Closed {
|
if topic.Is_Closed {
|
||||||
topic.Status = "closed"
|
topic.Status = "closed"
|
||||||
} else {
|
} else {
|
||||||
|
@ -284,10 +287,13 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
|
||||||
if topic.Avatar != "" && topic.Avatar[0] == '.' {
|
if topic.Avatar != "" && topic.Avatar[0] == '.' {
|
||||||
topic.Avatar = "/uploads/avatar_" + strconv.Itoa(topic.CreatedBy) + topic.Avatar
|
topic.Avatar = "/uploads/avatar_" + strconv.Itoa(topic.CreatedBy) + topic.Avatar
|
||||||
}
|
}
|
||||||
|
if is_super_admin || groups[group].Is_Admin {
|
||||||
|
topic.Css = staff_css_tmpl
|
||||||
|
}
|
||||||
|
|
||||||
// Get the replies..
|
// Get the replies..
|
||||||
//rows, err := db.Query("select rid, content, createdBy, createdAt from replies where tid = ?", tid)
|
//rows, err := db.Query("select rid, content, createdBy, createdAt from replies where tid = ?", tid)
|
||||||
rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name from replies left join users ON replies.createdBy = users.uid where tid = ?", topic.ID)
|
rows, err := db.Query("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.is_super_admin, users.group from replies left join users ON replies.createdBy = users.uid where tid = ?", topic.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InternalError(err,w,r,user)
|
InternalError(err,w,r,user)
|
||||||
return
|
return
|
||||||
|
@ -295,17 +301,22 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
err := rows.Scan(&rid, &replyContent, &replyCreatedBy, &replyCreatedAt, &replyLastEdit, &replyLastEditBy, &replyAvatar, &replyCreatedByName)
|
err := rows.Scan(&rid, &replyContent, &replyCreatedBy, &replyCreatedAt, &replyLastEdit, &replyLastEditBy, &replyAvatar, &replyCreatedByName, &is_super_admin, &group)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InternalError(err,w,r,user)
|
InternalError(err,w,r,user)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if is_super_admin || groups[group].Is_Admin {
|
||||||
|
replyCss = staff_css_tmpl
|
||||||
|
} else {
|
||||||
|
replyCss = no_css_tmpl
|
||||||
|
}
|
||||||
if replyAvatar != "" && replyAvatar[0] == '.' {
|
if replyAvatar != "" && replyAvatar[0] == '.' {
|
||||||
replyAvatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + replyAvatar
|
replyAvatar = "/uploads/avatar_" + strconv.Itoa(replyCreatedBy) + replyAvatar
|
||||||
}
|
}
|
||||||
|
|
||||||
replyList[currentID] = Reply{rid,topic.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar}
|
replyList[currentID] = Reply{rid,topic.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss}
|
||||||
currentID++
|
currentID++
|
||||||
}
|
}
|
||||||
err = rows.Err()
|
err = rows.Err()
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go config.go forum.go
|
go run errors.go main.go pages.go reply.go routes.go topic.go user.go utils.go config.go forum.go group.go
|
||||||
pause
|
pause
|
|
@ -1,7 +1,7 @@
|
||||||
{{template "header.html" . }}
|
{{template "header.html" . }}
|
||||||
<div class="rowblock">
|
<div class="rowblock">
|
||||||
{{range .ItemList}}<div class="rowitem">
|
{{range .ItemList}}<div class="rowitem">
|
||||||
<a href="/forum/{{.ID}}">{{.Name}}</a>
|
<a href="/forum/{{.ID}}" style="font-size: 20px;position:relative;top: -2px;font-weight: normal;text-transform: none;">{{.Name}}</a>
|
||||||
<a href="/topic/{{.LastTopicID}}" style="font-weight: normal;text-transform: none;float: right;">{{.LastTopic}} <small style="font-size: 12px;">{{.LastTopicTime}}</small></a>
|
<a href="/topic/{{.LastTopicID}}" style="font-weight: normal;text-transform: none;float: right;">{{.LastTopic}} <small style="font-size: 12px;">{{.LastTopicTime}}</small></a>
|
||||||
</div>{{end}}
|
</div>{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<div class="rowitem"{{ if .Something.Sticky }}style="background-color: #FFFFEA;"{{end}}>
|
<div class="rowitem"{{ if .Something.Sticky }}style="background-color: #FFFFEA;"{{end}}>
|
||||||
<a class='topic_name hide_on_edit'>{{.Something.Title}}</a>
|
<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='topic_status topic_status_e topic_status_{{.Something.Status}} hide_on_edit'>{{.Something.Status}}</span>
|
||||||
|
{{if .CurrentUser.Is_Admin}}
|
||||||
<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;">Edit</a>
|
||||||
<a href='/topic/delete/submit/{{.Something.ID}}' class="username" style="font-weight: normal;">Delete</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}}
|
{{ 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}}
|
||||||
|
@ -14,11 +15,12 @@
|
||||||
<option>closed</option>
|
<option>closed</option>
|
||||||
</select>
|
</select>
|
||||||
<button name="topic-button" class="formbutton show_on_edit submit_edit">Update</button>
|
<button name="topic-button" class="formbutton show_on_edit submit_edit">Update</button>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="rowblock">
|
<div class="rowblock">
|
||||||
<div class="rowitem passive editable_parent" style="border-bottom: none;{{ if .Something.Avatar }}background-image: url({{ .Something.Avatar }});background-position: left;background-repeat: no-repeat;background-size: 128px;padding-left: 136px;{{end}}">
|
<div class="rowitem passive editable_parent" style="border-bottom: none;{{ if .Something.Avatar }}background-image: url({{ .Something.Avatar }}), url(/static/white-dot.jpg);background-position: top left;background-repeat: no-repeat, repeat-y;background-size: 128px;padding-left: 136px;{{.Something.Css}}{{end}}">
|
||||||
<span class="hide_on_edit topic_content">{{.Something.Content}}</span>
|
<span class="hide_on_edit topic_content">{{.Something.Content}}</span>
|
||||||
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Something.Content}}</textarea>
|
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Something.Content}}</textarea>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
@ -27,12 +29,12 @@
|
||||||
</div><br />
|
</div><br />
|
||||||
<div class="rowblock" style="overflow: hidden;">
|
<div class="rowblock" style="overflow: hidden;">
|
||||||
{{range $index, $element := .ItemList}}
|
{{range $index, $element := .ItemList}}
|
||||||
<div class="rowitem passive deletable_block editable_parent" style="{{ if $element.Avatar }}background-image: url({{ $element.Avatar }});background-position: left;background-repeat: no-repeat;background-size: 128px;padding-left: 136px;{{end}}">
|
<div class="rowitem passive deletable_block editable_parent" style="{{ if $element.Avatar }}background-image: url({{$element.Avatar}}), url(/static/white-dot.jpg);background-position: top left;background-repeat: no-repeat, repeat-y;background-size: 128px;padding-left: 136px;{{$element.Css}}{{end}}">
|
||||||
<span class="editable_block">{{$element.ContentHtml}}</span>
|
<span class="editable_block">{{$element.ContentHtml}}</span>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<a class="username">{{$element.CreatedByName}}<a/>
|
<a class="username">{{$element.CreatedByName}}</a>
|
||||||
<a href="/reply/edit/submit/{{$element.ID}}"><button class="username edit_item">Edit</button></a>
|
{{if $.CurrentUser.Is_Admin}}<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>
|
<a href="/reply/delete/submit/{{$element.ID}}"><button class="username delete_item">Delete</button></a>{{end}}
|
||||||
</div>{{ end }}
|
</div>{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<div class="rowblock">
|
<div class="rowblock">
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
package main
|
package main
|
||||||
|
import "html/template"
|
||||||
|
|
||||||
type Topic struct
|
type Topic struct
|
||||||
{
|
{
|
||||||
|
@ -27,4 +28,5 @@ type TopicUser struct
|
||||||
|
|
||||||
CreatedByName string
|
CreatedByName string
|
||||||
Avatar string
|
Avatar string
|
||||||
|
Css template.CSS
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 309 KiB |