More conversations work.

Add support for AS in columns for SimpleInnerJoin.
Add a referrer policy to improve privacy a little.
Shorten /static/ to /s/ since it comes up so much.
Remove some obsolete code.
Shorten some variable names.
Reduce the amount of boilerplate in the patcher.

Added the RefNoTrack and RefNoRef privacy config settings.

You may need to run the updater / patcher for this commit.
This commit is contained in:
Azareal 2019-08-14 20:39:04 +10:00
parent 6ba4b665e3
commit 4d8c97812d
54 changed files with 901 additions and 456 deletions

View File

@ -362,7 +362,7 @@ func createTables(adapter qgen.Adapter) error {
) )
//columns("participants, createdBy, createdAt, lastReplyBy, lastReplyAt").Where("cid = ?") //columns("participants, createdBy, createdAt, lastReplyBy, lastReplyAt").Where("cid = ?")
/*qgen.Install.CreateTable("conversations", "", "", qgen.Install.CreateTable("conversations", "", "",
[]tC{ []tC{
tC{"cid", "int", 0, false, true, ""}, tC{"cid", "int", 0, false, true, ""},
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
@ -393,7 +393,7 @@ func createTables(adapter qgen.Adapter) error {
tC{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
tC{"cid", "int", 0, false, false, ""}, tC{"cid", "int", 0, false, false, ""},
}, nil, }, nil,
)*/ )
qgen.Install.CreateTable("activity_stream_matches", "", "", qgen.Install.CreateTable("activity_stream_matches", "", "",
[]tC{ []tC{

View File

@ -1,8 +1,9 @@
package common package common
import ( import (
"time"
"errors" "errors"
"time"
//"strconv" //"strconv"
"database/sql" "database/sql"
@ -17,62 +18,63 @@ var Convos ConversationStore
var convoStmts ConvoStmts var convoStmts ConvoStmts
type ConvoStmts struct { type ConvoStmts struct {
getPosts *sql.Stmt fetchPost *sql.Stmt
getPosts *sql.Stmt
countPosts *sql.Stmt countPosts *sql.Stmt
edit *sql.Stmt edit *sql.Stmt
create *sql.Stmt create *sql.Stmt
delete *sql.Stmt delete *sql.Stmt
editPost *sql.Stmt editPost *sql.Stmt
createPost *sql.Stmt createPost *sql.Stmt
deletePost *sql.Stmt
} }
/*func init() { func init() {
DbInits.Add(func(acc *qgen.Accumulator) error { DbInits.Add(func(acc *qgen.Accumulator) error {
convoStmts = ConvoStmts{ convoStmts = ConvoStmts{
getPosts: acc.Select("conversations_posts").Columns("pid, body, post, createdBy").Where("cid = ?").Limit("?,?").Prepare(), fetchPost: acc.Select("conversations_posts").Columns("cid, body, post, createdBy").Where("pid = ?").Prepare(),
getPosts: acc.Select("conversations_posts").Columns("pid, body, post, createdBy").Where("cid = ?").Limit("?,?").Prepare(),
countPosts: acc.Count("conversations_posts").Where("cid = ?").Prepare(), countPosts: acc.Count("conversations_posts").Where("cid = ?").Prepare(),
//edit: acc.Update("conversations").Set("participants = ?, lastReplyBy = ?, lastReplyAt = ?").Where("cid = ?").Prepare(), edit: acc.Update("conversations").Set("lastReplyBy = ?, lastReplyAt = ?").Where("cid = ?").Prepare(),
edit: acc.Update("conversations").Set("lastReplyBy = ?, lastReplyAt = ?").Where("cid = ?").Prepare(), create: acc.Insert("conversations").Columns("createdAt, lastReplyAt").Fields("UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
//create: acc.Insert("conversations").Columns("participants, createdAt, lastReplyAt").Fields("?,UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
create: acc.Insert("conversations").Columns("createdAt, lastReplyAt").Fields("UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
editPost: acc.Update("conversations_posts").Set("body = ?, post = ?").Where("cid = ?").Prepare(), editPost: acc.Update("conversations_posts").Set("body = ?, post = ?").Where("cid = ?").Prepare(),
createPost: acc.Insert("conversations_posts").Columns("cid, body, post, createdBy").Fields("?,?,?,?").Prepare(), createPost: acc.Insert("conversations_posts").Columns("cid, body, post, createdBy").Fields("?,?,?,?").Prepare(),
deletePost: acc.Delete("conversations_posts").Where("pid = ?").Prepare(),
} }
return acc.FirstError() return acc.FirstError()
}) })
}*/ }
type Conversation struct { type Conversation struct {
ID int ID int
//Participants string CreatedBy int
CreatedBy int CreatedAt time.Time
CreatedAt time.Time
LastReplyBy int LastReplyBy int
LastReplyAt time.Time LastReplyAt time.Time
} }
func (co *Conversation) Posts(offset int) (posts []*ConversationPost, err error) { func (co *Conversation) Posts(offset, itemsPerPage int) (posts []*ConversationPost, err error) {
rows, err := convoStmts.getPosts.Query(co.ID, offset, Config.ItemsPerPage) rows, err := convoStmts.getPosts.Query(co.ID, offset, itemsPerPage)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
convo := &ConversationPost{CID: co.ID} p := &ConversationPost{CID: co.ID}
err := rows.Scan(&convo.ID, &convo.Body, &convo.Post, &convo.CreatedBy) err := rows.Scan(&p.ID, &p.Body, &p.Post, &p.CreatedBy)
if err != nil { if err != nil {
return nil, err return nil, err
} }
convo, err = ConvoPostProcess.OnLoad(convo) p, err = ConvoPostProcess.OnLoad(p)
if err != nil { if err != nil {
return nil, err return nil, err
} }
posts = append(posts, convo) posts = append(posts, p)
} }
return posts, rows.Err() return posts, rows.Err()
} }
@ -85,12 +87,12 @@ func (co *Conversation) PostsCount() (count int) {
} }
func (co *Conversation) Update() error { func (co *Conversation) Update() error {
_, err := convoStmts.edit.Exec(/*co.Participants, */co.CreatedAt, co.LastReplyBy, co.LastReplyAt, co.ID) _, err := convoStmts.edit.Exec(co.CreatedAt, co.LastReplyBy, co.LastReplyAt, co.ID)
return err return err
} }
func (co *Conversation) Create() (int, error) { func (co *Conversation) Create() (int, error) {
res, err := convoStmts.create.Exec(/*co.Participants*/) res, err := convoStmts.create.Exec()
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -109,39 +111,34 @@ type ConversationStore interface {
} }
type DefaultConversationStore struct { type DefaultConversationStore struct {
get *sql.Stmt get *sql.Stmt
getUser *sql.Stmt getUser *sql.Stmt
getUserCount *sql.Stmt getUserCount *sql.Stmt
delete *sql.Stmt delete *sql.Stmt
deletePosts *sql.Stmt deletePosts *sql.Stmt
create *sql.Stmt deleteParticipants *sql.Stmt
create *sql.Stmt
addParticipant *sql.Stmt addParticipant *sql.Stmt
count *sql.Stmt count *sql.Stmt
} }
func NewDefaultConversationStore(acc *qgen.Accumulator) (*DefaultConversationStore, error) { func NewDefaultConversationStore(acc *qgen.Accumulator) (*DefaultConversationStore, error) {
return &DefaultConversationStore{ return &DefaultConversationStore{
//get: acc.Select("conversations").Columns("participants, createdBy, createdAt, lastReplyBy, lastReplyAt").Where("cid = ?").Prepare(), get: acc.Select("conversations").Columns("createdBy, createdAt, lastReplyBy, lastReplyAt").Where("cid = ?").Prepare(),
get: acc.Select("conversations").Columns("createdBy, createdAt, lastReplyBy, lastReplyAt").Where("cid = ?").Prepare(), getUser: acc.SimpleInnerJoin("conversations_participants AS cp", "conversations AS c", "cp.cid, c.createdBy, c.createdAt, c.lastReplyBy, c.lastReplyAt", "cp.cid = c.cid", "cp.uid = ?", "c.lastReplyAt DESC, c.createdAt DESC, c.cid DESC", "?,?"),
getUserCount: acc.Count("conversations_participants").Where("uid = ?").Prepare(),
//("replies", "users", "replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress, replies.likeCount, replies.attachCount, replies.actionType", "replies.createdBy = users.uid", "replies.tid = ?", "replies.rid ASC", "?,?") delete: acc.Delete("conversations").Where("cid = ?").Prepare(),
//(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) deletePosts: acc.Delete("conversations_posts").Where("cid = ?").Prepare(),
deleteParticipants: acc.Delete("conversations_participants").Where("cid = ?").Prepare(),
//getUser: acc.SimpleLeftJoin("conversations_participants AS cp","conversations AS c","c.cid, c.participants, c.createdBy, c.createdAt, c.lastReplyBy, c.lastReplyAt","cp.cid = c.cid","cp.uid = ?","c.lastReplyAt DESC, c.createdAt DESC, c.cid DESC","?,?"), create: acc.Insert("conversations").Columns("createdBy, createdAt, lastReplyAt").Fields("?,UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
getUser: acc.SimpleLeftJoin("conversations_participants AS cp","conversations AS c","c.cid, c.createdBy, c.createdAt, c.lastReplyBy, c.lastReplyAt","cp.cid = c.cid","cp.uid = ?","c.lastReplyAt DESC, c.createdAt DESC, c.cid DESC","?,?"),
getUserCount: acc.Count("conversations_participants").Where("uid = ?").Prepare(),
delete: acc.Delete("conversations").Where("cid = ?").Prepare(),
deletePosts: acc.Delete("conversations_posts").Where("cid = ?").Prepare(),
//create: acc.Insert("conversations").Columns("participants, createdBy, createdAt, lastReplyAt").Fields("?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
create: acc.Insert("conversations").Columns("createdBy, createdAt, lastReplyAt").Fields("?,UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
addParticipant: acc.Insert("conversations_participants").Columns("uid, cid").Fields("?,?").Prepare(), addParticipant: acc.Insert("conversations_participants").Columns("uid, cid").Fields("?,?").Prepare(),
count: acc.Count("conversations").Prepare(), count: acc.Count("conversations").Prepare(),
}, acc.FirstError() }, acc.FirstError()
} }
func (s *DefaultConversationStore) Get(id int) (*Conversation, error) { func (s *DefaultConversationStore) Get(id int) (*Conversation, error) {
convo := &Conversation{ID: id} convo := &Conversation{ID: id}
err := s.get.QueryRow(id).Scan(/*&convo.Participants, */&convo.CreatedBy, &convo.CreatedAt, &convo.LastReplyBy, &convo.LastReplyAt) err := s.get.QueryRow(id).Scan(&convo.CreatedBy, &convo.CreatedAt, &convo.LastReplyBy, &convo.LastReplyAt)
return convo, err return convo, err
} }
@ -154,7 +151,7 @@ func (s *DefaultConversationStore) GetUser(uid int, offset int) (cos []*Conversa
for rows.Next() { for rows.Next() {
co := &Conversation{} co := &Conversation{}
err := rows.Scan(&co.ID, /*&co.Participants,*/ &co.CreatedBy, &co.CreatedAt, &co.LastReplyBy, &co.LastReplyAt) err := rows.Scan(&co.ID, &co.CreatedBy, &co.CreatedAt, &co.LastReplyBy, &co.LastReplyAt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -179,6 +176,10 @@ func (s *DefaultConversationStore) Delete(id int) error {
return err return err
} }
_, err = s.deletePosts.Exec(id) _, err = s.deletePosts.Exec(id)
if err != nil {
return err
}
_, err = s.deleteParticipants.Exec(id)
return err return err
} }
@ -186,13 +187,7 @@ func (s *DefaultConversationStore) Create(content string, createdBy int, partici
if len(participants) == 0 { if len(participants) == 0 {
return 0, errors.New("no participants set") return 0, errors.New("no participants set")
} }
/*var pstr string res, err := s.create.Exec(createdBy)
for _, parti := range participants {
pstr += strconv.Itoa(parti) + ","
}
pstr = pstr[:len(pstr)-1]*/
res, err := s.create.Exec(createdBy/*, pstr*/)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -211,12 +206,12 @@ func (s *DefaultConversationStore) Create(content string, createdBy int, partici
} }
for _, p := range participants { for _, p := range participants {
_, err := s.addParticipant.Exec(p,lastID) _, err := s.addParticipant.Exec(p, lastID)
if err != nil { if err != nil {
return 0, err return 0, err
} }
} }
_, err = s.addParticipant.Exec(createdBy,lastID) _, err = s.addParticipant.Exec(createdBy, lastID)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -231,4 +226,4 @@ func (s *DefaultConversationStore) Count() (count int) {
LogError(err) LogError(err)
} }
return count return count
} }

View File

@ -1,11 +1,11 @@
package common package common
import ( import (
"io"
"encoding/hex"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"encoding/hex"
"io"
) )
var ConvoPostProcess ConvoPostProcessor = NewDefaultConvoPostProcessor() var ConvoPostProcess ConvoPostProcessor = NewDefaultConvoPostProcessor()
@ -99,13 +99,17 @@ func (pr *AesConvoPostProcessor) OnSave(co *ConversationPost) (*ConversationPost
} }
type ConversationPost struct { type ConversationPost struct {
ID int ID int
CID int CID int
Body string Body string
Post string // aes, '' Post string // aes, ''
CreatedBy int CreatedBy int
} }
func (co *ConversationPost) Fetch() error {
return convoStmts.fetchPost.QueryRow(co.ID).Scan(&co.CID, &co.Body, &co.Post, &co.CreatedBy)
}
func (co *ConversationPost) Update() error { func (co *ConversationPost) Update() error {
lco, err := ConvoPostProcess.OnSave(co) lco, err := ConvoPostProcess.OnSave(co)
if err != nil { if err != nil {
@ -122,11 +126,16 @@ func (co *ConversationPost) Create() (int, error) {
return 0, err return 0, err
} }
//GetHookTable().VhookNoRet("convo_post_create", lco) //GetHookTable().VhookNoRet("convo_post_create", lco)
res, err := convoStmts.createPost.Exec(lco.CID, lco.Body, lco.Post) res, err := convoStmts.createPost.Exec(lco.CID, lco.Body, lco.Post, lco.CreatedBy)
if err != nil { if err != nil {
return 0, err return 0, err
} }
lastID, err := res.LastInsertId() lastID, err := res.LastInsertId()
return int(lastID), err return int(lastID), err
} }
func (co *ConversationPost) Delete() error {
_, err := convoStmts.deletePost.Exec(co.ID)
return err
}

View File

@ -240,7 +240,7 @@ func (list SFileList) JSTmplInit() error {
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) checksum := hex.EncodeToString(hasher.Sum(nil))
list.Set("/static/"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}) list.Set("/s/"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLogf("Added the '%s' static file.", path) DebugLogf("Added the '%s' static file.", path)
return nil return nil
@ -285,7 +285,7 @@ func (list SFileList) Init() error {
} }
} }
list.Set("/static/"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mimetype, f, f.ModTime().UTC().Format(http.TimeFormat)}) list.Set("/s/"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mimetype, f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLogf("Added the '%s' static file.", path) DebugLogf("Added the '%s' static file.", path)
return nil return nil
@ -318,7 +318,7 @@ func (list SFileList) Add(path string, prefix string) error {
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) checksum := hex.EncodeToString(hasher.Sum(nil))
list.Set("/static"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}) list.Set("/s"+path, SFile{data, gzipData, checksum,path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLogf("Added the '%s' static file", path) DebugLogf("Added the '%s' static file", path)
return nil return nil

View File

@ -7,7 +7,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/Azareal/Gosora/common/phrases" p "github.com/Azareal/Gosora/common/phrases"
) )
/*type HResource struct { /*type HResource struct {
@ -15,7 +15,7 @@ import (
Hash string Hash string
}*/ }*/
// TODO: Allow resources in spots other than /static/ and possibly even external domains (e.g. CDNs) // TODO: Allow resources in spots other than /s/ and possibly even external domains (e.g. CDNs)
// TODO: Preload Trumboyg on Cosora on the forum list // TODO: Preload Trumboyg on Cosora on the forum list
type Header struct { type Header struct {
Title string Title string
@ -50,9 +50,9 @@ type Header struct {
ExtData ExtData ExtData ExtData
} }
func (header *Header) AddScript(name string) { func (h *Header) AddScript(name string) {
// TODO: Use a secondary static file map to avoid this concatenation? // TODO: Use a secondary static file map to avoid this concatenation?
fname := "/static/" + name fname := "/s/" + name
var oname string var oname string
if fname[0] == '/' && fname[1] != '/' { if fname[0] == '/' && fname[1] != '/' {
file, ok := StaticFiles.Get(fname) file, ok := StaticFiles.Get(fname)
@ -64,11 +64,11 @@ func (header *Header) AddScript(name string) {
oname = name oname = name
} }
//log.Print("oname:", oname) //log.Print("oname:", oname)
header.Scripts = append(header.Scripts, oname) h.Scripts = append(h.Scripts, oname)
} }
func (header *Header) AddPreScriptAsync(name string) { func (h *Header) AddPreScriptAsync(name string) {
fname := "/static/" + name fname := "/s/" + name
var oname string var oname string
if fname[0] == '/' && fname[1] != '/' { if fname[0] == '/' && fname[1] != '/' {
file, ok := StaticFiles.Get(fname) file, ok := StaticFiles.Get(fname)
@ -79,11 +79,11 @@ func (header *Header) AddPreScriptAsync(name string) {
if oname == "" { if oname == "" {
oname = name oname = name
} }
header.PreScriptsAsync = append(header.PreScriptsAsync, oname) h.PreScriptsAsync = append(h.PreScriptsAsync, oname)
} }
func (header *Header) AddScriptAsync(name string) { func (h *Header) AddScriptAsync(name string) {
fname := "/static/" + name fname := "/s/" + name
var oname string var oname string
if fname[0] == '/' && fname[1] != '/' { if fname[0] == '/' && fname[1] != '/' {
file, ok := StaticFiles.Get(fname) file, ok := StaticFiles.Get(fname)
@ -94,15 +94,15 @@ func (header *Header) AddScriptAsync(name string) {
if oname == "" { if oname == "" {
oname = name oname = name
} }
header.ScriptsAsync = append(header.ScriptsAsync, oname) h.ScriptsAsync = append(h.ScriptsAsync, oname)
} }
/*func (header *Header) Preload(name string) { /*func (h *Header) Preload(name string) {
header.Preload = append(header.Preload, name) h.Preload = append(h.Preload, name)
}*/ }*/
func (header *Header) AddSheet(name string) { func (h *Header) AddSheet(name string) {
fname := "/static/" + name fname := "/s/" + name
var oname string var oname string
if fname[0] == '/' && fname[1] != '/' { if fname[0] == '/' && fname[1] != '/' {
file, ok := StaticFiles.Get(fname) file, ok := StaticFiles.Get(fname)
@ -113,11 +113,11 @@ func (header *Header) AddSheet(name string) {
if oname == "" { if oname == "" {
oname = name oname = name
} }
header.Stylesheets = append(header.Stylesheets, oname) h.Stylesheets = append(h.Stylesheets, oname)
} }
func (header *Header) AddNotice(name string) { func (h *Header) AddNotice(name string) {
header.NoticeList = append(header.NoticeList, phrases.GetNoticePhrase(name)) h.NoticeList = append(h.NoticeList, p.GetNoticePhrase(name))
} }
// TODO: Add this to routes which don't use templates. E.g. Json APIs. // TODO: Add this to routes which don't use templates. E.g. Json APIs.
@ -289,9 +289,18 @@ type ConvoListPage struct {
Paginator Paginator
} }
type ConvoViewRow struct {
*ConversationPost
User *User
ClassName string
ContentLines string
}
type ConvoViewPage struct { type ConvoViewPage struct {
*Header *Header
Posts []*ConversationPost Convo *Conversation
Posts []ConvoViewRow
CanModify bool
Paginator Paginator
} }

View File

@ -104,6 +104,9 @@ type config struct {
DisableNoavatarRange bool DisableNoavatarRange bool
DisableDefaultNoavatar bool DisableDefaultNoavatar bool
RefNoTrack bool
RefNoRef bool
Noavatar string // ? - Move this into the settings table? Noavatar string // ? - Move this into the settings table?
ItemsPerPage int // ? - Move this into the settings table? ItemsPerPage int // ? - Move this into the settings table?
MaxTopicTitleLength int MaxTopicTitleLength int

View File

@ -167,7 +167,7 @@ func (theme *Theme) AddThemeStaticFiles() error {
hasher.Write(data) hasher.Write(data)
checksum := hex.EncodeToString(hasher.Sum(nil)) checksum := hex.EncodeToString(hasher.Sum(nil))
StaticFiles.Set("/static/"+theme.Name+path, SFile{data, gzipData, checksum,theme.Name+path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)}) StaticFiles.Set("/s/"+theme.Name+path, SFile{data, gzipData, checksum,theme.Name+path + "?h=" + checksum, 0, int64(len(data)), int64(len(gzipData)),strconv.Itoa(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
DebugLog("Added the '/" + theme.Name + path + "' static file for theme " + theme.Name + ".") DebugLog("Added the '/" + theme.Name + path + "' static file for theme " + theme.Name + ".")
return nil return nil

View File

@ -472,7 +472,7 @@ func buildNoavatar(uid int, width int) string {
} }
} }
if !Config.DisableDefaultNoavatar && uid < 5 { if !Config.DisableDefaultNoavatar && uid < 5 {
return "/static/n"+strconv.Itoa(uid)+"-"+strconv.Itoa(width)+".png?i=0" return "/s/n"+strconv.Itoa(uid)+"-"+strconv.Itoa(width)+".png?i=0"
} }
return strings.Replace(strings.Replace(Config.Noavatar, "{id}", strconv.Itoa(uid), 1), "{width}", strconv.Itoa(width), 1) return strings.Replace(strings.Replace(Config.Noavatar, "{id}", strconv.Itoa(uid), 1), "{width}", strconv.Itoa(width), 1)
} }

View File

@ -104,6 +104,10 @@ DisableNoavatarRange - This switch lets you disable the noavatar algorithm which
DisableDefaultNoavatar - This switch lets you disable the default noavatar algorithm which may intercept noavatars for increased efficiency. Default: false DisableDefaultNoavatar - This switch lets you disable the default noavatar algorithm which may intercept noavatars for increased efficiency. Default: false
RefNoTrack - This switch disables tracking the referrers of users who click from another site to your site and the referrers of any requests to resources from other sites as-well.
RefNoRef - This switch makes it so that if a user clicks on a link, then the incoming site won't know which site they're coming from.
NoAvatar - The default avatar to use for users when they don't have their own. The default for this may change in the near future to better utilise HTTP/2. Example: https://api.adorable.io/avatars/{width}/{id}.png NoAvatar - The default avatar to use for users when they don't have their own. The default for this may change in the near future to better utilise HTTP/2. Example: https://api.adorable.io/avatars/{width}/{id}.png
ItemsPerPage - The number of posts, topics, etc. you want on each page. ItemsPerPage - The number of posts, topics, etc. you want on each page.

View File

@ -1,9 +1,9 @@
<div class="rowitem passive deletable_block editable_parent post_item" style="background-color: #eaeaea;padding-top: 3px;padding-left: 4px;clear: both;border-bottom: solid 1px #ccc;padding-right: 3px;padding-bottom: 6px;"> <div class="rowitem passive deletable_block editable_parent post_item" style="background-color: #eaeaea;padding-top: 3px;padding-left: 4px;clear: both;border-bottom: solid 1px #ccc;padding-right: 3px;padding-bottom: 6px;">
<div class="userinfo" style="background: white;width: 132px;padding: 2px;margin-top: 2px;float: left;"> <div class="userinfo" style="background: white;width: 132px;padding: 2px;margin-top: 2px;float: left;">
<div class="avatar_item" style="background-image: url(/uploads/avatar_1.jpg), url(/static/white-dot.jpg);background-position: 0px -10px;background-repeat: no-repeat, repeat-y;background-size: 128px;width: 128px;height: 100%;min-height: 128px;border-style: solid;border-color: #eaeaea;border-width: 1px;">&nbsp;</div> <div class="avatar_item" style="background-image: url(/uploads/avatar_1.jpg), url(/s/white-dot.jpg);background-position:0px -10px;background-repeat:no-repeat, repeat-y;background-size:128px;width:128px;height:100%;min-height: 128px;border-style:solid;border-color:#eaeaea;border-width:1px;">&nbsp;</div>
<div class="the_name" style="margin-top: 3px;text-align: center;color: #505050;">Azareal</div> <div class="the_name" style="margin-top: 3px;text-align: center;color: #505050;">Azareal</div>
</div> </div>
<div class="content_container" style="background: white;margin-left: 137px;min-height: 128px;margin-bottom: 0;margin-right: 3px;"> <div class="content_container" style="background:white;margin-left:137px;min-height:128px;margin-bottom:0;margin-right:3px;">
<div class="editable_block user_content" style="padding: 5px;margin-top: 3px;margin-bottom: 0;background: white;min-height: 133px;padding-bottom: 0;width: 100%;">boo</div> <div class="editable_block user_content" style="padding: 5px;margin-top: 3px;margin-bottom: 0;background: white;min-height: 133px;padding-bottom: 0;width: 100%;">boo</div>
<div class="button_container" style="border-top: solid 1px #eaeaea;border-spacing: 0px;border-collapse: collapse;padding: 0;margin: 0;display: block;"> <div class="button_container" style="border-top: solid 1px #eaeaea;border-spacing: 0px;border-collapse: collapse;padding: 0;margin: 0;display: block;">
<a style="border-right: solid 1px #eaeaea;color: #505050;font-size: 13px;padding-left: 5px;padding-right: 5px;">Edit</a> <a style="border-right: solid 1px #eaeaea;color: #505050;font-size: 13px;padding-left: 5px;padding-right: 5px;">Edit</a>

View File

@ -125,6 +125,14 @@ var RouteMap = map[string]interface{}{
"routes.AccountEditEmailTokenSubmit": routes.AccountEditEmailTokenSubmit, "routes.AccountEditEmailTokenSubmit": routes.AccountEditEmailTokenSubmit,
"routes.AccountLogins": routes.AccountLogins, "routes.AccountLogins": routes.AccountLogins,
"routes.LevelList": routes.LevelList, "routes.LevelList": routes.LevelList,
"routes.Convos": routes.Convos,
"routes.ConvosCreate": routes.ConvosCreate,
"routes.Convo": routes.Convo,
"routes.ConvosCreateSubmit": routes.ConvosCreateSubmit,
"routes.ConvosDeleteSubmit": routes.ConvosDeleteSubmit,
"routes.ConvosCreateReplySubmit": routes.ConvosCreateReplySubmit,
"routes.ConvosDeleteReplySubmit": routes.ConvosDeleteReplySubmit,
"routes.ConvosEditReplySubmit": routes.ConvosEditReplySubmit,
"routes.ViewProfile": routes.ViewProfile, "routes.ViewProfile": routes.ViewProfile,
"routes.BanUserSubmit": routes.BanUserSubmit, "routes.BanUserSubmit": routes.BanUserSubmit,
"routes.UnbanUser": routes.UnbanUser, "routes.UnbanUser": routes.UnbanUser,
@ -278,53 +286,61 @@ var routeMapEnum = map[string]int{
"routes.AccountEditEmailTokenSubmit": 99, "routes.AccountEditEmailTokenSubmit": 99,
"routes.AccountLogins": 100, "routes.AccountLogins": 100,
"routes.LevelList": 101, "routes.LevelList": 101,
"routes.ViewProfile": 102, "routes.Convos": 102,
"routes.BanUserSubmit": 103, "routes.ConvosCreate": 103,
"routes.UnbanUser": 104, "routes.Convo": 104,
"routes.ActivateUser": 105, "routes.ConvosCreateSubmit": 105,
"routes.IPSearch": 106, "routes.ConvosDeleteSubmit": 106,
"routes.CreateTopicSubmit": 107, "routes.ConvosCreateReplySubmit": 107,
"routes.EditTopicSubmit": 108, "routes.ConvosDeleteReplySubmit": 108,
"routes.DeleteTopicSubmit": 109, "routes.ConvosEditReplySubmit": 109,
"routes.StickTopicSubmit": 110, "routes.ViewProfile": 110,
"routes.UnstickTopicSubmit": 111, "routes.BanUserSubmit": 111,
"routes.LockTopicSubmit": 112, "routes.UnbanUser": 112,
"routes.UnlockTopicSubmit": 113, "routes.ActivateUser": 113,
"routes.MoveTopicSubmit": 114, "routes.IPSearch": 114,
"routes.LikeTopicSubmit": 115, "routes.CreateTopicSubmit": 115,
"routes.AddAttachToTopicSubmit": 116, "routes.EditTopicSubmit": 116,
"routes.RemoveAttachFromTopicSubmit": 117, "routes.DeleteTopicSubmit": 117,
"routes.ViewTopic": 118, "routes.StickTopicSubmit": 118,
"routes.CreateReplySubmit": 119, "routes.UnstickTopicSubmit": 119,
"routes.ReplyEditSubmit": 120, "routes.LockTopicSubmit": 120,
"routes.ReplyDeleteSubmit": 121, "routes.UnlockTopicSubmit": 121,
"routes.ReplyLikeSubmit": 122, "routes.MoveTopicSubmit": 122,
"routes.AddAttachToReplySubmit": 123, "routes.LikeTopicSubmit": 123,
"routes.RemoveAttachFromReplySubmit": 124, "routes.AddAttachToTopicSubmit": 124,
"routes.ProfileReplyCreateSubmit": 125, "routes.RemoveAttachFromTopicSubmit": 125,
"routes.ProfileReplyEditSubmit": 126, "routes.ViewTopic": 126,
"routes.ProfileReplyDeleteSubmit": 127, "routes.CreateReplySubmit": 127,
"routes.PollVote": 128, "routes.ReplyEditSubmit": 128,
"routes.PollResults": 129, "routes.ReplyDeleteSubmit": 129,
"routes.AccountLogin": 130, "routes.ReplyLikeSubmit": 130,
"routes.AccountRegister": 131, "routes.AddAttachToReplySubmit": 131,
"routes.AccountLogout": 132, "routes.RemoveAttachFromReplySubmit": 132,
"routes.AccountLoginSubmit": 133, "routes.ProfileReplyCreateSubmit": 133,
"routes.AccountLoginMFAVerify": 134, "routes.ProfileReplyEditSubmit": 134,
"routes.AccountLoginMFAVerifySubmit": 135, "routes.ProfileReplyDeleteSubmit": 135,
"routes.AccountRegisterSubmit": 136, "routes.PollVote": 136,
"routes.AccountPasswordReset": 137, "routes.PollResults": 137,
"routes.AccountPasswordResetSubmit": 138, "routes.AccountLogin": 138,
"routes.AccountPasswordResetToken": 139, "routes.AccountRegister": 139,
"routes.AccountPasswordResetTokenSubmit": 140, "routes.AccountLogout": 140,
"routes.DynamicRoute": 141, "routes.AccountLoginSubmit": 141,
"routes.UploadedFile": 142, "routes.AccountLoginMFAVerify": 142,
"routes.StaticFile": 143, "routes.AccountLoginMFAVerifySubmit": 143,
"routes.RobotsTxt": 144, "routes.AccountRegisterSubmit": 144,
"routes.SitemapXml": 145, "routes.AccountPasswordReset": 145,
"routes.OpenSearchXml": 146, "routes.AccountPasswordResetSubmit": 146,
"routes.BadRoute": 147, "routes.AccountPasswordResetToken": 147,
"routes.HTTPSRedirect": 148, "routes.AccountPasswordResetTokenSubmit": 148,
"routes.DynamicRoute": 149,
"routes.UploadedFile": 150,
"routes.StaticFile": 151,
"routes.RobotsTxt": 152,
"routes.SitemapXml": 153,
"routes.OpenSearchXml": 154,
"routes.BadRoute": 155,
"routes.HTTPSRedirect": 156,
} }
var reverseRouteMapEnum = map[int]string{ var reverseRouteMapEnum = map[int]string{
0: "routes.Overview", 0: "routes.Overview",
@ -429,53 +445,61 @@ var reverseRouteMapEnum = map[int]string{
99: "routes.AccountEditEmailTokenSubmit", 99: "routes.AccountEditEmailTokenSubmit",
100: "routes.AccountLogins", 100: "routes.AccountLogins",
101: "routes.LevelList", 101: "routes.LevelList",
102: "routes.ViewProfile", 102: "routes.Convos",
103: "routes.BanUserSubmit", 103: "routes.ConvosCreate",
104: "routes.UnbanUser", 104: "routes.Convo",
105: "routes.ActivateUser", 105: "routes.ConvosCreateSubmit",
106: "routes.IPSearch", 106: "routes.ConvosDeleteSubmit",
107: "routes.CreateTopicSubmit", 107: "routes.ConvosCreateReplySubmit",
108: "routes.EditTopicSubmit", 108: "routes.ConvosDeleteReplySubmit",
109: "routes.DeleteTopicSubmit", 109: "routes.ConvosEditReplySubmit",
110: "routes.StickTopicSubmit", 110: "routes.ViewProfile",
111: "routes.UnstickTopicSubmit", 111: "routes.BanUserSubmit",
112: "routes.LockTopicSubmit", 112: "routes.UnbanUser",
113: "routes.UnlockTopicSubmit", 113: "routes.ActivateUser",
114: "routes.MoveTopicSubmit", 114: "routes.IPSearch",
115: "routes.LikeTopicSubmit", 115: "routes.CreateTopicSubmit",
116: "routes.AddAttachToTopicSubmit", 116: "routes.EditTopicSubmit",
117: "routes.RemoveAttachFromTopicSubmit", 117: "routes.DeleteTopicSubmit",
118: "routes.ViewTopic", 118: "routes.StickTopicSubmit",
119: "routes.CreateReplySubmit", 119: "routes.UnstickTopicSubmit",
120: "routes.ReplyEditSubmit", 120: "routes.LockTopicSubmit",
121: "routes.ReplyDeleteSubmit", 121: "routes.UnlockTopicSubmit",
122: "routes.ReplyLikeSubmit", 122: "routes.MoveTopicSubmit",
123: "routes.AddAttachToReplySubmit", 123: "routes.LikeTopicSubmit",
124: "routes.RemoveAttachFromReplySubmit", 124: "routes.AddAttachToTopicSubmit",
125: "routes.ProfileReplyCreateSubmit", 125: "routes.RemoveAttachFromTopicSubmit",
126: "routes.ProfileReplyEditSubmit", 126: "routes.ViewTopic",
127: "routes.ProfileReplyDeleteSubmit", 127: "routes.CreateReplySubmit",
128: "routes.PollVote", 128: "routes.ReplyEditSubmit",
129: "routes.PollResults", 129: "routes.ReplyDeleteSubmit",
130: "routes.AccountLogin", 130: "routes.ReplyLikeSubmit",
131: "routes.AccountRegister", 131: "routes.AddAttachToReplySubmit",
132: "routes.AccountLogout", 132: "routes.RemoveAttachFromReplySubmit",
133: "routes.AccountLoginSubmit", 133: "routes.ProfileReplyCreateSubmit",
134: "routes.AccountLoginMFAVerify", 134: "routes.ProfileReplyEditSubmit",
135: "routes.AccountLoginMFAVerifySubmit", 135: "routes.ProfileReplyDeleteSubmit",
136: "routes.AccountRegisterSubmit", 136: "routes.PollVote",
137: "routes.AccountPasswordReset", 137: "routes.PollResults",
138: "routes.AccountPasswordResetSubmit", 138: "routes.AccountLogin",
139: "routes.AccountPasswordResetToken", 139: "routes.AccountRegister",
140: "routes.AccountPasswordResetTokenSubmit", 140: "routes.AccountLogout",
141: "routes.DynamicRoute", 141: "routes.AccountLoginSubmit",
142: "routes.UploadedFile", 142: "routes.AccountLoginMFAVerify",
143: "routes.StaticFile", 143: "routes.AccountLoginMFAVerifySubmit",
144: "routes.RobotsTxt", 144: "routes.AccountRegisterSubmit",
145: "routes.SitemapXml", 145: "routes.AccountPasswordReset",
146: "routes.OpenSearchXml", 146: "routes.AccountPasswordResetSubmit",
147: "routes.BadRoute", 147: "routes.AccountPasswordResetToken",
148: "routes.HTTPSRedirect", 148: "routes.AccountPasswordResetTokenSubmit",
149: "routes.DynamicRoute",
150: "routes.UploadedFile",
151: "routes.StaticFile",
152: "routes.RobotsTxt",
153: "routes.SitemapXml",
154: "routes.OpenSearchXml",
155: "routes.BadRoute",
156: "routes.HTTPSRedirect",
} }
var osMapEnum = map[string]int{ var osMapEnum = map[string]int{
"unknown": 0, "unknown": 0,
@ -633,7 +657,7 @@ type HTTPSRedirect struct {}
func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close") w.Header().Set("Connection", "close")
counters.RouteViewCounter.Bump(148) counters.RouteViewCounter.Bump(156)
dest := "https://" + req.Host + req.URL.String() dest := "https://" + req.Host + req.URL.String()
http.Redirect(w, req, dest, http.StatusTemporaryRedirect) http.Redirect(w, req, dest, http.StatusTemporaryRedirect)
} }
@ -828,6 +852,11 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h.Set("X-Frame-Options", "deny") h.Set("X-Frame-Options", "deny")
h.Set("X-XSS-Protection", "1; mode=block") // TODO: Remove when we add a CSP? CSP's are horrendously glitchy things, tread with caution before removing h.Set("X-XSS-Protection", "1; mode=block") // TODO: Remove when we add a CSP? CSP's are horrendously glitchy things, tread with caution before removing
h.Set("X-Content-Type-Options", "nosniff") h.Set("X-Content-Type-Options", "nosniff")
if c.Config.RefNoRef || !c.Site.EnableSsl {
h.Set("Referrer-Policy","no-referrer")
} else {
h.Set("Referrer-Policy","strict-origin")
}
} }
if c.Dev.SuperDebug { if c.Dev.SuperDebug {
@ -836,8 +865,8 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Increment the request counter // Increment the request counter
counters.GlobalViewCounter.Bump() counters.GlobalViewCounter.Bump()
if prefix == "/static" { if prefix == "/s" { //old prefix: /static
counters.RouteViewCounter.Bump(143) counters.RouteViewCounter.Bump(151)
req.URL.Path += extraData req.URL.Path += extraData
routes.StaticFile(w, req) routes.StaticFile(w, req)
return return
@ -988,14 +1017,16 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
counters.LangViewCounter.Bump("none") counters.LangViewCounter.Bump("none")
} }
referrer := req.Header.Get("Referer") // Check the 'referrer' header too? :P if !c.Config.RefNoTrack {
if referrer != "" { referrer := req.Header.Get("Referer") // Check the 'referrer' header too? :P
// ? Optimise this a little? if referrer != "" {
referrer = strings.TrimPrefix(strings.TrimPrefix(referrer,"http://"),"https://") // ? Optimise this a little?
referrer = strings.Split(referrer,"/")[0] referrer = strings.TrimPrefix(strings.TrimPrefix(referrer,"http://"),"https://")
portless := strings.Split(referrer,":")[0] referrer = strings.Split(referrer,"/")[0]
if portless != "localhost" && portless != "127.0.0.1" && portless != c.Site.Host { portless := strings.Split(referrer,":")[0]
counters.ReferrerTracker.Bump(referrer) if portless != "localhost" && portless != "127.0.0.1" && portless != c.Site.Host {
counters.ReferrerTracker.Bump(referrer)
}
} }
} }
@ -1813,9 +1844,110 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
err = routes.LevelList(w,req,user,head) err = routes.LevelList(w,req,user,head)
case "/user/convos/":
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
counters.RouteViewCounter.Bump(102)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
}
err = routes.Convos(w,req,user,head)
case "/user/convos/create/":
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
counters.RouteViewCounter.Bump(103)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
}
err = routes.ConvosCreate(w,req,user,head)
case "/user/convo/":
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
counters.RouteViewCounter.Bump(104)
head, err := c.UserCheck(w,req,&user)
if err != nil {
return err
}
err = routes.Convo(w,req,user,head,extraData)
case "/user/convos/create/submit/":
err = c.NoSessionMismatch(w,req,user)
if err != nil {
return err
}
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
counters.RouteViewCounter.Bump(105)
err = routes.ConvosCreateSubmit(w,req,user)
case "/user/convos/delete/submit/":
err = c.NoSessionMismatch(w,req,user)
if err != nil {
return err
}
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
counters.RouteViewCounter.Bump(106)
err = routes.ConvosDeleteSubmit(w,req,user,extraData)
case "/user/convo/create/submit/":
err = c.NoSessionMismatch(w,req,user)
if err != nil {
return err
}
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
counters.RouteViewCounter.Bump(107)
err = routes.ConvosCreateReplySubmit(w,req,user)
case "/user/convo/delete/submit/":
err = c.NoSessionMismatch(w,req,user)
if err != nil {
return err
}
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
counters.RouteViewCounter.Bump(108)
err = routes.ConvosDeleteReplySubmit(w,req,user,extraData)
case "/user/convo/edit/submit/":
err = c.NoSessionMismatch(w,req,user)
if err != nil {
return err
}
err = c.MemberOnly(w,req,user)
if err != nil {
return err
}
counters.RouteViewCounter.Bump(109)
err = routes.ConvosEditReplySubmit(w,req,user,extraData)
default: default:
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(102) counters.RouteViewCounter.Bump(110)
head, err := c.UserCheck(w,req,&user) head, err := c.UserCheck(w,req,&user)
if err != nil { if err != nil {
return err return err
@ -1835,7 +1967,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(103) counters.RouteViewCounter.Bump(111)
err = routes.BanUserSubmit(w,req,user,extraData) err = routes.BanUserSubmit(w,req,user,extraData)
case "/users/unban/": case "/users/unban/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -1848,7 +1980,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(104) counters.RouteViewCounter.Bump(112)
err = routes.UnbanUser(w,req,user,extraData) err = routes.UnbanUser(w,req,user,extraData)
case "/users/activate/": case "/users/activate/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -1861,7 +1993,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(105) counters.RouteViewCounter.Bump(113)
err = routes.ActivateUser(w,req,user,extraData) err = routes.ActivateUser(w,req,user,extraData)
case "/users/ips/": case "/users/ips/":
err = c.MemberOnly(w,req,user) err = c.MemberOnly(w,req,user)
@ -1869,7 +2001,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(106) counters.RouteViewCounter.Bump(114)
head, err := c.UserCheck(w,req,&user) head, err := c.UserCheck(w,req,&user)
if err != nil { if err != nil {
return err return err
@ -1893,7 +2025,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(107) counters.RouteViewCounter.Bump(115)
err = routes.CreateTopicSubmit(w,req,user) err = routes.CreateTopicSubmit(w,req,user)
case "/topic/edit/submit/": case "/topic/edit/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -1906,7 +2038,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(108) counters.RouteViewCounter.Bump(116)
err = routes.EditTopicSubmit(w,req,user,extraData) err = routes.EditTopicSubmit(w,req,user,extraData)
case "/topic/delete/submit/": case "/topic/delete/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -1920,7 +2052,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
} }
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(109) counters.RouteViewCounter.Bump(117)
err = routes.DeleteTopicSubmit(w,req,user) err = routes.DeleteTopicSubmit(w,req,user)
case "/topic/stick/submit/": case "/topic/stick/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -1933,7 +2065,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(110) counters.RouteViewCounter.Bump(118)
err = routes.StickTopicSubmit(w,req,user,extraData) err = routes.StickTopicSubmit(w,req,user,extraData)
case "/topic/unstick/submit/": case "/topic/unstick/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -1946,7 +2078,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(111) counters.RouteViewCounter.Bump(119)
err = routes.UnstickTopicSubmit(w,req,user,extraData) err = routes.UnstickTopicSubmit(w,req,user,extraData)
case "/topic/lock/submit/": case "/topic/lock/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -1960,7 +2092,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
} }
req.URL.Path += extraData req.URL.Path += extraData
counters.RouteViewCounter.Bump(112) counters.RouteViewCounter.Bump(120)
err = routes.LockTopicSubmit(w,req,user) err = routes.LockTopicSubmit(w,req,user)
case "/topic/unlock/submit/": case "/topic/unlock/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -1973,7 +2105,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(113) counters.RouteViewCounter.Bump(121)
err = routes.UnlockTopicSubmit(w,req,user,extraData) err = routes.UnlockTopicSubmit(w,req,user,extraData)
case "/topic/move/submit/": case "/topic/move/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -1986,7 +2118,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(114) counters.RouteViewCounter.Bump(122)
err = routes.MoveTopicSubmit(w,req,user,extraData) err = routes.MoveTopicSubmit(w,req,user,extraData)
case "/topic/like/submit/": case "/topic/like/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -1999,7 +2131,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(115) counters.RouteViewCounter.Bump(123)
err = routes.LikeTopicSubmit(w,req,user,extraData) err = routes.LikeTopicSubmit(w,req,user,extraData)
case "/topic/attach/add/submit/": case "/topic/attach/add/submit/":
err = c.MemberOnly(w,req,user) err = c.MemberOnly(w,req,user)
@ -2016,7 +2148,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(116) counters.RouteViewCounter.Bump(124)
err = routes.AddAttachToTopicSubmit(w,req,user,extraData) err = routes.AddAttachToTopicSubmit(w,req,user,extraData)
case "/topic/attach/remove/submit/": case "/topic/attach/remove/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -2029,10 +2161,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(117) counters.RouteViewCounter.Bump(125)
err = routes.RemoveAttachFromTopicSubmit(w,req,user,extraData) err = routes.RemoveAttachFromTopicSubmit(w,req,user,extraData)
default: default:
counters.RouteViewCounter.Bump(118) counters.RouteViewCounter.Bump(126)
head, err := c.UserCheck(w,req,&user) head, err := c.UserCheck(w,req,&user)
if err != nil { if err != nil {
return err return err
@ -2056,7 +2188,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(119) counters.RouteViewCounter.Bump(127)
err = routes.CreateReplySubmit(w,req,user) err = routes.CreateReplySubmit(w,req,user)
case "/reply/edit/submit/": case "/reply/edit/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -2069,7 +2201,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(120) counters.RouteViewCounter.Bump(128)
err = routes.ReplyEditSubmit(w,req,user,extraData) err = routes.ReplyEditSubmit(w,req,user,extraData)
case "/reply/delete/submit/": case "/reply/delete/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -2082,7 +2214,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(121) counters.RouteViewCounter.Bump(129)
err = routes.ReplyDeleteSubmit(w,req,user,extraData) err = routes.ReplyDeleteSubmit(w,req,user,extraData)
case "/reply/like/submit/": case "/reply/like/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -2095,7 +2227,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(122) counters.RouteViewCounter.Bump(130)
err = routes.ReplyLikeSubmit(w,req,user,extraData) err = routes.ReplyLikeSubmit(w,req,user,extraData)
case "/reply/attach/add/submit/": case "/reply/attach/add/submit/":
err = c.MemberOnly(w,req,user) err = c.MemberOnly(w,req,user)
@ -2112,7 +2244,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(123) counters.RouteViewCounter.Bump(131)
err = routes.AddAttachToReplySubmit(w,req,user,extraData) err = routes.AddAttachToReplySubmit(w,req,user,extraData)
case "/reply/attach/remove/submit/": case "/reply/attach/remove/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -2125,7 +2257,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(124) counters.RouteViewCounter.Bump(132)
err = routes.RemoveAttachFromReplySubmit(w,req,user,extraData) err = routes.RemoveAttachFromReplySubmit(w,req,user,extraData)
} }
case "/profile": case "/profile":
@ -2141,7 +2273,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(125) counters.RouteViewCounter.Bump(133)
err = routes.ProfileReplyCreateSubmit(w,req,user) err = routes.ProfileReplyCreateSubmit(w,req,user)
case "/profile/reply/edit/submit/": case "/profile/reply/edit/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -2154,7 +2286,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(126) counters.RouteViewCounter.Bump(134)
err = routes.ProfileReplyEditSubmit(w,req,user,extraData) err = routes.ProfileReplyEditSubmit(w,req,user,extraData)
case "/profile/reply/delete/submit/": case "/profile/reply/delete/submit/":
err = c.NoSessionMismatch(w,req,user) err = c.NoSessionMismatch(w,req,user)
@ -2167,7 +2299,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(127) counters.RouteViewCounter.Bump(135)
err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData) err = routes.ProfileReplyDeleteSubmit(w,req,user,extraData)
} }
case "/poll": case "/poll":
@ -2183,23 +2315,23 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(128) counters.RouteViewCounter.Bump(136)
err = routes.PollVote(w,req,user,extraData) err = routes.PollVote(w,req,user,extraData)
case "/poll/results/": case "/poll/results/":
counters.RouteViewCounter.Bump(129) counters.RouteViewCounter.Bump(137)
err = routes.PollResults(w,req,user,extraData) err = routes.PollResults(w,req,user,extraData)
} }
case "/accounts": case "/accounts":
switch(req.URL.Path) { switch(req.URL.Path) {
case "/accounts/login/": case "/accounts/login/":
counters.RouteViewCounter.Bump(130) counters.RouteViewCounter.Bump(138)
head, err := c.UserCheck(w,req,&user) head, err := c.UserCheck(w,req,&user)
if err != nil { if err != nil {
return err return err
} }
err = routes.AccountLogin(w,req,user,head) err = routes.AccountLogin(w,req,user,head)
case "/accounts/create/": case "/accounts/create/":
counters.RouteViewCounter.Bump(131) counters.RouteViewCounter.Bump(139)
head, err := c.UserCheck(w,req,&user) head, err := c.UserCheck(w,req,&user)
if err != nil { if err != nil {
return err return err
@ -2216,7 +2348,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(132) counters.RouteViewCounter.Bump(140)
err = routes.AccountLogout(w,req,user) err = routes.AccountLogout(w,req,user)
case "/accounts/login/submit/": case "/accounts/login/submit/":
err = c.ParseForm(w,req,user) err = c.ParseForm(w,req,user)
@ -2224,10 +2356,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(133) counters.RouteViewCounter.Bump(141)
err = routes.AccountLoginSubmit(w,req,user) err = routes.AccountLoginSubmit(w,req,user)
case "/accounts/mfa_verify/": case "/accounts/mfa_verify/":
counters.RouteViewCounter.Bump(134) counters.RouteViewCounter.Bump(142)
head, err := c.UserCheck(w,req,&user) head, err := c.UserCheck(w,req,&user)
if err != nil { if err != nil {
return err return err
@ -2239,7 +2371,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(135) counters.RouteViewCounter.Bump(143)
err = routes.AccountLoginMFAVerifySubmit(w,req,user) err = routes.AccountLoginMFAVerifySubmit(w,req,user)
case "/accounts/create/submit/": case "/accounts/create/submit/":
err = c.ParseForm(w,req,user) err = c.ParseForm(w,req,user)
@ -2247,10 +2379,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(136) counters.RouteViewCounter.Bump(144)
err = routes.AccountRegisterSubmit(w,req,user) err = routes.AccountRegisterSubmit(w,req,user)
case "/accounts/password-reset/": case "/accounts/password-reset/":
counters.RouteViewCounter.Bump(137) counters.RouteViewCounter.Bump(145)
head, err := c.UserCheck(w,req,&user) head, err := c.UserCheck(w,req,&user)
if err != nil { if err != nil {
return err return err
@ -2262,10 +2394,10 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(138) counters.RouteViewCounter.Bump(146)
err = routes.AccountPasswordResetSubmit(w,req,user) err = routes.AccountPasswordResetSubmit(w,req,user)
case "/accounts/password-reset/token/": case "/accounts/password-reset/token/":
counters.RouteViewCounter.Bump(139) counters.RouteViewCounter.Bump(147)
head, err := c.UserCheck(w,req,&user) head, err := c.UserCheck(w,req,&user)
if err != nil { if err != nil {
return err return err
@ -2277,7 +2409,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
return err return err
} }
counters.RouteViewCounter.Bump(140) counters.RouteViewCounter.Bump(148)
err = routes.AccountPasswordResetTokenSubmit(w,req,user) err = routes.AccountPasswordResetTokenSubmit(w,req,user)
} }
/*case "/sitemaps": // TODO: Count these views /*case "/sitemaps": // TODO: Count these views
@ -2294,7 +2426,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
h.Del("Content-Type") h.Del("Content-Type")
h.Del("Content-Encoding") h.Del("Content-Encoding")
} }
counters.RouteViewCounter.Bump(142) counters.RouteViewCounter.Bump(150)
req.URL.Path += extraData req.URL.Path += extraData
// TODO: Find a way to propagate errors up from this? // TODO: Find a way to propagate errors up from this?
r.UploadHandler(w,req) // TODO: Count these views r.UploadHandler(w,req) // TODO: Count these views
@ -2304,7 +2436,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
// TODO: Add support for favicons and robots.txt files // TODO: Add support for favicons and robots.txt files
switch(extraData) { switch(extraData) {
case "robots.txt": case "robots.txt":
counters.RouteViewCounter.Bump(144) counters.RouteViewCounter.Bump(152)
return routes.RobotsTxt(w,req) return routes.RobotsTxt(w,req)
case "favicon.ico": case "favicon.ico":
gzw, ok := w.(c.GzipResponseWriter) gzw, ok := w.(c.GzipResponseWriter)
@ -2314,14 +2446,14 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
h.Del("Content-Type") h.Del("Content-Type")
h.Del("Content-Encoding") h.Del("Content-Encoding")
} }
req.URL.Path = "/static/favicon.ico" req.URL.Path = "/s/favicon.ico"
routes.StaticFile(w,req) routes.StaticFile(w,req)
return nil return nil
case "opensearch.xml": case "opensearch.xml":
counters.RouteViewCounter.Bump(146) counters.RouteViewCounter.Bump(154)
return routes.OpenSearchXml(w,req) return routes.OpenSearchXml(w,req)
/*case "sitemap.xml": /*case "sitemap.xml":
counters.RouteViewCounter.Bump(145) counters.RouteViewCounter.Bump(153)
return routes.SitemapXml(w,req)*/ return routes.SitemapXml(w,req)*/
} }
return c.NotFound(w,req,nil) return c.NotFound(w,req,nil)
@ -2332,7 +2464,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
r.RUnlock() r.RUnlock()
if ok { if ok {
counters.RouteViewCounter.Bump(141) // TODO: Be more specific about *which* dynamic route it is counters.RouteViewCounter.Bump(149) // TODO: Be more specific about *which* dynamic route it is
req.URL.Path += extraData req.URL.Path += extraData
return handle(w,req,user) return handle(w,req,user)
} }
@ -2343,7 +2475,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
} else { } else {
r.DumpRequest(req,"Bad Route") r.DumpRequest(req,"Bad Route")
} }
counters.RouteViewCounter.Bump(147) counters.RouteViewCounter.Bump(155)
return c.NotFound(w,req,nil) return c.NotFound(w,req,nil)
} }
return err return err

