We made more progress with topic views on Cosora. We accidentally broke Tempra Conflux's topic view in the process and we're planning to fix it in the following commit.

Added the ForumPermsStore.
Fixed the creation dates of the posts.
Added a skippable version of the vhooks.
Social Groups now work again, although I'm planning to refactor them ;)
Added more Markdown tests and fixed a few Markdown bugs.
This commit is contained in:
Azareal 2017-11-02 02:52:21 +00:00
parent 0361310eb2
commit 6f45c62815
18 changed files with 521 additions and 348 deletions

View File

@ -49,6 +49,7 @@ func initDatabase() (err error) {
if err != nil {
return err
}
fpstore = NewForumPermsStore()
log.Print("Loading the settings.")
err = LoadSettings()

View File

@ -21,8 +21,6 @@ var hooks = map[string][]func(interface{}) interface{}{
// Hooks with a variable number of arguments
var vhooks = map[string]func(...interface{}) interface{}{
"simple_forum_check_pre_perms": nil,
"forum_check_pre_perms": nil,
"intercept_build_widgets": nil,
"forum_trow_assign": nil,
"topics_topic_row_assign": nil,
@ -32,6 +30,14 @@ var vhooks = map[string]func(...interface{}) interface{}{
"topic_create_pre_loop": nil,
}
// Hooks with a variable number of arguments and return values for skipping the parent function and propagating an error upwards
var vhookSkippable = map[string]func(...interface{}) (bool, RouteError){
"simple_forum_check_pre_perms": nil,
"forum_check_pre_perms": nil,
}
//var vhookErrorable = map[string]func(...interface{}) (interface{}, RouteError){}
// Coming Soon:
type Message interface {
ID() int
@ -221,6 +227,9 @@ func (plugin *Plugin) AddHook(name string, handler interface{}) {
case func(...interface{}) interface{}:
vhooks[name] = h
plugin.Hooks[name] = 0
case func(...interface{}) (bool, RouteError):
vhookSkippable[name] = h
plugin.Hooks[name] = 0
default:
panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()?
}
@ -258,6 +267,8 @@ func (plugin *Plugin) RemoveHook(name string, handler interface{}) {
preRenderHooks[name] = hook
case func(...interface{}) interface{}:
delete(vhooks, name)
case func(...interface{}) (bool, RouteError):
delete(vhookSkippable, name)
default:
panic("I don't recognise this kind of handler!") // Should this be an error for the plugin instead of a panic()?
}
@ -302,6 +313,10 @@ func runVhook(name string, data ...interface{}) interface{} {
return vhooks[name](data...)
}
func runVhookSkippable(name string, data ...interface{}) (bool, RouteError) {
return vhookSkippable[name](data...)
}
func runVhookNoreturn(name string, data ...interface{}) {
_ = vhooks[name](data...)
}

19
forum_perms_store.go Normal file
View File

@ -0,0 +1,19 @@
package main
var fpstore *ForumPermsStore
type ForumPermsStore struct {
}
func NewForumPermsStore() *ForumPermsStore {
return &ForumPermsStore{}
}
func (fps *ForumPermsStore) Get(fid int, gid int) (fperms ForumPerms, err error) {
// TODO: Add a hook here and have plugin_socialgroups use it
group, err := gstore.Get(gid)
if err != nil {
return fperms, ErrNoRows
}
return group.Forums[fid], nil
}

View File

@ -532,8 +532,8 @@ func buildForumPermissions() error {
groups, err := gstore.GetAll()
if err != nil {
return err
}
for _, group := range groups {
if dev.DebugMode {
log.Print("Adding the forum permissions for Group #" + strconv.Itoa(group.ID) + " - " + group.Name)

View File

@ -45,7 +45,7 @@ func deactivateMarkdown() {
// An adapter for the parser, so that the parser can call itself recursively.
// This is less for the simple Markdown elements like bold and italics and more for the really complicated ones I plan on adding at some point.
func markdownParse(msg string) string {
msg = strings.TrimSpace(_markdownParse(msg+" ", 0))
msg = strings.TrimSuffix(_markdownParse(msg+" ", 0), " ")
log.Print("final msg: ", msg)
return msg
}
@ -172,9 +172,9 @@ func _markdownParse(msg string, n int) string {
index++
//log.Print("preskip index",index)
//log.Print("preskip msg[index]",msg[index])
//log.Print("preskip string(msg[index])",string(msg[index]))
//log.Print("preskip index: ", index)
//log.Print("preskip msg[index]: ", msg[index])
//log.Print("preskip string(msg[index]): ", string(msg[index]))
index = markdownSkipUntilAsterisk(msg, index)
if index >= len(msg) {
@ -222,8 +222,8 @@ func _markdownParse(msg string, n int) string {
sIndex++
}
//log.Print("sIndex",sIndex)
//log.Print("lIndex",lIndex)
//log.Print("sIndex: ", sIndex)
//log.Print("lIndex: ", lIndex)
if lIndex <= sIndex {
//log.Print("unclosed markdown element @ lIndex <= sIndex")
@ -241,19 +241,19 @@ func _markdownParse(msg string, n int) string {
break
}
//log.Print("final sIndex",sIndex)
//log.Print("final lIndex",lIndex)
//log.Print("final index",index)
//log.Print("final msg[index]",msg[index])
//log.Print("final string(msg[index])",string(msg[index]))
//log.Print("final sIndex: ", sIndex)
//log.Print("final lIndex: ",lIndex)
//log.Print("final index: ", index)
//log.Print("final msg[index]: ", msg[index])
//log.Print("final string(msg[index]): ", string(msg[index]))
//log.Print("final msg[sIndex]",msg[sIndex])
//log.Print("final string(msg[sIndex])",string(msg[sIndex]))
//log.Print("final msg[lIndex]",msg[lIndex])
//log.Print("final string(msg[lIndex])",string(msg[lIndex]))
//log.Print("final msg[sIndex]: ", msg[sIndex])
//log.Print("final string(msg[sIndex]): ", string(msg[sIndex]))
//log.Print("final msg[lIndex]: ", msg[lIndex])
//log.Print("final string(msg[lIndex]): ", string(msg[lIndex]))
//log.Print("[]byte(msg[:sIndex])",[]byte(msg[:sIndex]))
//log.Print("[]byte(msg[:lIndex])",[]byte(msg[:lIndex]))
//log.Print("[]byte(msg[:sIndex]): ", []byte(msg[:sIndex]))
//log.Print("[]byte(msg[:lIndex]): ", []byte(msg[:lIndex]))
outbytes = append(outbytes, msg[lastElement:startIndex]...)
@ -278,13 +278,13 @@ func _markdownParse(msg string, n int) string {
index--
case '\\':
if (index + 1) < len(msg) {
if isMarkdownStartChar(msg[index+1]) && msg[index+1] != '\\' {
outbytes = append(outbytes, msg[lastElement:index]...)
index++
lastElement = index
}
}
//case '`':
//case '_':
//case '~':
//case 10: // newline
}
}
@ -298,6 +298,10 @@ func _markdownParse(msg string, n int) string {
return string(outbytes)
}
func isMarkdownStartChar(char byte) bool {
return char == '\\' || char == '~' || char == '_' || char == 10 || char == '`' || char == '*'
}
func markdownFindChar(data string, index int, char byte) bool {
for ; index < len(data); index++ {
item := data[index]

View File

@ -561,7 +561,7 @@ func socialgroupsTopicCreatePreLoop(args ...interface{}) interface{} {
// TODO: Add privacy options
// TODO: Add support for multiple boards and add per-board simplified permissions
// TODO: Take isJs into account for routes which expect JSON responses
func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
func socialgroupsForumCheck(args ...interface{}) (skip bool, rerr RouteError) {
var r = args[1].(*http.Request)
var fid = args[3].(*int)
var forum = fstore.DirtyGet(*fid)
@ -569,19 +569,14 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
if forum.ParentType == "socialgroup" {
var err error
var w = args[0].(http.ResponseWriter)
var success = args[4].(*bool)
sgItem, ok := r.Context().Value("socialgroups_current_group").(*SocialGroup)
if !ok {
sgItem, err = socialgroupsGetGroup(forum.ParentID)
if err != nil {
InternalError(errors.New("Unable to find the parent group for a forum"), w, r)
*success = false
return false
return true, InternalError(errors.New("Unable to find the parent group for a forum"), w, r)
}
if !sgItem.Active {
NotFound(w, r)
*success = false
return false
return true, NotFound(w, r)
}
r = r.WithContext(context.WithValue(r.Context(), "socialgroups_current_group", sgItem))
}
@ -600,16 +595,16 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
err = socialgroupsGetMemberStmt.QueryRow(sgItem.ID, user.ID).Scan(&rank, &posts, &joinedAt)
if err != nil && err != ErrNoRows {
*success = false
InternalError(err, w, r)
return false
return true, InternalError(err, w, r)
} else if err != nil {
return true
// TODO: Should we let admins / guests into public groups?
return true, LocalError("You're not part of this group!", w, r, *user)
}
// TODO: Implement bans properly by adding the Local Ban API in the next commit
// TODO: How does this even work? Refactor it along with the rest of this plugin!
if rank < 0 {
return true
return true, LocalError("You've been banned from this group!", w, r, *user)
}
// Basic permissions for members, more complicated permissions coming in the next commit!
@ -622,10 +617,10 @@ func socialgroupsForumCheck(args ...interface{}) (skip interface{}) {
} else {
overrideForumPerms(&user.Perms, true)
}
return true
return true, nil
}
return false
return false, nil
}
// TODO: Override redirects? I don't think this is needed quite yet

View File

@ -214,6 +214,8 @@ func TestMarkdownRender(t *testing.T) {
msgList = addMEPair(msgList, "**hi*", "*<i>hi</i>")
msgList = addMEPair(msgList, "***hi***", "<b><i>hi</i></b>")
msgList = addMEPair(msgList, "***h***", "<b><i>h</i></b>")
msgList = addMEPair(msgList, "\\***h**\\*", "*<b>h</b>*")
msgList = addMEPair(msgList, "\\*\\**h*\\*\\*", "**<i>h</i>**")
msgList = addMEPair(msgList, "\\*hi\\*", "*hi*")
msgList = addMEPair(msgList, "d\\*hi\\*", "d*hi*")
msgList = addMEPair(msgList, "\\*hi\\*d", "*hi*d")
@ -225,8 +227,10 @@ func TestMarkdownRender(t *testing.T) {
msgList = addMEPair(msgList, "\\\\\\d", "\\\\\\d")
msgList = addMEPair(msgList, "d\\", "d\\")
msgList = addMEPair(msgList, "\\d\\", "\\d\\")
msgList = addMEPair(msgList, "*_hi_*", "<i><u>hi</u></i>")
msgList = addMEPair(msgList, "*~hi~*", "<i><s>hi</s></i>")
msgList = addMEPair(msgList, "~*hi*~", "<s><i>hi</i></s>")
msgList = addMEPair(msgList, "~ *hi* ~", "<s> <i>hi</i> </s>")
msgList = addMEPair(msgList, "_~hi~_", "<u><s>hi</s></u>")
msgList = addMEPair(msgList, "***~hi~***", "<b><i><s>hi</s></i></b>")
msgList = addMEPair(msgList, "**", "**")
@ -255,4 +259,37 @@ func TestMarkdownRender(t *testing.T) {
t.Error("Expected:", item.Expects)
}
}
for _, item := range msgList {
res = markdownParse("\n" + item.Msg)
if res != "\n"+item.Expects {
t.Error("Testing string '\n" + item.Msg + "'")
t.Error("Bad output:", "'"+res+"'")
//t.Error("Ouput in bytes:", []byte(res))
t.Error("Expected:", "\n"+item.Expects)
}
}
for _, item := range msgList {
res = markdownParse("\t" + item.Msg)
if res != "\t"+item.Expects {
t.Error("Testing string '\t" + item.Msg + "'")
t.Error("Bad output:", "'"+res+"'")
//t.Error("Ouput in bytes:", []byte(res))
t.Error("Expected:", "\t"+item.Expects)
}
}
for _, item := range msgList {
res = markdownParse("d" + item.Msg)
if res != "d"+item.Expects {
t.Error("Testing string 'd" + item.Msg + "'")
t.Error("Bad output:", "'"+res+"'")
//t.Error("Ouput in bytes:", []byte(res))
t.Error("Expected:", "d"+item.Expects)
}
}
// TODO: Write suffix tests and double string tests
// TODO: Write similar prefix, suffix, and double string tests for plugin_bbcode. Ditto for the outer parser along with suitable tests for that like making sure the URL parser and media embedder works.
}

View File

@ -6,7 +6,10 @@
*/
package main
import "errors"
import (
"errors"
"time"
)
// ? - Should we add a reply store to centralise all the reply logic? Would this cover profile replies too or would that be separate?
var rstore ReplyStore
@ -21,7 +24,8 @@ type ReplyUser struct {
UserLink string
CreatedByName string
Group int
CreatedAt string
CreatedAt time.Time
RelativeCreatedAt string
LastEdit int
LastEditBy int
Avatar string
@ -45,7 +49,8 @@ type Reply struct {
Content string
CreatedBy int
Group int
CreatedAt string
CreatedAt time.Time
RelativeCreatedAt string
LastEdit int
LastEditBy int
ContentLines int

View File

@ -533,22 +533,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) RouteError
if postGroup.IsMod || postGroup.IsAdmin {
topic.ClassName = config.StaffCSS
}
/*if headerVars.Settings["url_tags"] == false {
topic.URLName = ""
} else {
topic.URL, ok = external_sites[topic.URLPrefix]
if !ok {
topic.URL = topic.URLName
} else {
topic.URL = topic.URL + topic.URLName
}
}*/
topic.CreatedAt, err = relativeTimeFromString(topic.CreatedAt)
if err != nil {
topic.CreatedAt = ""
}
topic.RelativeCreatedAt = relativeTime(topic.CreatedAt)
// TODO: Make a function for this? Build a more sophisticated noavatar handling system?
if topic.Avatar != "" {
@ -604,7 +589,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) RouteError
replyItem.ClassName = ""
}
// TODO: Make a function for this? Build a more sophisticated noavatar handling system?
// TODO: Make a function for this? Build a more sophisticated noavatar handling system? Do bulk user loads and let the UserStore initialise this?
if replyItem.Avatar != "" {
if replyItem.Avatar[0] == '.' {
replyItem.Avatar = "/uploads/avatar_" + strconv.Itoa(replyItem.CreatedBy) + replyItem.Avatar
@ -614,22 +599,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) RouteError
}
replyItem.Tag = postGroup.Tag
/*if headerVars.Settings["url_tags"] == false {
replyItem.URLName = ""
} else {
replyItem.URL, ok = external_sites[replyItem.URLPrefix]
if !ok {
replyItem.URL = replyItem.URLName
} else {
replyItem.URL = replyItem.URL + replyItem.URLName
}
}*/
replyItem.CreatedAt, err = relativeTimeFromString(replyItem.CreatedAt)
if err != nil {
replyItem.CreatedAt = ""
}
replyItem.RelativeCreatedAt = relativeTime(replyItem.CreatedAt)
// We really shouldn't have inline HTML, we should do something about this...
if replyItem.ActionType != "" {
@ -683,7 +653,8 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) RouteError
}
var err error
var replyContent, replyCreatedByName, replyCreatedAt, replyAvatar, replyTag, replyClassName string
var replyCreatedAt time.Time
var replyContent, replyCreatedByName, replyRelativeCreatedAt, replyAvatar, replyTag, replyClassName string
var rid, replyCreatedBy, replyLastEdit, replyLastEditBy, replyLines, replyGroup int
var replyList []ReplyUser
@ -736,6 +707,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) RouteError
} else {
replyClassName = ""
}
if replyAvatar != "" {
if replyAvatar[0] == '.' {
replyAvatar = "/uploads/avatar_" + strconv.Itoa(replyCreatedBy) + replyAvatar
@ -754,10 +726,11 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) RouteError
replyLiked := false
replyLikeCount := 0
replyRelativeCreatedAt = relativeTime(replyCreatedAt)
// TODO: Add a hook here
replyList = append(replyList, ReplyUser{rid, puser.ID, replyContent, parseMessage(replyContent, 0, ""), replyCreatedBy, buildProfileURL(nameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
replyList = append(replyList, ReplyUser{rid, puser.ID, replyContent, parseMessage(replyContent, 0, ""), replyCreatedBy, buildProfileURL(nameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyRelativeCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
}
err = rows.Err()
if err != nil {

View File

@ -59,51 +59,51 @@ func simpleForumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fi
}
// Is there a better way of doing the skip AND the success flag on this hook like multiple returns?
if vhooks["simple_forum_check_pre_perms"] != nil {
if runVhook("simple_forum_check_pre_perms", w, r, user, &fid, &rerr, &headerLite).(bool) {
if vhookSkippable["simple_forum_check_pre_perms"] != nil {
var skip bool
skip, rerr = runVhookSkippable("simple_forum_check_pre_perms", w, r, user, &fid, &headerLite)
if skip {
return headerLite, rerr
}
}
group, err := gstore.Get(user.Group)
fperms, err := fpstore.Get(fid, user.Group)
if err != nil {
// TODO: Refactor this
log.Printf("Group #%d doesn't exist despite being used by User #%d", user.Group, user.ID)
log.Printf("Unable to get the forum perms for Group #%d for User #%d", user.Group, user.ID)
return nil, PreError("Something weird happened", w, r)
}
fperms := group.Forums[fid]
cascadeForumPerms(fperms, user)
return headerLite, nil
}
func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, ferr RouteError) {
headerVars, ferr = UserCheck(w, r, user)
if ferr != nil {
return headerVars, ferr
func forumUserCheck(w http.ResponseWriter, r *http.Request, user *User, fid int) (headerVars *HeaderVars, rerr RouteError) {
headerVars, rerr = UserCheck(w, r, user)
if rerr != nil {
return headerVars, rerr
}
if !fstore.Exists(fid) {
return headerVars, NotFound(w, r)
}
if vhooks["forum_check_pre_perms"] != nil {
if runVhook("forum_check_pre_perms", w, r, user, &fid, &ferr, &headerVars).(bool) {
return headerVars, ferr
if vhookSkippable["forum_check_pre_perms"] != nil {
var skip bool
skip, rerr = runVhookSkippable("forum_check_pre_perms", w, r, user, &fid, &headerVars)
if skip {
return headerVars, rerr
}
}
group, err := gstore.Get(user.Group)
fperms, err := fpstore.Get(fid, user.Group)
if err != nil {
// TODO: Refactor this
log.Printf("Group #%d doesn't exist despite being used by User #%d", user.Group, user.ID)
return headerVars, PreError("Something weird happened", w, r)
log.Printf("Unable to get the forum perms for Group #%d for User #%d", user.Group, user.ID)
return nil, PreError("Something weird happened", w, r)
}
fperms := group.Forums[fid]
//log.Printf("user.Perms: %+v\n", user.Perms)
//log.Printf("fperms: %+v\n", fperms)
cascadeForumPerms(fperms, user)
return headerVars, ferr
return headerVars, rerr
}
// TODO: Put this on the user instance? Do we really want forum specific logic in there? Maybe, a method which spits a new pointer with the same contents as user?

View File

@ -92,9 +92,10 @@ func compileTemplates() error {
log.Print("Compiling the templates")
topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, "Date", time.Now(), "Date", 0, "", "127.0.0.1", 0, 1, "classname", "weird-data", buildProfileURL("fake-user", 62), "Fake User", config.DefaultGroup, "", 0, "", "", "", "", "", 58, false}
var now = time.Now()
topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, relativeTime(now), now, relativeTime(now), 0, "", "127.0.0.1", 0, 1, "classname", "weird-data", buildProfileURL("fake-user", 62), "Fake User", config.DefaultGroup, "", 0, "", "", "", "", "", 58, false}
var replyList []ReplyUser
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", config.DefaultGroup, "", 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", config.DefaultGroup, now, relativeTime(now), 0, 0, "", "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, "", ""})
var varList = make(map[string]VarItem)
tpage := TopicPage{"Title", user, headerVars, replyList, topic, 1, 1}

View File

@ -351,7 +351,7 @@ var topic_alt_29 = []byte(`</textarea>
<div class="button_container">
`)
var topic_alt_30 = []byte(`<a href="/topic/like/submit/`)
var topic_alt_31 = []byte(`" class="action_button">+1</a>`)
var topic_alt_31 = []byte(`" class="action_button like_item">+1</a>`)
var topic_alt_32 = []byte(`<a href="/topic/edit/`)
var topic_alt_33 = []byte(`" class="action_button open_edit">Edit</a>`)
var topic_alt_34 = []byte(`<a href="/topic/delete/submit/`)
@ -369,106 +369,129 @@ var topic_alt_44 = []byte(`
var topic_alt_45 = []byte(`?session=`)
var topic_alt_46 = []byte(`&type=topic" class="action_button report_item">Report</a>
`)
var topic_alt_47 = []byte(`<a class="action_button action_button_right like_count hide_on_micro">`)
var topic_alt_48 = []byte(` up</a>`)
var topic_alt_49 = []byte(`
<a class="action_button action_button_right created_at hide_on_mobile">`)
var topic_alt_50 = []byte(`</a>
var topic_alt_47 = []byte(`
<div class="action_button_right">
`)
var topic_alt_51 = []byte(`<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">`)
var topic_alt_52 = []byte(`</a>`)
var topic_alt_53 = []byte(`
var topic_alt_48 = []byte(`<a class="action_button like_count hide_on_micro">`)
var topic_alt_49 = []byte(`</a>`)
var topic_alt_50 = []byte(`
<a class="action_button created_at hide_on_mobile">`)
var topic_alt_51 = []byte(`</a>
`)
var topic_alt_52 = []byte(`<a href="#" title="IP Address" class="action_button ip_item hide_on_mobile">`)
var topic_alt_53 = []byte(`</a>`)
var topic_alt_54 = []byte(`
</div>
</div>
</div><div style="clear:both;"></div>
</article>
`)
var topic_alt_54 = []byte(`
var topic_alt_55 = []byte(`
<article itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item `)
var topic_alt_55 = []byte(`action_item`)
var topic_alt_56 = []byte(`">
var topic_alt_56 = []byte(`action_item`)
var topic_alt_57 = []byte(`">
<div class="userinfo" aria-label="The information on the poster">
<div class="avatar_item" style="background-image: url(`)
var topic_alt_57 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
var topic_alt_58 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<a href="`)
var topic_alt_58 = []byte(`" class="the_name" rel="author">`)
var topic_alt_59 = []byte(`</a>
var topic_alt_59 = []byte(`" class="the_name" rel="author">`)
var topic_alt_60 = []byte(`</a>
`)
var topic_alt_60 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`)
var topic_alt_61 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_62 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `)
var topic_alt_63 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_64 = []byte(`
var topic_alt_61 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`)
var topic_alt_62 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_63 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `)
var topic_alt_64 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_65 = []byte(`
</div>
<div class="content_container" `)
var topic_alt_65 = []byte(`style="margin-left: 0px;"`)
var topic_alt_66 = []byte(`>
var topic_alt_66 = []byte(`style="margin-left: 0px;"`)
var topic_alt_67 = []byte(`>
`)
var topic_alt_67 = []byte(`
var topic_alt_68 = []byte(`
<span class="action_icon" style="font-size: 18px;padding-right: 5px;">`)
var topic_alt_68 = []byte(`</span>
<span itemprop="text">`)
var topic_alt_69 = []byte(`</span>
<span itemprop="text">`)
var topic_alt_70 = []byte(`</span>
`)
var topic_alt_70 = []byte(`
var topic_alt_71 = []byte(`
<div class="editable_block user_content" itemprop="text">`)
var topic_alt_71 = []byte(`</div>
var topic_alt_72 = []byte(`</div>
<div class="button_container">
`)
var topic_alt_72 = []byte(`<a href="/reply/like/submit/`)
var topic_alt_73 = []byte(`" class="action_button">+1</a>`)
var topic_alt_74 = []byte(`<a href="/reply/edit/submit/`)
var topic_alt_75 = []byte(`" class="action_button edit_item">Edit</a>`)
var topic_alt_76 = []byte(`<a href="/reply/delete/submit/`)
var topic_alt_77 = []byte(`" class="action_button delete_item">Delete</a>`)
var topic_alt_78 = []byte(`
var topic_alt_73 = []byte(`<a href="/reply/like/submit/`)
var topic_alt_74 = []byte(`" class="action_button like_item">+1</a>`)
var topic_alt_75 = []byte(`<a href="/reply/edit/submit/`)
var topic_alt_76 = []byte(`" class="action_button edit_item">Edit</a>`)
var topic_alt_77 = []byte(`<a href="/reply/delete/submit/`)
var topic_alt_78 = []byte(`" class="action_button delete_item">Delete</a>`)
var topic_alt_79 = []byte(`
<a href="/report/submit/`)
var topic_alt_79 = []byte(`?session=`)
var topic_alt_80 = []byte(`&type=reply" class="action_button report_item">Report</a>
var topic_alt_80 = []byte(`?session=`)
var topic_alt_81 = []byte(`&type=reply" class="action_button report_item">Report</a>
`)
var topic_alt_81 = []byte(`<a class="action_button action_button_right like_count hide_on_micro">`)
var topic_alt_82 = []byte(` up</a>`)
var topic_alt_83 = []byte(`
<a class="action_button action_button_right created_at hide_on_mobile">`)
var topic_alt_84 = []byte(`</a>
var topic_alt_82 = []byte(`
<div class="action_button_right">
`)
var topic_alt_85 = []byte(`<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">`)
var topic_alt_86 = []byte(`</a>`)
var topic_alt_87 = []byte(`
var topic_alt_83 = []byte(`<a class="action_button like_count hide_on_micro">`)
var topic_alt_84 = []byte(`</a>`)
var topic_alt_85 = []byte(`
<a class="action_button created_at hide_on_mobile">`)
var topic_alt_86 = []byte(`</a>
`)
var topic_alt_87 = []byte(`<a href="#" title="IP Address" class="action_button ip_item hide_on_mobile">`)
var topic_alt_88 = []byte(`</a>`)
var topic_alt_89 = []byte(`
</div>
</div>
`)
var topic_alt_88 = []byte(`
var topic_alt_90 = []byte(`
</div>
<div style="clear:both;"></div>
</article>
`)
var topic_alt_89 = []byte(`</div>
var topic_alt_91 = []byte(`</div>
`)
var topic_alt_90 = []byte(`
var topic_alt_92 = []byte(`
<div class="rowblock topic_reply_container">
<div class="userinfo" aria-label="The information on the poster">
<div class="avatar_item" style="background-image: url(`)
var topic_alt_93 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<a href="`)
var topic_alt_94 = []byte(`" class="the_name" rel="author">`)
var topic_alt_95 = []byte(`</a>
`)
var topic_alt_96 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">`)
var topic_alt_97 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_98 = []byte(`<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level `)
var topic_alt_99 = []byte(`</div><div class="tag_post"></div></div>`)
var topic_alt_100 = []byte(`
</div>
<div class="rowblock topic_reply_form quick_create_form">
<form id="reply_form" enctype="multipart/form-data" action="/reply/create/" method="post"></form>
<input form="reply_form" name="tid" value='`)
var topic_alt_91 = []byte(`' type="hidden" />
var topic_alt_101 = []byte(`' type="hidden" />
<div class="formrow real_first_child">
<div class="formitem">
<textarea id="input_content" form="reply_form" name="reply-content" placeholder="Insert reply here" required></textarea>
<textarea id="input_content" form="reply_form" name="reply-content" placeholder="What do you think?" required></textarea>
</div>
</div>
<div class="formrow quick_button_row">
<div class="formitem">
<button form="reply_form" name="reply-button" class="formbutton">Create Reply</button>
`)
var topic_alt_92 = []byte(`
var topic_alt_102 = []byte(`
<input name="upload_files" form="reply_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`)
var topic_alt_93 = []byte(`
var topic_alt_103 = []byte(`
</div>
</div>
</div>
</div>
`)
var topic_alt_94 = []byte(`
var topic_alt_104 = []byte(`
</main>

View File

@ -3,8 +3,8 @@
// Code generated by Gosora. More below:
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "net/http"
import "strconv"
import "net/http"
// nolint
func init() {
@ -182,108 +182,126 @@ w.Write(topic_alt_45)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(topic_alt_46)
}
if tmpl_topic_alt_vars.Topic.LikeCount > 0 {
w.Write(topic_alt_47)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount)))
if tmpl_topic_alt_vars.Topic.LikeCount > 0 {
w.Write(topic_alt_48)
}
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.LikeCount)))
w.Write(topic_alt_49)
w.Write([]byte(tmpl_topic_alt_vars.Topic.CreatedAt))
w.Write(topic_alt_50)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_51)
w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress))
w.Write(topic_alt_52)
}
w.Write(topic_alt_50)
w.Write([]byte(tmpl_topic_alt_vars.Topic.RelativeCreatedAt))
w.Write(topic_alt_51)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_52)
w.Write([]byte(tmpl_topic_alt_vars.Topic.IPAddress))
w.Write(topic_alt_53)
}
w.Write(topic_alt_54)
if len(tmpl_topic_alt_vars.ItemList) != 0 {
for _, item := range tmpl_topic_alt_vars.ItemList {
w.Write(topic_alt_54)
if item.ActionType != "" {
w.Write(topic_alt_55)
}
if item.ActionType != "" {
w.Write(topic_alt_56)
w.Write([]byte(item.Avatar))
}
w.Write(topic_alt_57)
w.Write([]byte(item.UserLink))
w.Write([]byte(item.Avatar))
w.Write(topic_alt_58)
w.Write([]byte(item.CreatedByName))
w.Write([]byte(item.UserLink))
w.Write(topic_alt_59)
if item.Tag != "" {
w.Write([]byte(item.CreatedByName))
w.Write(topic_alt_60)
w.Write([]byte(item.Tag))
if item.Tag != "" {
w.Write(topic_alt_61)
} else {
w.Write([]byte(item.Tag))
w.Write(topic_alt_62)
w.Write([]byte(strconv.Itoa(item.Level)))
w.Write(topic_alt_63)
}
w.Write(topic_alt_64)
if item.ActionType != "" {
w.Write(topic_alt_65)
}
w.Write(topic_alt_66)
if item.ActionType != "" {
w.Write(topic_alt_67)
w.Write([]byte(item.ActionIcon))
w.Write(topic_alt_68)
w.Write([]byte(item.ActionType))
w.Write(topic_alt_69)
} else {
w.Write(topic_alt_63)
w.Write([]byte(strconv.Itoa(item.Level)))
w.Write(topic_alt_64)
}
w.Write(topic_alt_65)
if item.ActionType != "" {
w.Write(topic_alt_66)
}
w.Write(topic_alt_67)
if item.ActionType != "" {
w.Write(topic_alt_68)
w.Write([]byte(item.ActionIcon))
w.Write(topic_alt_69)
w.Write([]byte(item.ActionType))
w.Write(topic_alt_70)
w.Write([]byte(item.ContentHtml))
} else {
w.Write(topic_alt_71)
w.Write([]byte(item.ContentHtml))
w.Write(topic_alt_72)
if tmpl_topic_alt_vars.CurrentUser.Loggedin {
if tmpl_topic_alt_vars.CurrentUser.Perms.LikeItem {
w.Write(topic_alt_72)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_73)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_74)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.EditReply {
w.Write(topic_alt_74)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_75)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_76)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.DeleteReply {
w.Write(topic_alt_76)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_77)
}
w.Write(topic_alt_78)
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_78)
}
w.Write(topic_alt_79)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write([]byte(strconv.Itoa(item.ID)))
w.Write(topic_alt_80)
}
if item.LikeCount > 0 {
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(topic_alt_81)
w.Write([]byte(strconv.Itoa(item.LikeCount)))
}
w.Write(topic_alt_82)
}
if item.LikeCount > 0 {
w.Write(topic_alt_83)
w.Write([]byte(item.CreatedAt))
w.Write([]byte(strconv.Itoa(item.LikeCount)))
w.Write(topic_alt_84)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
}
w.Write(topic_alt_85)
w.Write([]byte(item.IPAddress))
w.Write([]byte(item.RelativeCreatedAt))
w.Write(topic_alt_86)
}
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_87)
}
w.Write([]byte(item.IPAddress))
w.Write(topic_alt_88)
}
}
w.Write(topic_alt_89)
if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply {
}
w.Write(topic_alt_90)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
}
}
w.Write(topic_alt_91)
if tmpl_topic_alt_vars.CurrentUser.Perms.UploadFiles {
if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply {
w.Write(topic_alt_92)
}
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Avatar))
w.Write(topic_alt_93)
}
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Link))
w.Write(topic_alt_94)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Name))
w.Write(topic_alt_95)
if tmpl_topic_alt_vars.CurrentUser.Tag != "" {
w.Write(topic_alt_96)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Tag))
w.Write(topic_alt_97)
} else {
w.Write(topic_alt_98)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.CurrentUser.Level)))
w.Write(topic_alt_99)
}
w.Write(topic_alt_100)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_101)
if tmpl_topic_alt_vars.CurrentUser.Perms.UploadFiles {
w.Write(topic_alt_102)
}
w.Write(topic_alt_103)
}
w.Write(topic_alt_104)
w.Write(footer_0)
if len(tmpl_topic_alt_vars.Header.Themes) != 0 {
for _, item := range tmpl_topic_alt_vars.Header.Themes {

View File

@ -34,7 +34,7 @@
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea>
<div class="button_container">
{{if .CurrentUser.Loggedin}}
{{if .CurrentUser.Perms.LikeItem}}<a href="/topic/like/submit/{{.Topic.ID}}" class="action_button">+1</a>{{end}}
{{if .CurrentUser.Perms.LikeItem}}<a href="/topic/like/submit/{{.Topic.ID}}" class="action_button like_item">+1</a>{{end}}
{{if .CurrentUser.Perms.EditTopic}}<a href="/topic/edit/{{.Topic.ID}}" class="action_button open_edit">Edit</a>{{end}}
{{if .CurrentUser.Perms.DeleteTopic}}<a href="/topic/delete/submit/{{.Topic.ID}}" class="action_button delete_item">Delete</a>{{end}}
{{if .CurrentUser.Perms.CloseTopic}}
@ -43,9 +43,11 @@
{{if .Topic.Sticky}}<a href='/topic/unstick/submit/{{.Topic.ID}}' class="action_button">Unpin</a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="action_button">Pin</a>{{end}}{{end}}
<a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="action_button report_item">Report</a>
{{end}}
{{if .Topic.LikeCount}}<a class="action_button action_button_right like_count hide_on_micro">{{.Topic.LikeCount}} up</a>{{end}}
<a class="action_button action_button_right created_at hide_on_mobile">{{.Topic.CreatedAt}}</a>
{{if .CurrentUser.Perms.ViewIPs}}<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">{{.Topic.IPAddress}}</a>{{end}}
<div class="action_button_right">
{{if .Topic.LikeCount}}<a class="action_button like_count hide_on_micro">{{.Topic.LikeCount}}</a>{{end}}
<a class="action_button created_at hide_on_mobile">{{.Topic.RelativeCreatedAt}}</a>
{{if .CurrentUser.Perms.ViewIPs}}<a href="#" title="IP Address" class="action_button ip_item hide_on_mobile">{{.Topic.IPAddress}}</a>{{end}}
</div>
</div>
</div><div style="clear:both;"></div>
</article>
@ -66,14 +68,16 @@
<div class="editable_block user_content" itemprop="text">{{.ContentHtml}}</div>
<div class="button_container">
{{if $.CurrentUser.Loggedin}}
{{if $.CurrentUser.Perms.LikeItem}}<a href="/reply/like/submit/{{.ID}}" class="action_button">+1</a>{{end}}
{{if $.CurrentUser.Perms.LikeItem}}<a href="/reply/like/submit/{{.ID}}" class="action_button like_item">+1</a>{{end}}
{{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="action_button edit_item">Edit</a>{{end}}
{{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="action_button delete_item">Delete</a>{{end}}
<a href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="action_button report_item">Report</a>
{{end}}
{{if .LikeCount}}<a class="action_button action_button_right like_count hide_on_micro">{{.LikeCount}} up</a>{{end}}
<a class="action_button action_button_right created_at hide_on_mobile">{{.CreatedAt}}</a>
{{if $.CurrentUser.Perms.ViewIPs}}<a href="#" title="IP Address" class="action_button action_button_right ip_item hide_on_mobile">{{.IPAddress}}</a>{{end}}
<div class="action_button_right">
{{if .LikeCount}}<a class="action_button like_count hide_on_micro">{{.LikeCount}}</a>{{end}}
<a class="action_button created_at hide_on_mobile">{{.RelativeCreatedAt}}</a>
{{if $.CurrentUser.Perms.ViewIPs}}<a href="#" title="IP Address" class="action_button ip_item hide_on_mobile">{{.IPAddress}}</a>{{end}}
</div>
</div>
{{end}}
</div>
@ -82,12 +86,18 @@
{{end}}</div>
{{if .CurrentUser.Perms.CreateReply}}
<div class="rowblock topic_reply_container">
<div class="userinfo" aria-label="The information on the poster">
<div class="avatar_item" style="background-image: url({{.CurrentUser.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div>
<a href="{{.CurrentUser.Link}}" class="the_name" rel="author">{{.CurrentUser.Name}}</a>
{{if .CurrentUser.Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.CurrentUser.Tag}}</div><div class="tag_post"></div></div>{{else}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag post_level">Level {{.CurrentUser.Level}}</div><div class="tag_post"></div></div>{{end}}
</div>
<div class="rowblock topic_reply_form quick_create_form">
<form id="reply_form" enctype="multipart/form-data" action="/reply/create/" method="post"></form>
<input form="reply_form" name="tid" value='{{.Topic.ID}}' type="hidden" />
<div class="formrow real_first_child">
<div class="formitem">
<textarea id="input_content" form="reply_form" name="reply-content" placeholder="Insert reply here" required></textarea>
<textarea id="input_content" form="reply_form" name="reply-content" placeholder="What do you think?" required></textarea>
</div>
</div>
<div class="formrow quick_button_row">
@ -100,6 +110,7 @@
</div>
</div>
</div>
</div>
{{end}}
</main>

View File

@ -8,8 +8,10 @@
--primary-link-color: hsl(0,0%,40%);
--primary-text-color: hsl(0,0%,20%);
--lightened-primary-text-color: hsl(0,0%,30%);
--extra-lightened-primary-text-color: hsl(0,0%,40%);
--inverse-primary-text-color: white;
--light-text-color: hsl(0,0%,55%);
--lighter-text-color: hsl(0,0%,65%);
}
* {
@ -402,6 +404,25 @@ select, input, textarea {
color: hsl(0,0%,30%);
}
.topic_reply_container {
display: flex;
border: 0;
}
.topic_reply_form {
margin: 0px;
width: 100%;
}
.topic_reply_form .trumbowyg-button-pane:after {
display: none;
}
.topic_reply_form .trumbowyg-editor {
border-left: none;
border-right: none;
}
.topic_reply_form .quick_button_row {
margin-bottom: 7px;
}
#prevFloat, #nextFloat {
display: none;
}
@ -600,33 +621,67 @@ select, input, textarea {
}
.userinfo {
margin-right: 16px;
padding: 18px;
display: flex;
flex-direction: column;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 12px;
padding-top: 30px;
padding-left: 42px;
padding-right: 42px;
padding-bottom: 18px;
height: min-content;
/*overflow: hidden;
text-overflow: ellipsis;*/
}
.content_container {
width: 100%;
padding: 16px;
padding: 17px;
display: flex;
flex-direction: column;
}
.avatar_item {
border-radius: 50px;
width: 90px;
height: 90px;
margin-bottom: 5px;
background-size: 128px;
width: 84px;
height: 84px;
margin-bottom: 12px;
background-size: 120px;
}
.the_name, .userinfo .tag_block {
margin-left: auto;
margin-right: auto;
}
.the_name {
font-size: 19px;
font-size: 18px;
color: var(--lightened-primary-text-color);
margin-left: auto;
margin-right: auto;
}
.userinfo .tag_block {
color: var(--extra-lightened-primary-text-color);
}
.button_container {
margin-top: auto;
display: flex;
}
.action_button {
margin-right: 5px;
color: var(--light-text-color);
font-size: 14px;
display: inline-block;
}
.action_button_right {
display: inline-flex;
margin-left: auto;
margin-right: auto;
}
.like_count:after {
content: " likes";
margin-right: 6px;
}
.created_at:before, .ip_item:before {
border-left: 1px solid var(--element-border-color);
content: "";
margin-right: 10px;
margin-top: 1px;
margin-bottom: 1px;
}
.created_at {
margin-right: 10px;
}
@media(max-width: 670px) {

View File

@ -652,6 +652,9 @@ button.username {
padding-right: 7px;
}
.action_button_right {
display: flex;
}
.action_button_right .action_button {
border-left: solid 1px #eaeaea;
border-right: none;
}
@ -660,10 +663,21 @@ button.username {
margin-right: auto;
}
.like_label:before { content: "😀"; }
.edit_label:before { content: "🖊️"; }
.trash_label:before { content: "🗑️"; }
.flag_label:before { content: "🚩"; }
.like_label:before {
content: "😀";
}
.like_count:after {
content: " up";
}
.edit_label:before {
content: "🖊️";
}
.trash_label:before {
content: "🗑️";
}
.flag_label:before {
content: "🚩";
}
.mod_button {
margin-right: 4px;

View File

@ -24,7 +24,8 @@ type Topic struct {
CreatedBy int
IsClosed bool
Sticky bool
CreatedAt string
CreatedAt time.Time
RelativeCreatedAt string
LastReplyAt time.Time
RelativeLastReplyAt string
//LastReplyBy int
@ -45,7 +46,8 @@ type TopicUser struct {
CreatedBy int
IsClosed bool
Sticky bool
CreatedAt string
CreatedAt time.Time
RelativeCreatedAt string
LastReplyAt time.Time
RelativeLastReplyAt string
//LastReplyBy int