View File

@ -567,7 +567,7 @@ func BenchmarkRoutesSerial(b *testing.B) {
b.Log("Plugins have already been initialised, they can't be deinitialised so these tests will run with plugins on") b.Log("Plugins have already been initialised, they can't be deinitialised so these tests will run with plugins on")
} }
static_w := httptest.NewRecorder() static_w := httptest.NewRecorder()
static_req := httptest.NewRequest("get","/static/global.js",bytes.NewReader(nil)) static_req := httptest.NewRequest("get","/s/global.js",bytes.NewReader(nil))
static_handler := http.HandlerFunc(route_static) static_handler := http.HandlerFunc(route_static)
topic_w := httptest.NewRecorder() topic_w := httptest.NewRecorder()
@ -1134,7 +1134,7 @@ func TestStaticRoute(t *testing.T) {
} }
static_w := httptest.NewRecorder() static_w := httptest.NewRecorder()
static_req := httptest.NewRequest("get","/static/global.js",bytes.NewReader(nil)) static_req := httptest.NewRequest("get","/s/global.js",bytes.NewReader(nil))
static_handler := http.HandlerFunc(route_static) static_handler := http.HandlerFunc(route_static)
static_handler.ServeHTTP(static_w,static_req) static_handler.ServeHTTP(static_w,static_req)

View File

@ -318,6 +318,8 @@
"password_reset_email_sent":"An email was sent to you. Please follow the steps within.", "password_reset_email_sent":"An email was sent to you. Please follow the steps within.",
"password_reset_token_token_verified":"Your password was successfully updated.", "password_reset_token_token_verified":"Your password was successfully updated.",
"convo_dev":"Messages are currently under development. Some features may not work yet and your messages may be purged every now and then.",
"panel_forum_created":"The forum was successfully created.", "panel_forum_created":"The forum was successfully created.",
"panel_forum_deleted":"The forum was successfully deleted.", "panel_forum_deleted":"The forum was successfully deleted.",
"panel_forum_updated":"The forum was successfully updated.", "panel_forum_updated":"The forum was successfully updated.",

View File

@ -134,10 +134,10 @@ func storeInit() (err error) {
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
/*c.Convos, err = c.NewDefaultConversationStore(acc) c.Convos, err = c.NewDefaultConversationStore(acc)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
}*/ }
err = phrases.InitPhrases(c.Site.Language) err = phrases.InitPhrases(c.Site.Language)
if err != nil { if err != nil {

View File

@ -9,6 +9,7 @@ import (
) )
type tblColumn = qgen.DBTableColumn type tblColumn = qgen.DBTableColumn
type tC = tblColumn
type tblKey = qgen.DBTableKey type tblKey = qgen.DBTableKey
func init() { func init() {
@ -35,6 +36,7 @@ func init() {
addPatch(20, patch20) addPatch(20, patch20)
addPatch(21, patch21) addPatch(21, patch21)
addPatch(22, patch22) addPatch(22, patch22)
addPatch(23, patch23)
} }
func patch0(scanner *bufio.Scanner) (err error) { func patch0(scanner *bufio.Scanner) (err error) {
@ -48,8 +50,8 @@ func patch0(scanner *bufio.Scanner) (err error) {
} }
err = execStmt(qgen.Builder.CreateTable("menus", "", "", err = execStmt(qgen.Builder.CreateTable("menus", "", "",
[]tblColumn{ []tC{
tblColumn{"mid", "int", 0, false, true, ""}, tC{"mid", "int", 0, false, true, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"mid", "primary","",false}, tblKey{"mid", "primary","",false},
@ -60,23 +62,23 @@ func patch0(scanner *bufio.Scanner) (err error) {
} }
err = execStmt(qgen.Builder.CreateTable("menu_items", "", "", err = execStmt(qgen.Builder.CreateTable("menu_items", "", "",
[]tblColumn{ []tC{
tblColumn{"miid", "int", 0, false, true, ""}, tC{"miid", "int", 0, false, true, ""},
tblColumn{"mid", "int", 0, false, false, ""}, tC{"mid", "int", 0, false, false, ""},
tblColumn{"name", "varchar", 200, false, false, ""}, tC{"name", "varchar", 200, false, false, ""},
tblColumn{"htmlID", "varchar", 200, false, false, "''"}, tC{"htmlID", "varchar", 200, false, false, "''"},
tblColumn{"cssClass", "varchar", 200, false, false, "''"}, tC{"cssClass", "varchar", 200, false, false, "''"},
tblColumn{"position", "varchar", 100, false, false, ""}, tC{"position", "varchar", 100, false, false, ""},
tblColumn{"path", "varchar", 200, false, false, "''"}, tC{"path", "varchar", 200, false, false, "''"},
tblColumn{"aria", "varchar", 200, false, false, "''"}, tC{"aria", "varchar", 200, false, false, "''"},
tblColumn{"tooltip", "varchar", 200, false, false, "''"}, tC{"tooltip", "varchar", 200, false, false, "''"},
tblColumn{"tmplName", "varchar", 200, false, false, "''"}, tC{"tmplName", "varchar", 200, false, false, "''"},
tblColumn{"order", "int", 0, false, false, "0"}, tC{"order", "int", 0, false, false, "0"},
tblColumn{"guestOnly", "boolean", 0, false, false, "0"}, tC{"guestOnly", "boolean", 0, false, false, "0"},
tblColumn{"memberOnly", "boolean", 0, false, false, "0"}, tC{"memberOnly", "boolean", 0, false, false, "0"},
tblColumn{"staffOnly", "boolean", 0, false, false, "0"}, tC{"staffOnly", "boolean", 0, false, false, "0"},
tblColumn{"adminOnly", "boolean", 0, false, false, "0"}, tC{"adminOnly", "boolean", 0, false, false, "0"},
}, },
[]tblKey{ []tblKey{
tblKey{"miid", "primary","",false}, tblKey{"miid", "primary","",false},
@ -176,14 +178,14 @@ func patch2(scanner *bufio.Scanner) error {
func patch3(scanner *bufio.Scanner) error { func patch3(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.CreateTable("registration_logs", "", "", return execStmt(qgen.Builder.CreateTable("registration_logs", "", "",
[]tblColumn{ []tC{
tblColumn{"rlid", "int", 0, false, true, ""}, tC{"rlid", "int", 0, false, true, ""},
tblColumn{"username", "varchar", 100, false, false, ""}, tC{"username", "varchar", 100, false, false, ""},
tblColumn{"email", "varchar", 100, false, false, ""}, tC{"email", "varchar", 100, false, false, ""},
tblColumn{"failureReason", "varchar", 100, false, false, ""}, tC{"failureReason", "varchar", 100, false, false, ""},
tblColumn{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed? tC{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
tblColumn{"ipaddress", "varchar", 200, false, false, ""}, tC{"ipaddress", "varchar", 200, false, false, ""},
tblColumn{"doneAt", "createdAt", 0, false, false, ""}, tC{"doneAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"rlid", "primary","",false}, tblKey{"rlid", "primary","",false},
@ -240,13 +242,13 @@ func patch4(scanner *bufio.Scanner) error {
} }
err = execStmt(qgen.Builder.CreateTable("pages", "utf8mb4", "utf8mb4_general_ci", err = execStmt(qgen.Builder.CreateTable("pages", "utf8mb4", "utf8mb4_general_ci",
[]tblColumn{ []tC{
tblColumn{"pid", "int", 0, false, true, ""}, tC{"pid", "int", 0, false, true, ""},
tblColumn{"name", "varchar", 200, false, false, ""}, tC{"name", "varchar", 200, false, false, ""},
tblColumn{"title", "varchar", 200, false, false, ""}, tC{"title", "varchar", 200, false, false, ""},
tblColumn{"body", "text", 0, false, false, ""}, tC{"body", "text", 0, false, false, ""},
tblColumn{"allowedGroups", "text", 0, false, false, ""}, tC{"allowedGroups", "text", 0, false, false, ""},
tblColumn{"menuID", "int", 0, false, false, "-1"}, tC{"menuID", "int", 0, false, false, "-1"},
}, },
[]tblKey{ []tblKey{
tblKey{"pid", "primary","",false}, tblKey{"pid", "primary","",false},
@ -278,18 +280,18 @@ func patch5(scanner *bufio.Scanner) error {
} }
err = execStmt(qgen.Builder.CreateTable("users_2fa_keys", "utf8mb4", "utf8mb4_general_ci", err = execStmt(qgen.Builder.CreateTable("users_2fa_keys", "utf8mb4", "utf8mb4_general_ci",
[]tblColumn{ []tC{
tblColumn{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
tblColumn{"secret", "varchar", 100, false, false, ""}, tC{"secret", "varchar", 100, false, false, ""},
tblColumn{"scratch1", "varchar", 50, false, false, ""}, tC{"scratch1", "varchar", 50, false, false, ""},
tblColumn{"scratch2", "varchar", 50, false, false, ""}, tC{"scratch2", "varchar", 50, false, false, ""},
tblColumn{"scratch3", "varchar", 50, false, false, ""}, tC{"scratch3", "varchar", 50, false, false, ""},
tblColumn{"scratch4", "varchar", 50, false, false, ""}, tC{"scratch4", "varchar", 50, false, false, ""},
tblColumn{"scratch5", "varchar", 50, false, false, ""}, tC{"scratch5", "varchar", 50, false, false, ""},
tblColumn{"scratch6", "varchar", 50, false, false, ""}, tC{"scratch6", "varchar", 50, false, false, ""},
tblColumn{"scratch7", "varchar", 50, false, false, ""}, tC{"scratch7", "varchar", 50, false, false, ""},
tblColumn{"scratch8", "varchar", 50, false, false, ""}, tC{"scratch8", "varchar", 50, false, false, ""},
tblColumn{"createdAt", "createdAt", 0, false, false, ""}, tC{"createdAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"uid", "primary","",false}, tblKey{"uid", "primary","",false},
@ -308,8 +310,8 @@ func patch6(scanner *bufio.Scanner) error {
func patch7(scanner *bufio.Scanner) error { func patch7(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.CreateTable("users_avatar_queue", "", "", return execStmt(qgen.Builder.CreateTable("users_avatar_queue", "", "",
[]tblColumn{ []tC{
tblColumn{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
}, },
[]tblKey{ []tblKey{
tblKey{"uid", "primary","",false}, tblKey{"uid", "primary","",false},
@ -371,8 +373,8 @@ func patch8(scanner *bufio.Scanner) error {
return err return err
} }
return execStmt(qgen.Builder.CreateTable("updates", "", "", return execStmt(qgen.Builder.CreateTable("updates", "", "",
[]tblColumn{ []tC{
tblColumn{"dbVersion", "int", 0, false, false, "0"}, tC{"dbVersion", "int", 0, false, false, "0"},
}, },
[]tblKey{}, []tblKey{},
)) ))
@ -386,12 +388,12 @@ func patch9(scanner *bufio.Scanner) error {
} }
return execStmt(qgen.Builder.CreateTable("login_logs", "", "", return execStmt(qgen.Builder.CreateTable("login_logs", "", "",
[]tblColumn{ []tC{
tblColumn{"lid", "int", 0, false, true, ""}, tC{"lid", "int", 0, false, true, ""},
tblColumn{"uid", "int", 0, false, false, ""}, tC{"uid", "int", 0, false, false, ""},
tblColumn{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed? tC{"success", "bool", 0, false, false, "0"}, // Did this attempt succeed?
tblColumn{"ipaddress", "varchar", 200, false, false, ""}, tC{"ipaddress", "varchar", 200, false, false, ""},
tblColumn{"doneAt", "createdAt", 0, false, false, ""}, tC{"doneAt", "createdAt", 0, false, false, ""},
}, },
[]tblKey{ []tblKey{
tblKey{"lid", "primary","",false}, tblKey{"lid", "primary","",false},
@ -403,11 +405,11 @@ var acc = qgen.NewAcc
var itoa = strconv.Itoa var itoa = strconv.Itoa
func patch10(scanner *bufio.Scanner) error { func patch10(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("topics", tblColumn{"attachCount", "int", 0, false, false, "0"}, nil)) err := execStmt(qgen.Builder.AddColumn("topics", tC{"attachCount", "int", 0, false, false, "0"}, nil))
if err != nil { if err != nil {
return err return err
} }
err = execStmt(qgen.Builder.AddColumn("topics", tblColumn{"lastReplyID", "int", 0, false, false, "0"}, nil)) err = execStmt(qgen.Builder.AddColumn("topics", tC{"lastReplyID", "int", 0, false, false, "0"}, nil))
if err != nil { if err != nil {
return err return err
} }
@ -443,7 +445,7 @@ func patch10(scanner *bufio.Scanner) error {
} }
func patch11(scanner *bufio.Scanner) error { func patch11(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("replies", tblColumn{"attachCount", "int", 0, false, false, "0"}, nil)) err := execStmt(qgen.Builder.AddColumn("replies", tC{"attachCount", "int", 0, false, false, "0"}, nil))
if err != nil { if err != nil {
return err return err
} }
@ -517,7 +519,7 @@ func patch12(scanner *bufio.Scanner) error {
} }
func patch13(scanner *bufio.Scanner) error { func patch13(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("widgets", tblColumn{"wid", "int", 0, false, true, ""}, &tblKey{"wid", "primary","",false})) err := execStmt(qgen.Builder.AddColumn("widgets", tC{"wid", "int", 0, false, true, ""}, &tblKey{"wid", "primary","",false}))
if err != nil { if err != nil {
return err return err
} }
@ -548,18 +550,18 @@ func patch15(scanner *bufio.Scanner) error {
func patch16(scanner *bufio.Scanner) error { func patch16(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.CreateTable("password_resets", "", "", return execStmt(qgen.Builder.CreateTable("password_resets", "", "",
[]tblColumn{ []tC{
tblColumn{"email", "varchar", 200, false, false, ""}, tC{"email", "varchar", 200, false, false, ""},
tblColumn{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key tC{"uid", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tblColumn{"validated", "varchar", 200, false, false, ""}, // Token given once the one-use token is consumed, used to prevent multiple people consuming the same one-use token tC{"validated", "varchar", 200, false, false, ""}, // Token given once the one-use token is consumed, used to prevent multiple people consuming the same one-use token
tblColumn{"token", "varchar", 200, false, false, ""}, tC{"token", "varchar", 200, false, false, ""},
tblColumn{"createdAt", "createdAt", 0, false, false, ""}, tC{"createdAt", "createdAt", 0, false, false, ""},
}, nil, }, nil,
)) ))
} }
func patch17(scanner *bufio.Scanner) error { func patch17(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("attachments", tblColumn{"extra", "varchar", 200, false, false, ""}, nil)) err := execStmt(qgen.Builder.AddColumn("attachments", tC{"extra", "varchar", 200, false, false, ""}, nil))
if err != nil { if err != nil {
return err return err
} }
@ -595,14 +597,14 @@ func patch17(scanner *bufio.Scanner) error {
} }
func patch18(scanner *bufio.Scanner) error { func patch18(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.AddColumn("forums", tblColumn{"order", "int", 0, false, false, "0"}, nil)) return execStmt(qgen.Builder.AddColumn("forums", tC{"order", "int", 0, false, false, "0"}, nil))
} }
func patch19(scanner *bufio.Scanner) error { func patch19(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.CreateTable("memchunks", "", "", return execStmt(qgen.Builder.CreateTable("memchunks", "", "",
[]tblColumn{ []tC{
tblColumn{"count", "int", 0, false, false, "0"}, tC{"count", "int", 0, false, false, "0"},
tblColumn{"createdAt", "datetime", 0, false, false, ""}, tC{"createdAt", "datetime", 0, false, false, ""},
}, nil, }, nil,
)) ))
} }
@ -631,29 +633,82 @@ func patch20(scanner *bufio.Scanner) error {
} }
func patch21(scanner *bufio.Scanner) error { func patch21(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.AddColumn("memchunks", tblColumn{"stack", "int", 0, false, false, "0"}, nil)) err := execStmt(qgen.Builder.AddColumn("memchunks", tC{"stack", "int", 0, false, false, "0"}, nil))
if err != nil { if err != nil {
return err return err
} }
err = execStmt(qgen.Builder.AddColumn("memchunks", tblColumn{"heap", "int", 0, false, false, "0"}, nil)) err = execStmt(qgen.Builder.AddColumn("memchunks", tC{"heap", "int", 0, false, false, "0"}, nil))
if err != nil { if err != nil {
return err return err
} }
err = execStmt(qgen.Builder.CreateTable("meta", "", "", err = execStmt(qgen.Builder.CreateTable("meta", "", "",
[]tblColumn{ []tC{
tblColumn{"name", "varchar", 200, false, false, ""}, tC{"name", "varchar", 200, false, false, ""},
tblColumn{"value", "varchar", 200, false, false, ""}, tC{"value", "varchar", 200, false, false, ""},
}, nil, }, nil,
)) ))
if err != nil { if err != nil {
return err return err
} }
return execStmt(qgen.Builder.AddColumn("activity_stream", tblColumn{"createdAt", "createdAt", 0, false, false, ""}, nil)) return execStmt(qgen.Builder.AddColumn("activity_stream", tC{"createdAt", "createdAt", 0, false, false, ""}, nil))
} }
func patch22(scanner *bufio.Scanner) error { func patch22(scanner *bufio.Scanner) error {
return execStmt(qgen.Builder.AddColumn("forums", tblColumn{"tmpl", "varchar", 200, false, false, "''"}, nil)) return execStmt(qgen.Builder.AddColumn("forums", tC{"tmpl", "varchar", 200, false, false, "''"}, nil))
}
func patch23(scanner *bufio.Scanner) error {
err := execStmt(qgen.Builder.DropTable("conversations"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.CreateTable("conversations", "", "",
[]tC{
tC{"cid", "int", 0, false, true, ""},
tC{"createdBy", "int", 0, false, false, ""}, // TODO: Make this a foreign key
tC{"createdAt", "createdAt", 0, false, false, ""},
tC{"lastReplyAt", "datetime", 0, false, false, ""},
tC{"lastReplyBy", "int", 0, false, false, ""},
},
[]tblKey{
tblKey{"cid", "primary","",false},
},
))
if err != nil {
return err
}
err = execStmt(qgen.Builder.DropTable("conversations_posts"))
if err != nil {
return err
}
err = execStmt(qgen.Builder.CreateTable("conversations_posts", "", "",
[]tC{
tC{"pid", "int", 0, false, true, ""},
tC{"cid", "int", 0, false, false, ""},
tC{"createdBy", "int", 0, false, false, ""},
tC{"body", "varchar", 50, false, false, ""},
tC{"post", "varchar", 50, false, false, "''"},
},
[]tblKey{
tblKey{"pid", "primary","",false},
},
))
if err != nil {
return err
}
err = execStmt(qgen.Builder.DropTable("conversations_participants"))
if err != nil {
return err
}
return execStmt(qgen.Builder.CreateTable("conversations_participants", "", "",
[]tC{
tC{"uid", "int", 0, false, false, ""},
tC{"cid", "int", 0, false, false, ""},
}, nil,
))
} }

View File

@ -83,9 +83,9 @@ function asyncGetScript(source) {
} }
function notifyOnScript(source) { function notifyOnScript(source) {
source = "/static/"+source; source = "/s/"+source;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let ss = source.replace("/static/",""); let ss = source.replace("/s/","");
try { try {
let ssp = ss.charAt(0).toUpperCase() + ss.slice(1) let ssp = ss.charAt(0).toUpperCase() + ss.slice(1)
console.log("ssp:",ssp) console.log("ssp:",ssp)
@ -138,8 +138,8 @@ function loadScript(name, callback,fail) {
let parts = value.split("; current_theme="); let parts = value.split("; current_theme=");
if (parts.length == 2) fname += "_"+ parts.pop().split(";").shift(); if (parts.length == 2) fname += "_"+ parts.pop().split(";").shift();
let url = "/static/"+fname+".js" let url = "/s/"+fname+".js"
let iurl = "/static/"+name+".js" let iurl = "/s/"+name+".js"
asyncGetScript(url) asyncGetScript(url)
.then(callback) .then(callback)
.catch((e) => { .catch((e) => {
@ -161,7 +161,7 @@ function loadScript(name, callback,fail) {
/* /*
function loadTmpl(name,callback) { function loadTmpl(name,callback) {
let url = "/static/"+name let url = "/s/"+name
let worker = new Worker(url); let worker = new Worker(url);
} }
*/ */

View File

@ -1,4 +1,4 @@
$.emojiarea.path = '/static/smilies/emojiarea'; $.emojiarea.path = '/s/smilies/emojiarea';
$.emojiarea.icons = [{"name" : "<i class='icon-smile'></i>", "icons" : {':bowtie:' : 'bowtie.png', $.emojiarea.icons = [{"name" : "<i class='icon-smile'></i>", "icons" : {':bowtie:' : 'bowtie.png',
':smile:' : 'smile.png', ':smile:' : 'smile.png',
':laughing:' : 'laughing.png', ':laughing:' : 'laughing.png',

View File

@ -654,7 +654,7 @@ func (a *MysqlAdapter) complexSelect(preBuilder *selectPrebuilder, sb *strings.B
return nil return nil
} }
func (adapter *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) { func (a *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
if table1 == "" { if table1 == "" {
return "", errors.New("You need a name for the left table") return "", errors.New("You need a name for the left table")
} }
@ -668,7 +668,7 @@ func (adapter *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 s
return "", errors.New("No joiners found for SimpleLeftJoin") return "", errors.New("No joiners found for SimpleLeftJoin")
} }
whereStr, err := adapter.buildJoinWhere(where) whereStr, err := a.buildJoinWhere(where)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -684,14 +684,14 @@ func (adapter *MysqlAdapter) SimpleLeftJoin(name string, table1 string, table2 s
as2 = " AS `"+ thalf2[1]+"`" as2 = " AS `"+ thalf2[1]+"`"
} }
var querystr = "SELECT" + adapter.buildJoinColumns(columns) + " FROM `" + thalf1[0] + "`"+as1+" LEFT JOIN `" + thalf2[0] + "`"+as2+" ON " + adapter.buildJoiners(joiners) + whereStr + adapter.buildOrderby(orderby) + adapter.buildLimit(limit) q := "SELECT" + a.buildJoinColumns(columns) + " FROM `" + thalf1[0] + "`"+as1+" LEFT JOIN `" + thalf2[0] + "`"+as2+" ON " + a.buildJoiners(joiners) + whereStr + a.buildOrderby(orderby) + a.buildLimit(limit)
querystr = strings.TrimSpace(querystr) q = strings.TrimSpace(q)
adapter.pushStatement(name, "select", querystr) a.pushStatement(name, "select", q)
return querystr, nil return q, nil
} }
func (adapter *MysqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) { func (a *MysqlAdapter) SimpleInnerJoin(name string, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (string, error) {
if table1 == "" { if table1 == "" {
return "", errors.New("You need a name for the left table") return "", errors.New("You need a name for the left table")
} }
@ -705,16 +705,27 @@ func (adapter *MysqlAdapter) SimpleInnerJoin(name string, table1 string, table2
return "", errors.New("No joiners found for SimpleInnerJoin") return "", errors.New("No joiners found for SimpleInnerJoin")
} }
whereStr, err := adapter.buildJoinWhere(where) whereStr, err := a.buildJoinWhere(where)
if err != nil { if err != nil {
return "", err return "", err
} }
var querystr = "SELECT " + adapter.buildJoinColumns(columns) + " FROM `" + table1 + "` INNER JOIN `" + table2 + "` ON " + adapter.buildJoiners(joiners) + whereStr + adapter.buildOrderby(orderby) + adapter.buildLimit(limit) thalf1 := strings.Split(strings.Replace(table1," as ", " AS ",-1)," AS ")
var as1 string
if len(thalf1) == 2 {
as1 = " AS `"+ thalf1[1]+"`"
}
thalf2 := strings.Split(strings.Replace(table2," as ", " AS ",-1)," AS ")
var as2 string
if len(thalf2) == 2 {
as2 = " AS `"+ thalf2[1]+"`"
}
querystr = strings.TrimSpace(querystr) q := "SELECT " + a.buildJoinColumns(columns) + " FROM `" + thalf1[0] + "`"+as1+" INNER JOIN `" + thalf2[0] + "`"+as2+" ON " + a.buildJoiners(joiners) + whereStr + a.buildOrderby(orderby) + a.buildLimit(limit)
adapter.pushStatement(name, "select", querystr)
return querystr, nil q = strings.TrimSpace(q)
a.pushStatement(name, "select", q)
return q, nil
} }
func (adapter *MysqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, error) { func (adapter *MysqlAdapter) SimpleUpdateSelect(up *updatePrebuilder) (string, error) {
@ -767,7 +778,7 @@ func (adapter *MysqlAdapter) SimpleInsertLeftJoin(name string, ins DBInsert, sel
return "", err return "", err
} }
var q = "INSERT INTO `" + ins.Table + "`(" + adapter.buildColumns(ins.Columns) + ") SELECT" + adapter.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON " + adapter.buildJoiners(sel.Joiners) + whereStr + adapter.buildOrderby(sel.Orderby) + adapter.buildLimit(sel.Limit) q := "INSERT INTO `" + ins.Table + "`(" + adapter.buildColumns(ins.Columns) + ") SELECT" + adapter.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON " + adapter.buildJoiners(sel.Joiners) + whereStr + adapter.buildOrderby(sel.Orderby) + adapter.buildLimit(sel.Limit)
q = strings.TrimSpace(q) q = strings.TrimSpace(q)
adapter.pushStatement(name, "insert", q) adapter.pushStatement(name, "insert", q)
@ -856,7 +867,7 @@ func (adapter *MysqlAdapter) SimpleInsertInnerJoin(name string, ins DBInsert, se
return "", err return "", err
} }
var q = "INSERT INTO `" + ins.Table + "`(" + adapter.buildColumns(ins.Columns) + ") SELECT" + adapter.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON " + adapter.buildJoiners(sel.Joiners) + whereStr + adapter.buildOrderby(sel.Orderby) + adapter.buildLimit(sel.Limit) q := "INSERT INTO `" + ins.Table + "`(" + adapter.buildColumns(ins.Columns) + ") SELECT" + adapter.buildJoinColumns(sel.Columns) + " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON " + adapter.buildJoiners(sel.Joiners) + whereStr + adapter.buildOrderby(sel.Orderby) + adapter.buildLimit(sel.Limit)
q = strings.TrimSpace(q) q = strings.TrimSpace(q)
adapter.pushStatement(name, "insert", q) adapter.pushStatement(name, "insert", q)
@ -882,13 +893,13 @@ func (adapter *MysqlAdapter) Builder() *prebuilder {
return &prebuilder{adapter} return &prebuilder{adapter}
} }
func (adapter *MysqlAdapter) Write() error { func (a *MysqlAdapter) Write() error {
var stmts, body string var stmts, body string
for _, name := range adapter.BufferOrder { for _, name := range a.BufferOrder {
if name[0] == '_' { if name[0] == '_' {
continue continue
} }
stmt := adapter.Buffer[name] stmt := a.Buffer[name]
// ? - Table creation might be a little complex for Go to do outside a SQL file :( // ? - Table creation might be a little complex for Go to do outside a SQL file :(
if stmt.Type == "upsert" { if stmt.Type == "upsert" {
stmts += "\t" + name + " *qgen.MySQLUpsertCallback\n" stmts += "\t" + name + " *qgen.MySQLUpsertCallback\n"
@ -945,12 +956,12 @@ func _gen_mysql() (err error) {
} }
// Internal methods, not exposed in the interface // Internal methods, not exposed in the interface
func (adapter *MysqlAdapter) pushStatement(name string, stype string, querystr string) { func (a *MysqlAdapter) pushStatement(name string, stype string, querystr string) {
if name == "" { if name == "" {
return return
} }
adapter.Buffer[name] = DBStmt{querystr, stype} a.Buffer[name] = DBStmt{querystr, stype}
adapter.BufferOrder = append(adapter.BufferOrder, name) a.BufferOrder = append(a.BufferOrder, name)
} }
func (adapter *MysqlAdapter) stringyType(ctype string) bool { func (adapter *MysqlAdapter) stringyType(ctype string) bool {

View File

@ -593,6 +593,11 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h.Set("X-Frame-Options", "deny") h.Set("X-Frame-Options", "deny")
h.Set("X-XSS-Protection", "1; mode=block") // TODO: Remove when we add a CSP? CSP's are horrendously glitchy things, tread with caution before removing h.Set("X-XSS-Protection", "1; mode=block") // TODO: Remove when we add a CSP? CSP's are horrendously glitchy things, tread with caution before removing
h.Set("X-Content-Type-Options", "nosniff") h.Set("X-Content-Type-Options", "nosniff")
if c.Config.RefNoRef || !c.Site.EnableSsl {
h.Set("Referrer-Policy","no-referrer")
} else {
h.Set("Referrer-Policy","strict-origin")
}
} }
if c.Dev.SuperDebug { if c.Dev.SuperDebug {
@ -601,7 +606,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Increment the request counter // Increment the request counter
counters.GlobalViewCounter.Bump() counters.GlobalViewCounter.Bump()
if prefix == "/static" { if prefix == "/s" { //old prefix: /static
counters.RouteViewCounter.Bump({{ index .AllRouteMap "routes.StaticFile" }}) counters.RouteViewCounter.Bump({{ index .AllRouteMap "routes.StaticFile" }})
req.URL.Path += extraData req.URL.Path += extraData
routes.StaticFile(w, req) routes.StaticFile(w, req)
@ -753,14 +758,16 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
counters.LangViewCounter.Bump("none") counters.LangViewCounter.Bump("none")
} }
referrer := req.Header.Get("Referer") // Check the 'referrer' header too? :P if !c.Config.RefNoTrack {
if referrer != "" { referrer := req.Header.Get("Referer") // Check the 'referrer' header too? :P
// ? Optimise this a little? if referrer != "" {
referrer = strings.TrimPrefix(strings.TrimPrefix(referrer,"http://"),"https://") // ? Optimise this a little?
referrer = strings.Split(referrer,"/")[0] referrer = strings.TrimPrefix(strings.TrimPrefix(referrer,"http://"),"https://")
portless := strings.Split(referrer,":")[0] referrer = strings.Split(referrer,"/")[0]
if portless != "localhost" && portless != "127.0.0.1" && portless != c.Site.Host { portless := strings.Split(referrer,":")[0]
counters.ReferrerTracker.Bump(referrer) if portless != "localhost" && portless != "127.0.0.1" && portless != c.Site.Host {
counters.ReferrerTracker.Bump(referrer)
}
} }
} }
@ -840,7 +847,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
h.Del("Content-Type") h.Del("Content-Type")
h.Del("Content-Encoding") h.Del("Content-Encoding")
} }
req.URL.Path = "/static/favicon.ico" req.URL.Path = "/s/favicon.ico"
routes.StaticFile(w,req) routes.StaticFile(w,req)
return nil return nil
case "opensearch.xml": case "opensearch.xml":

View File

@ -49,7 +49,7 @@ func userRoutes() *RouteGroup {
return newRouteGroup("/user/").Routes( return newRouteGroup("/user/").Routes(
View("routes.ViewProfile", "/user/").LitBefore("req.URL.Path += extraData"), View("routes.ViewProfile", "/user/").LitBefore("req.URL.Path += extraData"),
Set("routes.AccountEdit","/user/edit", Set("routes.AccountEdit", "/user/edit/",
MView("", "/"), MView("", "/"),
MView("Password", "/password/"), MView("Password", "/password/"),
Action("PasswordSubmit", "/password/submit/"), // TODO: Full test this Action("PasswordSubmit", "/password/submit/"), // TODO: Full test this
@ -82,14 +82,15 @@ func userRoutes() *RouteGroup {
MView("routes.LevelList", "/user/levels/"), MView("routes.LevelList", "/user/levels/"),
//MView("routes.LevelRankings", "/user/rankings/"), //MView("routes.LevelRankings", "/user/rankings/"),
//MView("routes.Alerts", "/user/alerts/"), //MView("routes.Alerts", "/user/alerts/"),
/*MView("routes.Convos", "/user/convos/"), MView("routes.Convos", "/user/convos/"),
MView("routes.ConvosCreate", "/user/convos/create/"), MView("routes.ConvosCreate", "/user/convos/create/"),
MView("routes.Convo", "/user/convo/","extraData"), MView("routes.Convo", "/user/convo/", "extraData"),
Action("routes.ConvosCreateSubmit", "/user/convos/create/submit/"), Action("routes.ConvosCreateSubmit", "/user/convos/create/submit/"),
Action("routes.ConvosDeleteSubmit", "/user/convos/delete/submit/","extraData"), Action("routes.ConvosDeleteSubmit", "/user/convos/delete/submit/", "extraData"),
Action("routes.ConvosCreateReplySubmit", "/user/convo/create/submit/"), Action("routes.ConvosCreateReplySubmit", "/user/convo/create/submit/"),
Action("routes.ConvosDeleteReplySubmit", "/user/convo/delete/submit/"),*/ Action("routes.ConvosDeleteReplySubmit", "/user/convo/delete/submit/","extraData"),
Action("routes.ConvosEditReplySubmit", "/user/convo/edit/submit/", "extraData"),
) )
} }
@ -171,6 +172,8 @@ func accountRoutes() *RouteGroup {
func panelRoutes() *RouteGroup { func panelRoutes() *RouteGroup {
return newRouteGroup("/panel/").Before("SuperModOnly").NoHeader().Routes( return newRouteGroup("/panel/").Before("SuperModOnly").NoHeader().Routes(
View("panel.Dashboard", "/panel/"), View("panel.Dashboard", "/panel/"),
//View("panel.StatsDisk", "/panel/stats/disk/"),
View("panel.Forums", "/panel/forums/"), View("panel.Forums", "/panel/forums/"),
Action("panel.ForumsCreateSubmit", "/panel/forums/create/"), Action("panel.ForumsCreateSubmit", "/panel/forums/create/"),
Action("panel.ForumsDelete", "/panel/forums/delete/", "extraData"), Action("panel.ForumsDelete", "/panel/forums/delete/", "extraData"),

View File

@ -21,8 +21,8 @@ func ParseSEOURL(urlBit string) (slug string, id int, err error) {
return halves[0], tid, err return halves[0], tid, err
} }
var slen1 = len("</static/>; rel=preload; as=script,") var slen1 = len("</s/>; rel=preload; as=script,")
var slen2 = len("</static/>; rel=preload; as=style,") var slen2 = len("</s/>; rel=preload; as=style,")
func doPush(w http.ResponseWriter, header *c.Header) { func doPush(w http.ResponseWriter, header *c.Header) {
//fmt.Println("in doPush") //fmt.Println("in doPush")
@ -32,7 +32,7 @@ func doPush(w http.ResponseWriter, header *c.Header) {
var push = func(in []string) { var push = func(in []string) {
sb.Grow((slen1 + 5) * len(in)) sb.Grow((slen1 + 5) * len(in))
for _, path := range in { for _, path := range in {
sb.WriteString("</static/") sb.WriteString("</s/")
sb.WriteString(path) sb.WriteString(path)
sb.WriteString(">; rel=preload; as=script,") sb.WriteString(">; rel=preload; as=script,")
} }
@ -44,7 +44,7 @@ func doPush(w http.ResponseWriter, header *c.Header) {
if len(header.Stylesheets) > 0 { if len(header.Stylesheets) > 0 {
sb.Grow((slen2 + 6) * len(header.Stylesheets)) sb.Grow((slen2 + 6) * len(header.Stylesheets))
for _, path := range header.Stylesheets { for _, path := range header.Stylesheets {
sb.WriteString("</static/") sb.WriteString("</s/")
sb.WriteString(path) sb.WriteString(path)
sb.WriteString(">; rel=preload; as=style,") sb.WriteString(">; rel=preload; as=style,")
} }
@ -69,9 +69,9 @@ func doPush(w http.ResponseWriter, header *c.Header) {
var push = func(in []string) { var push = func(in []string) {
for _, path := range in { for _, path := range in {
//fmt.Println("pushing /static/" + path) //fmt.Println("pushing /s/" + path)
// TODO: Avoid concatenating here // TODO: Avoid concatenating here
err := pusher.Push("/static/"+path, nil) err := pusher.Push("/s/"+path, nil)
if err != nil { if err != nil {
break break
} }
@ -116,7 +116,7 @@ func renderTemplate3(tmplName string, hookName string, w http.ResponseWriter, r
h.Stylesheets = nil h.Stylesheets = nil
c.PrepResources(&h.CurrentUser, h, h.Theme) c.PrepResources(&h.CurrentUser, h, h.Theme)
for _, ss := range s { for _, ss := range s {
h.Stylesheets = append(h.Stylesheets,ss) h.Stylesheets = append(h.Stylesheets, ss)
} }
if h.CurrentUser.Loggedin { if h.CurrentUser.Loggedin {
@ -141,4 +141,4 @@ func renderTemplate3(tmplName string, hookName string, w http.ResponseWriter, r
} }
// TODO: Rename renderTemplate to RenderTemplate instead of using this hack to avoid breaking things // TODO: Rename renderTemplate to RenderTemplate instead of using this hack to avoid breaking things
var RenderTemplate = renderTemplate3 var RenderTemplate = renderTemplate3

View File

@ -7,11 +7,12 @@ import (
"strings" "strings"
c "github.com/Azareal/Gosora/common" c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/common/phrases" p "github.com/Azareal/Gosora/common/phrases"
) )
func Convos(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header) c.RouteError { func Convos(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header) c.RouteError {
accountEditHead("convos", w, r, &user, header) accountEditHead("convos", w, r, &user, header)
header.AddNotice("convo_dev")
ccount := c.Convos.GetUserCount(user.ID) ccount := c.Convos.GetUserCount(user.ID)
page, _ := strconv.Atoi(r.FormValue("page")) page, _ := strconv.Atoi(r.FormValue("page"))
offset, page, lastPage := c.PageOffset(ccount, page, c.Config.ItemsPerPage) offset, page, lastPage := c.PageOffset(ccount, page, c.Config.ItemsPerPage)
@ -30,9 +31,11 @@ func Convos(w http.ResponseWriter, r *http.Request, user c.User, header *c.Heade
func Convo(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header, scid string) c.RouteError { func Convo(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header, scid string) c.RouteError {
accountEditHead("convo", w, r, &user, header) accountEditHead("convo", w, r, &user, header)
header.AddSheet(header.Theme.Name + "/convo.css")
header.AddNotice("convo_dev")
cid, err := strconv.Atoi(scid) cid, err := strconv.Atoi(scid)
if err != nil { if err != nil {
return c.LocalError(phrases.GetErrorPhrase("id_must_be_integer"), w, r, user) return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
} }
convo, err := c.Convos.Get(cid) convo, err := c.Convos.Get(cid)
@ -50,7 +53,7 @@ func Convo(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header
offset, page, lastPage := c.PageOffset(pcount, page, c.Config.ItemsPerPage) offset, page, lastPage := c.PageOffset(pcount, page, c.Config.ItemsPerPage)
pageList := c.Paginate(page, lastPage, 5) pageList := c.Paginate(page, lastPage, 5)
posts, err := convo.Posts(offset) posts, err := convo.Posts(offset, c.Config.ItemsPerPage)
// TODO: Report a better error for no posts // TODO: Report a better error for no posts
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.NotFound(w, r, header) return c.NotFound(w, r, header)
@ -58,12 +61,23 @@ func Convo(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
pi := c.Account{header, "dashboard", "convo", c.ConvoViewPage{header, posts, c.Paginator{pageList, page, lastPage}}} pitems := make([]c.ConvoViewRow, len(posts))
for i, post := range posts {
user, err := c.Users.Get(post.CreatedBy)
if err != nil {
return c.InternalError(err, w, r)
}
pitems[i] = c.ConvoViewRow{post, user, "", "4"}
}
canModify := user.ID == convo.CreatedBy || user.IsSuperMod
pi := c.Account{header, "dashboard", "convo", c.ConvoViewPage{header, convo, pitems, canModify, c.Paginator{pageList, page, lastPage}}}
return renderTemplate("account", w, r, header, pi) return renderTemplate("account", w, r, header, pi)
} }
func ConvosCreate(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header) c.RouteError { func ConvosCreate(w http.ResponseWriter, r *http.Request, user c.User, header *c.Header) c.RouteError {
accountEditHead("create_convo", w, r, &user, header) accountEditHead("create_convo", w, r, &user, header)
header.AddNotice("convo_dev")
recpName := "" recpName := ""
pi := c.Account{header, "dashboard", "create_convo", c.ConvoCreatePage{header, recpName}} pi := c.Account{header, "dashboard", "create_convo", c.ConvoCreatePage{header, recpName}}
return renderTemplate("account", w, r, header, pi) return renderTemplate("account", w, r, header, pi)
@ -103,13 +117,6 @@ func ConvosCreateSubmit(w http.ResponseWriter, r *http.Request, user c.User) c.R
return nil return nil
} }
/*type ConversationPost struct {
ID int
CID int
Body string
Post string // aes, ''
}*/
func ConvosDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, scid string) c.RouteError { func ConvosDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, scid string) c.RouteError {
_, ferr := c.SimpleUserCheck(w, r, &user) _, ferr := c.SimpleUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
@ -117,7 +124,7 @@ func ConvosDeleteSubmit(w http.ResponseWriter, r *http.Request, user c.User, sci
} }
cid, err := strconv.Atoi(scid) cid, err := strconv.Atoi(scid)
if err != nil { if err != nil {
return c.LocalError(phrases.GetErrorPhrase("id_must_be_integer"), w, r, user) return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
} }
err = c.Convos.Delete(cid) err = c.Convos.Delete(cid)
@ -138,11 +145,61 @@ func ConvosCreateReplySubmit(w http.ResponseWriter, r *http.Request, user c.User
return nil return nil
} }
func ConvosDeleteReplySubmit(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError { func ConvosDeleteReplySubmit(w http.ResponseWriter, r *http.Request, user c.User, scpid string) c.RouteError {
_, ferr := c.SimpleUserCheck(w, r, &user)
if ferr != nil {
return ferr
}
cpid, err := strconv.Atoi(scpid)
if err != nil {
return c.LocalError(p.GetErrorPhrase("id_must_be_integer"), w, r, user)
}
post := &c.ConversationPost{ID: cpid}
err = post.Fetch()
if err == sql.ErrNoRows {
return c.NotFound(w, r, nil)
} else if err != nil {
return c.InternalError(err, w, r)
}
convo, err := c.Convos.Get(post.CID)
if err == sql.ErrNoRows {
return c.NotFound(w, r, nil)
} else if err != nil {
return c.InternalError(err, w, r)
}
pcount := convo.PostsCount()
if pcount == 0 {
return c.NotFound(w, r, nil)
}
posts, err := convo.Posts(0, c.Config.ItemsPerPage)
// TODO: Report a better error for no posts
if err == sql.ErrNoRows {
return c.NotFound(w, r, nil)
} else if err != nil {
return c.InternalError(err, w, r)
}
if post.ID == posts[0].ID {
err = c.Convos.Delete(convo.ID)
} else {
err = post.Delete()
}
if err != nil {
return c.InternalError(err, w, r)
}
http.Redirect(w, r, "/user/convo/"+strconv.Itoa(post.CID), http.StatusSeeOther)
return nil
}
func ConvosEditReplySubmit(w http.ResponseWriter, r *http.Request, user c.User, scpid string) c.RouteError {
_, ferr := c.SimpleUserCheck(w, r, &user) _, ferr := c.SimpleUserCheck(w, r, &user)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
http.Redirect(w, r, "/user/convo/id", http.StatusSeeOther) http.Redirect(w, r, "/user/convo/id", http.StatusSeeOther)
return nil return nil
} }

View File

@ -24,7 +24,8 @@ func ReportSubmit(w http.ResponseWriter, r *http.Request, user c.User, sitemID s
// TODO: Localise these titles and bodies // TODO: Localise these titles and bodies
var title, content string var title, content string
if itemType == "reply" { switch itemType {
case "reply":
reply, err := c.Rstore.Get(itemID) reply, err := c.Rstore.Get(itemID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("We were unable to find the reported post", w, r, user) return c.LocalError("We were unable to find the reported post", w, r, user)
@ -41,7 +42,7 @@ func ReportSubmit(w http.ResponseWriter, r *http.Request, user c.User, sitemID s
title = "Reply: " + topic.Title title = "Reply: " + topic.Title
content = reply.Content + "\n\nOriginal Post: #rid-" + strconv.Itoa(itemID) content = reply.Content + "\n\nOriginal Post: #rid-" + strconv.Itoa(itemID)
} else if itemType == "user-reply" { case "user-reply":
userReply, err := c.Prstore.Get(itemID) userReply, err := c.Prstore.Get(itemID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("We weren't able to find the reported post", w, r, user) return c.LocalError("We weren't able to find the reported post", w, r, user)
@ -57,7 +58,7 @@ func ReportSubmit(w http.ResponseWriter, r *http.Request, user c.User, sitemID s
} }
title = "Profile: " + profileOwner.Name title = "Profile: " + profileOwner.Name
content = userReply.Content + "\n\nOriginal Post: @" + strconv.Itoa(userReply.ParentID) content = userReply.Content + "\n\nOriginal Post: @" + strconv.Itoa(userReply.ParentID)
} else if itemType == "topic" { case "topic":
topic, err := c.Topics.Get(itemID) topic, err := c.Topics.Get(itemID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.NotFound(w, r, nil) return c.NotFound(w, r, nil)
@ -66,7 +67,27 @@ func ReportSubmit(w http.ResponseWriter, r *http.Request, user c.User, sitemID s
} }
title = "Topic: " + topic.Title title = "Topic: " + topic.Title
content = topic.Content + "\n\nOriginal Post: #tid-" + strconv.Itoa(itemID) content = topic.Content + "\n\nOriginal Post: #tid-" + strconv.Itoa(itemID)
} else { case "convo-reply":
post := &c.ConversationPost{ID: itemID}
err := post.Fetch()
if err == sql.ErrNoRows {
return c.NotFound(w, r, nil)
} else if err != nil {
return c.InternalError(err, w, r)
}
post, err = c.ConvoPostProcess.OnLoad(post)
if err != nil {
return c.InternalError(err, w, r)
}
user, err := c.Users.Get(post.CreatedBy)
if err != nil {
return c.InternalError(err, w, r)
}
title = "Convo Post: " + user.Name
content = post.Body + "\n\nOriginal Post: #cpid-" + strconv.Itoa(itemID)
default:
_, hasHook := headerLite.Hooks.VhookNeedHook("report_preassign", &itemID, &itemType) _, hasHook := headerLite.Hooks.VhookNeedHook("report_preassign", &itemID, &itemType)
if hasHook { if hasHook {
return nil return nil

View File

@ -0,0 +1,8 @@
CREATE TABLE [conversations] (
[cid] int not null IDENTITY,
[createdBy] int not null,
[createdAt] datetime not null,
[lastReplyAt] datetime not null,
[lastReplyBy] int not null,
primary key([cid])
);

View File

@ -0,0 +1,4 @@
CREATE TABLE [conversations_participants] (
[uid] int not null,
[cid] int not null
);

View File

@ -0,0 +1,8 @@
CREATE TABLE [conversations_posts] (
[pid] int not null IDENTITY,
[cid] int not null,
[createdBy] int not null,
[body] nvarchar (50) not null,
[post] nvarchar (50) DEFAULT '' not null,
primary key([pid])
);

View File

@ -0,0 +1,8 @@
CREATE TABLE `conversations` (
`cid` int not null AUTO_INCREMENT,
`createdBy` int not null,
`createdAt` datetime not null,
`lastReplyAt` datetime not null,
`lastReplyBy` int not null,
primary key(`cid`)
);

View File

@ -0,0 +1,4 @@
CREATE TABLE `conversations_participants` (
`uid` int not null,
`cid` int not null
);

View File

@ -0,0 +1,8 @@
CREATE TABLE `conversations_posts` (
`pid` int not null AUTO_INCREMENT,
`cid` int not null,
`createdBy` int not null,
`body` varchar(50) not null,
`post` varchar(50) DEFAULT '' not null,
primary key(`pid`)
);

View File

@ -0,0 +1,8 @@
CREATE TABLE "conversations" (
`cid` serial not null,
`createdBy` int not null,
`createdAt` timestamp not null,
`lastReplyAt` timestamp not null,
`lastReplyBy` int not null,
primary key(`cid`)
);

View File

@ -0,0 +1,4 @@
CREATE TABLE "conversations_participants" (
`uid` int not null,
`cid` int not null
);

View File

@ -0,0 +1,8 @@
CREATE TABLE "conversations_posts" (
`pid` serial not null,
`cid` int not null,
`createdBy` int not null,
`body` varchar (50) not null,
`post` varchar (50) DEFAULT '' not null,
primary key(`pid`)
);

View File

@ -9,6 +9,8 @@
<div class="rowitem passive"><a href="/user/edit/email/">{{lang "account_menu_email"}}</a></div> <div class="rowitem passive"><a href="/user/edit/email/">{{lang "account_menu_email"}}</a></div>
<!--<div class="rowitem passive"><a href="/user/edit/notifications/">{{lang "account_menu_notifications"}}</a> <span class="account_soon">Coming Soon</span></div>--> <!--<div class="rowitem passive"><a href="/user/edit/notifications/">{{lang "account_menu_notifications"}}</a> <span class="account_soon">Coming Soon</span></div>-->
<div class="rowitem passive"><a href="/user/edit/logins/">{{lang "account_menu_logins"}}</a></div> <div class="rowitem passive"><a href="/user/edit/logins/">{{lang "account_menu_logins"}}</a></div>
<!--<div class="rowitem passive"><a href="/user/edit/penalties/">{{lang "account_menu_penalties"}}</a></div>-->
<div class="rowitem passive"><a href="/user/convos/">{{lang "account_menu_messages"}}</a></div>
{{/** TODO: Add an alerts page with pagination to go through alerts which either don't fit in the alerts drop-down or which have already been dismissed. Bear in mind though that dismissed alerts older than two weeks might be purged to save space and to speed up the database **/}} {{/** TODO: Add an alerts page with pagination to go through alerts which either don't fit in the alerts drop-down or which have already been dismissed. Bear in mind though that dismissed alerts older than two weeks might be purged to save space and to speed up the database **/}}
</div> </div>
</nav> </nav>

View File

@ -3,8 +3,4 @@
<h1>{{lang "convo_head"}}</h1> <h1>{{lang "convo_head"}}</h1>
</div> </div>
</div> </div>
{{range .Posts}} <div class="colstack_item hash_hide">{{template "convo_row.html" . }}</div>
<div class="colstack_item">
<div class="rowitem">{{.Body}}</div>
</div>
{{end}}

21
templates/convo_row.html Normal file
View File

@ -0,0 +1,21 @@
{{/** TODO: Temporary hack until we find a more granular way of doing this. Perhaps, a custom include function? **/}}
{{if .Header.Theme.BgAvatars}}
{{range .Posts}}
<div id="post-{{.ID}}" class="rowitem passive deletable_block editable_parent simple {{.ClassName}}" style="background-image:url({{.User.Avatar}}),url(/s/post-avatar-bg.jpg);background-position:0px {{if le .ContentLines 5}}-1{{end}}0px;">
<span class="editable_block user_content simple">{{.Body}}</span>
<span class="controls">
<a href="{{.User.Link}}" class="real_username username">{{.User.Name}}</a>&nbsp;&nbsp;
{{if $.CanModify}}<a href="/user/convo/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_edit_tooltip"}}" aria-label="{{lang "profile_comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
<a href="/user/convo/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_delete_tooltip"}}" aria-label="{{lang "profile_comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>{{end}}
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=convo-reply"><button class="username report_item flag_label" title="{{lang "profile_comments_report_tooltip"}}" aria-label="{{lang "profile_comments_report_aria"}}"></button></a>
{{if .User.Tag}}<a class="username hide_on_mobile user_tag" style="float:right;">{{.User.Tag}}</a>{{end}}
</span>
</div>
{{end}}
{{else}}
{{template "convo_row_alt.html" . }}
{{end}}

View File

@ -0,0 +1,26 @@
{{range .Posts}}
<div id="post-{{.ID}}" class="rowitem passive deletable_block editable_parent comment {{.ClassName}}">
<div class="topRow">
<div class="userbit">
<img src="{{.User.MicroAvatar}}" alt="Avatar" title="{{.User.Name}}'s Avatar" aria-hidden="true" />
<span class="nameAndTitle">
<a href="{{.User.Link}}" class="real_username username">{{.User.Name}}</a>
{{if .User.Tag}}<a class="username hide_on_mobile user_tag" style="float:right;">{{.User.Tag}}</a>{{end}}
</span>
</div>
<span class="controls">
{{if $.CanModify}}
<a href="/user/convo/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_edit_tooltip"}}" aria-label="{{lang "profile_comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
<a href="/user/convo/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_delete_tooltip"}}" aria-label="{{lang "profile_comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>
{{end}}
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=convo-reply"><button class="username report_item flag_label" title="{{lang "profile_comments_report_tooltip"}}" aria-label="{{lang "profile_comments_report_aria"}}"></button></a>
</span>
</div>
<div class="content_column">
<span class="editable_block user_content">{{.Body}}</span>
</div>
</div>
<div class="after_comment"></div>
{{end}}

View File

@ -1,7 +1,7 @@
<div class="colstack_item colstack_head rowhead"> <div class="colstack_item colstack_head rowhead">
<div class="rowitem"> <div class="rowitem">
<h1>{{lang "convos_head"}}</h1> <h1>{{lang "convos_head"}}</h1>
<h2>Create Convo</h2> <h2><a href="/user/convos/create/">Create Convo</a></h2>
</div> </div>
</div> </div>
<div class="colstack_item"> <div class="colstack_item">

View File

@ -3,16 +3,16 @@
<head> <head>
<title>{{.Title}} | {{.Header.Site.Name}}</title> <title>{{.Title}} | {{.Header.Site.Name}}</title>
{{range .Header.Stylesheets}} {{range .Header.Stylesheets}}
<link href="/static/{{.}}" rel="stylesheet" type="text/css">{{end}} <link href="/s/{{.}}" rel="stylesheet" type="text/css">{{end}}
{{range .Header.PreScriptsAsync}} {{range .Header.PreScriptsAsync}}
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}} <script async type="text/javascript" src="/s/{{.}}"></script>{{end}}
<meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" /> <meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" />
<script type="text/javascript" src="/static/init.js?i=8"></script> <script type="text/javascript" src="/s/init.js?i=9"></script>
{{range .Header.ScriptsAsync}} {{range .Header.ScriptsAsync}}
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}} <script async type="text/javascript" src="/s/{{.}}"></script>{{end}}
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script> <script type="text/javascript" src="/s/jquery-3.1.1.min.js"></script>
{{range .Header.Scripts}} {{range .Header.Scripts}}
<script type="text/javascript" src="/static/{{.}}"></script>{{end}} <script type="text/javascript" src="/s/{{.}}"></script>{{end}}
<meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" /> <meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" />
{{if .Header.MetaDesc}}<meta name="description" content="{{.Header.MetaDesc}}" />{{end}} {{if .Header.MetaDesc}}<meta name="description" content="{{.Header.MetaDesc}}" />{{end}}
{{/** TODO: Have page / forum / topic level tags and descriptions below as-well **/}} {{/** TODO: Have page / forum / topic level tags and descriptions below as-well **/}}

View File

@ -3,10 +3,10 @@
</div> </div>
<div id="panel_primary_themes" class="colstack_item panel_themes complex_rowlist"> <div id="panel_primary_themes" class="colstack_item panel_themes complex_rowlist">
{{range .PrimaryThemes}} {{range .PrimaryThemes}}
<div class="theme_row rowitem editable_parent"{{if .FullImage}} style="background-image: url('/static/{{.FullImage}}');background-position: center;background-size: 50%;background-repeat: no-repeat;"{{end}}> <div class="theme_row rowitem editable_parent"{{if .FullImage}} style="background-image:url('/s/{{.FullImage}}');background-position:center;background-size:50%;background-repeat:no-repeat;"{{end}}>
<span style="float: left;"> <span style="float:left;">
<a href="/panel/themes/{{.Name}}" class="editable_block" style="font-size: 17px;">{{.FriendlyName}}</a><br /> <a href="/panel/themes/{{.Name}}" class="editable_block" style="font-size:17px;">{{.FriendlyName}}</a><br />
<small class="panel_theme_author" style="margin-left: 2px;">{{lang "panel_themes_author_prefix"}}<a href="//{{.URL}}">{{.Creator}}</a></small> <small class="panel_theme_author" style="margin-left:2px;">{{lang "panel_themes_author_prefix"}}<a href="//{{.URL}}">{{.Creator}}</a></small>
</span> </span>
<span class="panel_floater"> <span class="panel_floater">
{{if .MobileFriendly}}<span class="panel_tag panel_theme_mobile" title="{{lang "panel_themes_mobile_friendly_tooltip"}}" aria-label="{{lang "panel_themes_mobile_friendly_aria"}}">📱</span>{{end}} {{if .MobileFriendly}}<span class="panel_tag panel_theme_mobile" title="{{lang "panel_themes_mobile_friendly_tooltip"}}" aria-label="{{lang "panel_themes_mobile_friendly_aria"}}">📱</span>{{end}}
@ -22,10 +22,10 @@
</div> </div>
<div id="panel_variant_themes" class="colstack_item panel_themes"> <div id="panel_variant_themes" class="colstack_item panel_themes">
{{range .VariantThemes}} {{range .VariantThemes}}
<div class="theme_row rowitem editable_parent"{{if .FullImage}} style="background-image: url('/static/{{.FullImage}}');background-position: center;background-size: 50%;background-repeat: no-repeat;"{{end}}> <div class="theme_row rowitem editable_parent"{{if .FullImage}} style="background-image:url('/s/{{.FullImage}}');background-position:center;background-size:50%;background-repeat:no-repeat;"{{end}}>
<span style="float: left;"> <span style="float: left;">
<a href="/panel/themes/{{.Name}}" class="editable_block" style="font-size: 17px;">{{.FriendlyName}}</a><br /> <a href="/panel/themes/{{.Name}}" class="editable_block" style="font-size:17px;">{{.FriendlyName}}</a><br />
<small class="panel_theme_author" style="margin-left: 2px;">{{lang "panel_themes_author_prefix"}}<a href="//{{.URL}}">{{.Creator}}</a></small> <small class="panel_theme_author" style="margin-left:2px;">{{lang "panel_themes_author_prefix"}}<a href="//{{.URL}}">{{.Creator}}</a></small>
</span> </span>
<span class="panel_floater"> <span class="panel_floater">
{{if .MobileFriendly}}<span class="panel_tag panel_theme_mobile" title="{{lang "panel_themes_mobile_friendly_tooltip"}}" aria-label="{{lang "panel_themes_mobile_friendly_aria"}}">📱</span>{{end}} {{if .MobileFriendly}}<span class="panel_tag panel_theme_mobile" title="{{lang "panel_themes_mobile_friendly_tooltip"}}" aria-label="{{lang "panel_themes_mobile_friendly_aria"}}">📱</span>{{end}}

View File

@ -27,9 +27,9 @@
{{if not .CurrentUser.Loggedin}}<div class="rowitem passive"> {{if not .CurrentUser.Loggedin}}<div class="rowitem passive">
<a class="profile_menu_item">{{lang "profile_login_for_options"}}</a> <a class="profile_menu_item">{{lang "profile_login_for_options"}}</a>
</div>{{else}} </div>{{else}}
<!--<div class="rowitem passive"> <div class="rowitem passive">
<a href="/user/convos/create/" class="profile_menu_item">{{lang "profile_send_message"}}</a> <a href="/user/convos/create/" class="profile_menu_item">{{lang "profile_send_message"}}</a>
</div>--> </div>
<!--<div class="rowitem passive"> <!--<div class="rowitem passive">
<a class="profile_menu_item">{{lang "profile_add_friend"}}</a> <a class="profile_menu_item">{{lang "profile_add_friend"}}</a>
</div>--> </div>-->

View File

@ -1,21 +1,21 @@
{{/** TODO: Temporary hack until we find a more granular way of doing this. Perhaps, a custom include function? **/}} {{/** TODO: Temporary hack until we find a more granular way of doing this. Perhaps, a custom include function? **/}}
{{if .Header.Theme.BgAvatars}} {{if .Header.Theme.BgAvatars}}
{{range .ItemList}} {{range .ItemList}}
<div id="post-{{.ID}}" class="rowitem passive deletable_block editable_parent simple {{.ClassName}}" style="background-image:url({{.Avatar}}),url(/static/post-avatar-bg.jpg);background-position:0px {{if le .ContentLines 5}}-1{{end}}0px;"> <div id="post-{{.ID}}" class="rowitem passive deletable_block editable_parent simple {{.ClassName}}" style="background-image:url({{.Avatar}}),url(/s/post-avatar-bg.jpg);background-position:0px {{if le .ContentLines 5}}-1{{end}}0px;">
<span class="editable_block user_content simple">{{.ContentHtml}}</span> <span class="editable_block user_content simple">{{.ContentHtml}}</span>
<span class="controls"> <span class="controls">
<a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a>&nbsp;&nbsp; <a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a>&nbsp;&nbsp;
{{if $.CurrentUser.IsMod}}<a href="/profile/reply/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_edit_tooltip"}}" aria-label="{{lang "profile_comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a> {{if $.CurrentUser.IsMod}}<a href="/profile/reply/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_edit_tooltip"}}" aria-label="{{lang "profile_comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
<a href="/profile/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_delete_tooltip"}}" aria-label="{{lang "profile_comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>{{end}} <a href="/profile/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_delete_tooltip"}}" aria-label="{{lang "profile_comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>{{end}}
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=user-reply"><button class="username report_item flag_label" title="{{lang "profile_comments_report_tooltip"}}" aria-label="{{lang "profile_comments_report_aria"}}"></button></a> <a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=user-reply"><button class="username report_item flag_label" title="{{lang "profile_comments_report_tooltip"}}" aria-label="{{lang "profile_comments_report_aria"}}"></button></a>
{{if .Tag}}<a class="username hide_on_mobile user_tag" style="float:right;">{{.Tag}}</a>{{end}} {{if .Tag}}<a class="username hide_on_mobile user_tag" style="float:right;">{{.Tag}}</a>{{end}}
</span> </span>
</div> </div>
{{end}} {{end}}
{{else}} {{else}}
{{template "profile_comments_row_alt.html" . }} {{template "profile_comments_row_alt.html" . }}
{{end}} {{end}}

View File

@ -1,24 +1,24 @@
{{range .ItemList}} {{range .ItemList}}
<div id="post-{{.ID}}" class="rowitem passive deletable_block editable_parent comment {{.ClassName}}"> <div id="post-{{.ID}}" class="rowitem passive deletable_block editable_parent comment {{.ClassName}}">
<div class="topRow"> <div class="topRow">
<div class="userbit"> <div class="userbit">
<img src="{{.MicroAvatar}}" alt="Avatar" title="{{.CreatedByName}}'s Avatar" aria-hidden="true" /> <img src="{{.MicroAvatar}}" alt="Avatar" title="{{.CreatedByName}}'s Avatar" aria-hidden="true" />
<span class="nameAndTitle"> <span class="nameAndTitle">
<a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a> <a href="{{.UserLink}}" class="real_username username">{{.CreatedByName}}</a>
{{if .Tag}}<a class="username hide_on_mobile user_tag" style="float:right;">{{.Tag}}</a>{{end}} {{if .Tag}}<a class="username hide_on_mobile user_tag" style="float:right;">{{.Tag}}</a>{{end}}
</span>
</div>
<span class="controls">
{{if $.CurrentUser.IsMod}}
<a href="/profile/reply/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_edit_tooltip"}}" aria-label="{{lang "profile_comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
<a href="/profile/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_delete_tooltip"}}" aria-label="{{lang "profile_comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>
{{end}}
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=user-reply"><button class="username report_item flag_label" title="{{lang "profile_comments_report_tooltip"}}" aria-label="{{lang "profile_comments_report_aria"}}"></button></a>
</span> </span>
</div> </div>
<div class="content_column"> <span class="controls">
<span class="editable_block user_content">{{.ContentHtml}}</span> {{if $.CurrentUser.IsMod}}
</div> <a href="/profile/reply/edit/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_edit_tooltip"}}" aria-label="{{lang "profile_comments_edit_aria"}}"><button class="username edit_item edit_label"></button></a>
<a href="/profile/reply/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="mod_button" title="{{lang "profile_comments_delete_tooltip"}}" aria-label="{{lang "profile_comments_delete_aria"}}"><button class="username delete_item delete_label"></button></a>
{{end}}
<a class="mod_button" href="/report/submit/{{.ID}}?s={{$.CurrentUser.Session}}&type=user-reply"><button class="username report_item flag_label" title="{{lang "profile_comments_report_tooltip"}}" aria-label="{{lang "profile_comments_report_aria"}}"></button></a>
</span>
</div> </div>
<div class="after_comment"></div> <div class="content_column">
<span class="editable_block user_content">{{.ContentHtml}}</span>
</div>
</div>
<div class="after_comment"></div>
{{end}} {{end}}

View File

@ -31,7 +31,7 @@
{{end}} {{end}}
<article {{scope "opening_post"}} itemscope itemtype="http://schema.org/CreativeWork" class="rowblock post_container top_post" aria-label="{{lang "topic.opening_post_aria"}}"> <article {{scope "opening_post"}} itemscope itemtype="http://schema.org/CreativeWork" class="rowblock post_container top_post" aria-label="{{lang "topic.opening_post_aria"}}">
<div class="rowitem passive editable_parent post_item {{.Topic.ClassName}}" style="background-image: url({{.Topic.Avatar}}), url(/static/{{.Header.Theme.Name}}/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;"> <div class="rowitem passive editable_parent post_item {{.Topic.ClassName}}" style="background-image:url({{.Topic.Avatar}}), url(/s/{{.Header.Theme.Name}}/post-avatar-bg.jpg);background-position:0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;">
<div class="hide_on_edit topic_content user_content" itemprop="text">{{.Topic.ContentHTML}}</div> <div class="hide_on_edit topic_content user_content" itemprop="text">{{.Topic.ContentHTML}}</div>
{{if .CurrentUser.Loggedin}}<textarea name="topic_content" class="show_on_edit topic_content_input edit_source">{{.Topic.Content}}</textarea>{{end}} {{if .CurrentUser.Loggedin}}<textarea name="topic_content" class="show_on_edit topic_content_input edit_source">{{.Topic.Content}}</textarea>{{end}}

View File

@ -1,6 +1,6 @@
<div class="rowblock topic_reply_container"> <div class="rowblock topic_reply_container">
<div class="userinfo" aria-label="{{lang "topic.your_information"}}"> <div class="userinfo" aria-label="{{lang "topic.your_information"}}">
<div class="avatar_item" style="background-image:url({{.CurrentUser.Avatar}}),url(/static/white-dot.jpg);background-position:0px -10px;">&nbsp;</div> <div class="avatar_item" style="background-image:url({{.CurrentUser.Avatar}}),url(/s/white-dot.jpg);background-position:0px -10px;">&nbsp;</div>
<div class="user_meta"> <div class="user_meta">
<a href="{{.CurrentUser.Link}}" class="the_name" rel="author">{{.CurrentUser.Name}}</a> <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}} {{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}}

View File

@ -1,5 +1,5 @@
<div class="userinfo" aria-label="{{lang "topic.userinfo_aria"}}"> <div class="userinfo" aria-label="{{lang "topic.userinfo_aria"}}">
<div class="avatar_item" style="background-image: url({{.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;">&nbsp;</div> <div class="avatar_item" style="background-image:url({{.Avatar}}), url(/s/white-dot.jpg);background-position:0px -10px;">&nbsp;</div>
<div class="user_meta"> <div class="user_meta">
<a href="{{.UserLink}}" class="the_name" rel="author">{{.CreatedByName}}</a> <a href="{{.UserLink}}" class="the_name" rel="author">{{.CreatedByName}}</a>
{{if .Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.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 .Level}}</div><div class="tag_post"></div></div>{{end}} {{if .Tag}}<div class="tag_block"><div class="tag_pre"></div><div class="post_tag">{{.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 .Level}}</div><div class="tag_post"></div></div>{{end}}

View File

@ -1,5 +1,5 @@
<article class="rowblock post_container poll" aria-level="{{lang "topic.poll_aria"}}"> <article class="rowblock post_container poll" aria-level="{{lang "topic.poll_aria"}}">
<div class="rowitem passive editable_parent post_item poll_item {{.Topic.ClassName}}" style="background-image: url({{.Topic.Avatar}}), url(/static/{{.Header.Theme.Name}}/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;"> <div class="rowitem passive editable_parent post_item poll_item {{.Topic.ClassName}}" style="background-image:url({{.Topic.Avatar}}), url(/s/{{.Header.Theme.Name}}/post-avatar-bg.jpg);background-position:0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;">
<div class="topic_content user_content"> <div class="topic_content user_content">
{{range .Poll.QuickOptions}} {{range .Poll.QuickOptions}}
<div class="poll_option"> <div class="poll_option">

View File

@ -5,7 +5,7 @@
<span itemprop="text">{{.ActionType}}</span> <span itemprop="text">{{.ActionType}}</span>
</article> </article>
{{else}} {{else}}
<article {{scope "post"}} id="post-{{.ID}}" itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item {{.ClassName}}" style="background-image: url({{.Avatar}}), url(/static/{{$.Header.Theme.Name}}/post-avatar-bg.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;"> <article {{scope "post"}} id="post-{{.ID}}" itemscope itemtype="http://schema.org/CreativeWork" class="rowitem passive deletable_block editable_parent post_item {{.ClassName}}" style="background-image:url({{.Avatar}}), url(/s/{{$.Header.Theme.Name}}/post-avatar-bg.jpg);background-position:0px {{if le .ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;">
{{/** TODO: We might end up with <br>s in the inline editor, fix this **/}} {{/** TODO: We might end up with <br>s in the inline editor, fix this **/}}
<div class="editable_block user_content" itemprop="text">{{.ContentHtml}}</div> <div class="editable_block user_content" itemprop="text">{{.ContentHtml}}</div>
{{if $.CurrentUser.Loggedin}}<div class="auto_hide edit_source">{{.Content}}</div>{{end}} {{if $.CurrentUser.Loggedin}}<div class="auto_hide edit_source">{{.Content}}</div>{{end}}

View File

View File

@ -17,7 +17,7 @@
//console.log("af") //console.log("af")
let loggedIn = document.head.querySelector("[property='x-loggedin']").content; let loggedIn = document.head.querySelector("[property='x-loggedin']").content;
if(loggedIn) { if(loggedIn) {
if(navigator.userAgent.indexOf("Firefox") != -1) $.trumbowyg.svgPath = "/static/trumbowyg/ui/icons.svg"; if(navigator.userAgent.indexOf("Firefox") != -1) $.trumbowyg.svgPath = "/s/trumbowyg/ui/icons.svg";
// Is there we way we can append instead? Maybe, an editor plugin? // Is there we way we can append instead? Maybe, an editor plugin?
attachItemCallback = function(attachItem) { attachItemCallback = function(attachItem) {

View File

@ -0,0 +1,30 @@
.rowitem .topRow {
display: flex;
width: 100%;
}
.rowitem .userbit {
display: flex;
}
.rowitem .topRow .nameAndTitle {
display: flex;
flex-direction: column;
margin-left: 8px;
}
.nameAndTitle .real_username {
font-size: 17px;
line-height: 16px;
}
.userbit img {
width: 40px;
height: 40px;
border-radius: 24px;
}
.controls {
margin-left: auto;
}
.controls a {
margin-right: 8px;
}
.content_column {
margin-top: 5px;
}

View File

@ -351,6 +351,10 @@ h1, h2, h3, h4, h5 {
.rowhead h1, .opthead h1, .colstack_head h1 { .rowhead h1, .opthead h1, .colstack_head h1 {
font-size: 21px; font-size: 21px;
} }
.rowhead h1 + h2, .opthead h1 + h2, .colstack_right .colstack_head .rowitem h1 + h2 {
margin-left: auto;
}
.sidebar .rowhead { .sidebar .rowhead {
margin-left: 18px; margin-left: 18px;
margin-top: 4px; margin-top: 4px;

View File

@ -16,8 +16,6 @@
width: 64px; width: 64px;
height: 64px; height: 64px;
border-radius: 32px; border-radius: 32px;
margin-left: auto;
margin-right: auto;
} }
.nameRow { .nameRow {
display: flex; display: flex;

View File

View File