Added Quick Topic.
Added Attachments. Added Attachment Media Embeds. Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing. Added petabytes as a unit and cleaned up a few of the friendly units. Refactored the username change logic to make it easier to maintain. Refactored the avatar change logic to make it easier to maintain. Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on! Snuck some CSS Variables into Tempra Conflux. Added the GroupCache interface to MemoryGroupStore. Added the Length method to MemoryGroupStore. Added support for a site short name. Added the UploadFiles permission. Renamed more functions. Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow. Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler. Added support for if statements operating on slices and maps for the template compiler. Fixed a security exploit in reply editing. Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports. Fixed buttons having blue outlines on focus on Shadow. Refactored the topic creation logic to make it easier to maintain. Made a few responsive fixes, but there's still more to do in the following commits!
4
.gitignore
vendored
@ -5,11 +5,15 @@ tmp.txt
|
||||
run_notemplategen.bat
|
||||
brun.bat
|
||||
|
||||
attachs/*
|
||||
!attachs/filler.txt
|
||||
uploads/avatar_*
|
||||
uploads/socialgroup_*
|
||||
backups/*.sql
|
||||
node_modules/*
|
||||
bin/*
|
||||
out/*
|
||||
logs/*
|
||||
*.exe
|
||||
*.exe~
|
||||
*.prof
|
||||
|
1
attachs/filler.txt
Normal file
@ -0,0 +1 @@
|
||||
This file is here so that Git will include this folder in the repository.
|
3
auth.go
@ -117,6 +117,7 @@ func (auth *DefaultAuth) SetCookies(w http.ResponseWriter, uid int, session stri
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
|
||||
// GetCookies fetches the current user's session cookies
|
||||
func (auth *DefaultAuth) GetCookies(r *http.Request) (uid int, session string, err error) {
|
||||
// Are there any session cookies..?
|
||||
cookie, err := r.Cookie("uid")
|
||||
@ -134,6 +135,7 @@ func (auth *DefaultAuth) GetCookies(r *http.Request) (uid int, session string, e
|
||||
return uid, cookie.Value, err
|
||||
}
|
||||
|
||||
// SessionCheck checks if a user has session cookies and whether they're valid
|
||||
func (auth *DefaultAuth) SessionCheck(w http.ResponseWriter, r *http.Request) (user *User, halt bool) {
|
||||
uid, session, err := auth.GetCookies(r)
|
||||
if err != nil {
|
||||
@ -156,6 +158,7 @@ func (auth *DefaultAuth) SessionCheck(w http.ResponseWriter, r *http.Request) (u
|
||||
return user, false
|
||||
}
|
||||
|
||||
// CreateSession generates a new session to allow a remote client to stay logged in as a specific user
|
||||
func (auth *DefaultAuth) CreateSession(uid int) (session string, err error) {
|
||||
session, err = GenerateSafeString(sessionLength)
|
||||
if err != nil {
|
||||
|
1
cache.go
@ -8,6 +8,7 @@ const CACHE_STATIC int = 0
|
||||
const CACHE_DYNAMIC int = 1
|
||||
const CACHE_SQL int = 2
|
||||
|
||||
// nolint
|
||||
// ErrCacheDesync is thrown whenever a piece of data, for instance, a user is out of sync with the database. Currently unused.
|
||||
var ErrCacheDesync = errors.New("The cache is out of sync with the database.") // TODO: A cross-server synchronisation mechanism
|
||||
|
||||
|
@ -2,7 +2,8 @@ package main
|
||||
|
||||
func init() {
|
||||
// Site Info
|
||||
site.Name = "TS"
|
||||
site.ShortName = "TS" // This should be less than three letters to fit in the navbar
|
||||
site.Name = "Test Site"
|
||||
site.Email = ""
|
||||
site.URL = "localhost"
|
||||
site.Port = "8080" // 8080
|
||||
@ -47,7 +48,7 @@ func init() {
|
||||
config.Noavatar = "https://api.adorable.io/avatars/285/{id}@{site_url}.png"
|
||||
config.ItemsPerPage = 25
|
||||
|
||||
// Developer flag
|
||||
// Developer flags
|
||||
dev.DebugMode = true
|
||||
//dev.SuperDebug = true
|
||||
//dev.TemplateDebug = true
|
||||
|
@ -230,6 +230,7 @@ func SecurityError(w http.ResponseWriter, r *http.Request, user User) {
|
||||
}
|
||||
}
|
||||
|
||||
// NotFound is used when the requested page doesn't exist
|
||||
// ? - Add a JSQ and JS version of this?
|
||||
// ? - Add a user parameter?
|
||||
func NotFound(w http.ResponseWriter, r *http.Request) {
|
||||
@ -243,7 +244,7 @@ func NotFound(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// nolint
|
||||
// CustomError lets us make custom error types which aren't covered by the generic functions above
|
||||
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) {
|
||||
w.WriteHeader(errcode)
|
||||
pi := Page{errtitle, user, getDefaultHeaderVar(), tList, errmsg}
|
||||
@ -258,7 +259,7 @@ func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWri
|
||||
}
|
||||
}
|
||||
|
||||
// nolint
|
||||
// CustomErrorJSQ is a version of CustomError which lets us handle both JSON and regular pages depending on how it's being accessed
|
||||
func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User, isJs bool) {
|
||||
if !isJs {
|
||||
CustomError(errmsg, errcode, errtitle, w, r, user)
|
||||
@ -267,7 +268,7 @@ func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.Response
|
||||
}
|
||||
}
|
||||
|
||||
// nolint
|
||||
// CustomErrorJS is the pure JSON version of CustomError
|
||||
func CustomErrorJS(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) {
|
||||
w.WriteHeader(errcode)
|
||||
_, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
|
||||
|
@ -1,333 +0,0 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
font-family: arial;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'EmojiFont';
|
||||
src: url('https://github.com/Ranks/emojione/raw/master/assets/fonts/emojione-svg.woff2') format('woff2'),
|
||||
url('https://github.com/Ranks/emojione/raw/master/assets/fonts/emojione-svg.woff') format('woff'), local("arial");
|
||||
}
|
||||
|
||||
@supports (-ms-ime-align:auto) {
|
||||
.user_content
|
||||
{
|
||||
font-family: EmojiFont, arial;
|
||||
}
|
||||
}
|
||||
@-moz-document url-prefix() {
|
||||
.user_content
|
||||
{
|
||||
font-family: EmojiFont, arial;
|
||||
}
|
||||
}
|
||||
|
||||
.move_left
|
||||
{
|
||||
float: left;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
}
|
||||
.move_right
|
||||
{
|
||||
float: left;
|
||||
position: relative;
|
||||
left: -50%;
|
||||
}
|
||||
ul
|
||||
{
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
height: 28px;
|
||||
list-style-type: none;
|
||||
}
|
||||
li
|
||||
{
|
||||
height: 28px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-left: 10px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
li a
|
||||
{
|
||||
text-decoration: none;
|
||||
color: #515151;
|
||||
}
|
||||
li a:hover
|
||||
{
|
||||
color: #7a7a7a;
|
||||
}
|
||||
.menu_left
|
||||
{
|
||||
float: left;
|
||||
border-right: 1px solid #ccc;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.menu_left:first-child
|
||||
{
|
||||
border-left: 1px solid #ccc
|
||||
}
|
||||
.menu_right
|
||||
{
|
||||
float: right;
|
||||
border-left: 1px solid #ccc;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.container
|
||||
{
|
||||
width: 90%;
|
||||
padding: 0px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.rowblock
|
||||
{
|
||||
border: 1px solid #ccc;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
padding-top: 0px;
|
||||
}
|
||||
.rowblock:empty
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.colblock_left
|
||||
{
|
||||
border: 1px solid #ccc;
|
||||
padding: 0px;
|
||||
padding-top: 0px;
|
||||
width: 30%;
|
||||
float: left;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.colblock_right
|
||||
{
|
||||
border: 1px solid #ccc;
|
||||
padding: 0px;
|
||||
padding-top: 0px;
|
||||
width: 65%;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.colblock_left:empty
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
.colblock_right:empty
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rowitem
|
||||
{
|
||||
width: 100%;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 17px;
|
||||
padding-bottom: 12px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.rowitem.passive
|
||||
{
|
||||
font-weight: normal;
|
||||
text-transform: none;
|
||||
}
|
||||
.rowitem:not(:last-child)/*:not(:only-child)*/
|
||||
{
|
||||
border-bottom: 1px dotted #ccc;
|
||||
}
|
||||
.rowitem a
|
||||
{
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.rowitem a:hover
|
||||
{
|
||||
color: silver;
|
||||
}
|
||||
|
||||
.col_left
|
||||
{
|
||||
width: 30%;
|
||||
float: left;
|
||||
}
|
||||
.col_right
|
||||
{
|
||||
width: 69%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.colitem
|
||||
{
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 17px;
|
||||
padding-bottom: 12px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.colitem.passive
|
||||
{
|
||||
font-weight: normal;
|
||||
text-transform: none;
|
||||
}
|
||||
.colitem a
|
||||
{
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.colitem a:hover
|
||||
{
|
||||
color: silver;
|
||||
}
|
||||
|
||||
.formrow
|
||||
{
|
||||
/*height: 40px;*/
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*Clearfix*/
|
||||
.formrow:before,
|
||||
.formrow:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.formrow:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.formrow:not(:last-child)
|
||||
{
|
||||
border-bottom: 1px dotted #ccc;
|
||||
}
|
||||
|
||||
.formitem
|
||||
{
|
||||
float: left;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 13px;
|
||||
padding-bottom: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.formitem:first-child
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.formitem:not(:last-child)
|
||||
{
|
||||
border-right: 1px dotted #ccc;
|
||||
}
|
||||
|
||||
.formitem.invisible_border
|
||||
{
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Mostly for textareas */
|
||||
.formitem:only-child
|
||||
{
|
||||
width: 97%;
|
||||
}
|
||||
.formitem textarea
|
||||
{
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
}
|
||||
.formitem:has-child()
|
||||
{
|
||||
margin: 0 auto;
|
||||
float: none;
|
||||
}
|
||||
|
||||
button
|
||||
{
|
||||
background: white;
|
||||
border: 1px solid #8e8e8e;
|
||||
}
|
||||
|
||||
/* Topics */
|
||||
.topic_status
|
||||
{
|
||||
text-transform: none;
|
||||
margin-left: 8px;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
background-color: #E8E8E8; /* 232,232,232. All three RGB colours being the same seems to create a shade of gray */
|
||||
color: #505050; /* 80,80,80 */
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.topic_status:empty
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.username
|
||||
{
|
||||
text-transform: none;
|
||||
margin-left: 0px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
color: #505050; /* 80,80,80 */
|
||||
background-color: #FFFFFF;
|
||||
border-style: dotted;
|
||||
border-color: #505050; /* 232,232,232. All three RGB colours being the same seems to create a shade of gray */
|
||||
border-width: 1px;
|
||||
font-size: 15px;
|
||||
}
|
||||
button.username
|
||||
{
|
||||
position: relative;
|
||||
top: -0.25px;
|
||||
}
|
||||
|
||||
.show_on_edit
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert
|
||||
{
|
||||
display: block;
|
||||
padding: 5px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.alert_success
|
||||
{
|
||||
display: block;
|
||||
padding: 5px;
|
||||
border: 1px solid A2FC00;
|
||||
margin-bottom: 10px;
|
||||
background-color: DAF7A6;
|
||||
}
|
||||
.alert_error
|
||||
{
|
||||
display: block;
|
||||
padding: 5px;
|
||||
border: 1px solid #FF004B;
|
||||
margin-bottom: 8px;
|
||||
background-color: #FEB7CC;
|
||||
}
|
22
extend.go
@ -32,6 +32,28 @@ var vhooks = map[string]func(...interface{}) interface{}{
|
||||
"topic_create_pre_loop": nil,
|
||||
}
|
||||
|
||||
// Coming Soon:
|
||||
type Message interface {
|
||||
ID() int
|
||||
Poster() int
|
||||
Contents() string
|
||||
ParsedContents() string
|
||||
}
|
||||
|
||||
// While the idea is nice, this might result in too much code duplication, as we have seventy billion page structs, what else could we do to get static typing with these in plugins?
|
||||
type PageInt interface {
|
||||
Title() string
|
||||
HeaderVars() *HeaderVars
|
||||
CurrentUser() *User
|
||||
GetExtData(name string) interface{}
|
||||
SetExtData(name string, contents interface{})
|
||||
}
|
||||
|
||||
// Coming Soon:
|
||||
var messageHooks = map[string][]func(Message, PageInt, ...interface{}) interface{}{
|
||||
"topic_reply_row_assign": nil,
|
||||
}
|
||||
|
||||
// Hooks which take in and spit out a string. This is usually used for parser components
|
||||
var sshooks = map[string][]func(string) string{
|
||||
"preparse_preassign": nil,
|
||||
|
1
forum.go
@ -46,6 +46,7 @@ type ForumSimple struct {
|
||||
Preset string
|
||||
}
|
||||
|
||||
// Copy gives you a non-pointer concurrency safe copy of the forum
|
||||
func (forum *Forum) Copy() (fcopy Forum) {
|
||||
//forum.LastLock.RLock()
|
||||
fcopy = *forum
|
||||
|
@ -43,14 +43,14 @@ type ForumStore interface {
|
||||
//GetFirstChild(parentID int, parentType string) (*Forum,error)
|
||||
Create(forumName string, forumDesc string, active bool, preset string) (int, error)
|
||||
|
||||
GetGlobalCount() int
|
||||
GlobalCount() int
|
||||
}
|
||||
|
||||
type ForumCache interface {
|
||||
CacheGet(id int) (*Forum, error)
|
||||
CacheSet(forum *Forum) error
|
||||
CacheDelete(id int)
|
||||
GetLength() int
|
||||
Length() int
|
||||
}
|
||||
|
||||
// MemoryForumStore is a struct which holds an arbitrary number of forums in memory, usually all of them, although we might introduce functionality to hold a smaller subset in memory for sites with an extremely large number of forums
|
||||
@ -385,7 +385,8 @@ func (mfs *MemoryForumStore) Create(forumName string, forumDesc string, active b
|
||||
}
|
||||
|
||||
// ! Might be slightly inaccurate, if the sync.Map is constantly shifting and churning, but it'll stabilise eventually. Also, slow. Don't use this on every request x.x
|
||||
func (mfs *MemoryForumStore) GetLength() (length int) {
|
||||
// Length returns the number of forums in the memory cache
|
||||
func (mfs *MemoryForumStore) Length() (length int) {
|
||||
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
|
||||
length++
|
||||
return true
|
||||
@ -394,8 +395,8 @@ func (mfs *MemoryForumStore) GetLength() (length int) {
|
||||
}
|
||||
|
||||
// TODO: Get the total count of forums in the forum store minus the blanked forums rather than doing a heavy query for this?
|
||||
// GetGlobalCount returns the total number of forums
|
||||
func (mfs *MemoryForumStore) GetGlobalCount() (fcount int) {
|
||||
// GlobalCount returns the total number of forums
|
||||
func (mfs *MemoryForumStore) GlobalCount() (fcount int) {
|
||||
err := mfs.getForumCount.QueryRow().Scan(&fcount)
|
||||
if err != nil {
|
||||
LogError(err)
|
||||
|
14
gen_mysql.go
@ -43,6 +43,7 @@ var groupEntryExistsStmt *sql.Stmt
|
||||
var getForumTopicsOffsetStmt *sql.Stmt
|
||||
var getExpiredScheduledGroupsStmt *sql.Stmt
|
||||
var getSyncStmt *sql.Stmt
|
||||
var getAttachmentStmt *sql.Stmt
|
||||
var getTopicRepliesOffsetStmt *sql.Stmt
|
||||
var getTopicListStmt *sql.Stmt
|
||||
var getTopicUserStmt *sql.Stmt
|
||||
@ -68,6 +69,7 @@ var addThemeStmt *sql.Stmt
|
||||
var createGroupStmt *sql.Stmt
|
||||
var addModlogEntryStmt *sql.Stmt
|
||||
var addAdminlogEntryStmt *sql.Stmt
|
||||
var addAttachmentStmt *sql.Stmt
|
||||
var createWordFilterStmt *sql.Stmt
|
||||
var addForumPermsToGroupStmt *sql.Stmt
|
||||
var replaceScheduleGroupStmt *sql.Stmt
|
||||
@ -341,6 +343,12 @@ func _gen_mysql() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Print("Preparing getAttachment statement.")
|
||||
getAttachmentStmt, err = db.Prepare("SELECT `sectionID`,`sectionTable`,`originID`,`originTable`,`uploadedBy`,`path` FROM `attachments` WHERE `path` = ? AND `sectionID` = ? AND `sectionTable` = ?")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Print("Preparing getTopicRepliesOffset statement.")
|
||||
getTopicRepliesOffsetStmt, err = db.Prepare("SELECT `replies`.`rid`,`replies`.`content`,`replies`.`createdBy`,`replies`.`createdAt`,`replies`.`lastEdit`,`replies`.`lastEditBy`,`users`.`avatar`,`users`.`name`,`users`.`group`,`users`.`url_prefix`,`users`.`url_name`,`users`.`level`,`replies`.`ipaddress`,`replies`.`likeCount`,`replies`.`actionType` FROM `replies` LEFT JOIN `users` ON `replies`.`createdBy` = `users`.`uid` WHERE `tid` = ? LIMIT ?,?")
|
||||
if err != nil {
|
||||
@ -491,6 +499,12 @@ func _gen_mysql() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Print("Preparing addAttachment statement.")
|
||||
addAttachmentStmt, err = db.Prepare("INSERT INTO `attachments`(`sectionID`,`sectionTable`,`originID`,`originTable`,`uploadedBy`,`path`) VALUES (?,?,?,?,?,?)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Print("Preparing createWordFilter statement.")
|
||||
createWordFilterStmt, err = db.Prepare("INSERT INTO `word_filters`(`find`,`replacement`) VALUES (?,?)")
|
||||
if err != nil {
|
||||
|
@ -107,6 +107,9 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
case "/theme":
|
||||
routeChangeTheme(w,req,user)
|
||||
return
|
||||
case "/attachs":
|
||||
routeShowAttachment(w,req,user,extra_data)
|
||||
return
|
||||
case "/report":
|
||||
switch(req.URL.Path) {
|
||||
case "/report/submit/":
|
||||
|
@ -895,36 +895,37 @@ func BenchmarkCustomRouterSerial(b *testing.B) {
|
||||
})
|
||||
}*/
|
||||
|
||||
// TODO: Take the attachment system into account in these parser benches
|
||||
func BenchmarkParserSerial(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.Run("empty_post", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = parseMessage("")
|
||||
_ = parseMessage("", 0, "")
|
||||
}
|
||||
})
|
||||
b.Run("short_post", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = parseMessage("Hey everyone, how's it going?")
|
||||
_ = parseMessage("Hey everyone, how's it going?", 0, "")
|
||||
}
|
||||
})
|
||||
b.Run("one_smily", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = parseMessage("Hey everyone, how's it going? :)")
|
||||
_ = parseMessage("Hey everyone, how's it going? :)", 0, "")
|
||||
}
|
||||
})
|
||||
b.Run("five_smilies", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = parseMessage("Hey everyone, how's it going? :):):):):)")
|
||||
_ = parseMessage("Hey everyone, how's it going? :):):):):)", 0, "")
|
||||
}
|
||||
})
|
||||
b.Run("ten_smilies", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = parseMessage("Hey everyone, how's it going? :):):):):):):):):):)")
|
||||
_ = parseMessage("Hey everyone, how's it going? :):):):):):):):):):)", 0, "")
|
||||
}
|
||||
})
|
||||
b.Run("twenty_smilies", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = parseMessage("Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)")
|
||||
_ = parseMessage("Hey everyone, how's it going? :):):):):):):):):):):):):):):):):):):):)", 0, "")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
2
group.go
@ -27,6 +27,8 @@ type Group struct {
|
||||
CanSee []int // The IDs of the forums this group can see
|
||||
}
|
||||
|
||||
// ! Ahem, don't listen to the comment below. It's not concurrency safe right now.
|
||||
// Copy gives you a non-pointer concurrency safe copy of the group
|
||||
func (group *Group) Copy() Group {
|
||||
return *group
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ var groupCreateMutex sync.Mutex
|
||||
var groupUpdateMutex sync.Mutex
|
||||
var gstore GroupStore
|
||||
|
||||
// ? - We could fallback onto the database when an item can't be found in the cache?
|
||||
type GroupStore interface {
|
||||
LoadGroups() error
|
||||
DirtyGet(id int) *Group
|
||||
@ -23,6 +24,10 @@ type GroupStore interface {
|
||||
GetRange(lower int, higher int) ([]*Group, error)
|
||||
}
|
||||
|
||||
type GroupCache interface {
|
||||
Length() int
|
||||
}
|
||||
|
||||
type MemoryGroupStore struct {
|
||||
groups []*Group // TODO: Use a sync.Map instead of a slice
|
||||
groupCapCount int
|
||||
@ -203,3 +208,7 @@ func (mgs *MemoryGroupStore) GetRange(lower int, higher int) (groups []*Group, e
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (mgs *MemoryGroupStore) Length() int {
|
||||
return len(mgs.groups)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 286 KiB After Width: | Height: | Size: 296 KiB |
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 301 KiB |
@ -28,12 +28,17 @@ var dbUsername string
|
||||
var dbPassword string
|
||||
var dbName string
|
||||
var dbPort string
|
||||
var siteName, siteURL, serverPort string
|
||||
|
||||
var siteShortName string
|
||||
var siteName string
|
||||
var siteURL string
|
||||
var serverPort string
|
||||
|
||||
var defaultAdapter = "mysql"
|
||||
var defaultHost = "localhost"
|
||||
var defaultUsername = "root"
|
||||
var defaultDbname = "gosora"
|
||||
var defaultSiteShortName = "SN"
|
||||
var defaultSiteName = "Site Name"
|
||||
var defaultsiteURL = "localhost"
|
||||
var defaultServerPort = "80" // 8080's a good one, if you're testing and don't want it to clash with port 80
|
||||
@ -145,57 +150,58 @@ func main() {
|
||||
configContents := []byte(`package main
|
||||
|
||||
func init() {
|
||||
// Site Info
|
||||
site.Name = "` + siteName + `"
|
||||
site.Email = ""
|
||||
site.URL = "` + siteURL + `"
|
||||
site.Port = "` + serverPort + `"
|
||||
site.EnableSsl = false
|
||||
site.EnableEmails = false
|
||||
site.HasProxy = false // Cloudflare counts as this, if it's sitting in the middle
|
||||
config.SslPrivkey = ""
|
||||
config.SslFullchain = ""
|
||||
site.Language = "english"
|
||||
// Site Info
|
||||
site.ShortName = "` + siteShortName + `" // This should be less than three letters to fit in the navbar
|
||||
site.Name = "` + siteName + `"
|
||||
site.Email = ""
|
||||
site.URL = "` + siteURL + `"
|
||||
site.Port = "` + serverPort + `"
|
||||
site.EnableSsl = false
|
||||
site.EnableEmails = false
|
||||
site.HasProxy = false // Cloudflare counts as this, if it's sitting in the middle
|
||||
config.SslPrivkey = ""
|
||||
config.SslFullchain = ""
|
||||
site.Language = "english"
|
||||
|
||||
// Database details
|
||||
dbConfig.Host = "` + dbHost + `"
|
||||
dbConfig.Username = "` + dbUsername + `"
|
||||
dbConfig.Password = "` + dbPassword + `"
|
||||
dbConfig.Dbname = "` + dbName + `"
|
||||
dbConfig.Port = "` + dbPort + `" // You probably won't need to change this
|
||||
// Database details
|
||||
dbConfig.Host = "` + dbHost + `"
|
||||
dbConfig.Username = "` + dbUsername + `"
|
||||
dbConfig.Password = "` + dbPassword + `"
|
||||
dbConfig.Dbname = "` + dbName + `"
|
||||
dbConfig.Port = "` + dbPort + `" // You probably won't need to change this
|
||||
|
||||
// Limiters
|
||||
config.MaxRequestSize = 5 * megabyte
|
||||
// Limiters
|
||||
config.MaxRequestSize = 5 * megabyte
|
||||
|
||||
// Caching
|
||||
config.CacheTopicUser = CACHE_STATIC
|
||||
config.UserCacheCapacity = 120 // The max number of users held in memory
|
||||
config.TopicCacheCapacity = 200 // The max number of topics held in memory
|
||||
// Caching
|
||||
config.CacheTopicUser = CACHE_STATIC
|
||||
config.UserCacheCapacity = 120 // The max number of users held in memory
|
||||
config.TopicCacheCapacity = 200 // The max number of topics held in memory
|
||||
|
||||
// Email
|
||||
config.SMTPServer = ""
|
||||
config.SMTPUsername = ""
|
||||
config.SMTPPassword = ""
|
||||
config.SMTPPort = "25"
|
||||
// Email
|
||||
config.SMTPServer = ""
|
||||
config.SMTPUsername = ""
|
||||
config.SMTPPassword = ""
|
||||
config.SMTPPort = "25"
|
||||
|
||||
// Misc
|
||||
config.DefaultRoute = routeTopics
|
||||
config.DefaultGroup = 3 // Should be a setting in the database
|
||||
config.ActivationGroup = 5 // Should be a setting in the database
|
||||
config.StaffCSS = "staff_post"
|
||||
config.DefaultForum = 2
|
||||
config.MinifyTemplates = true
|
||||
config.MultiServer = false // Experimental: Enable Cross-Server Synchronisation and several other features
|
||||
// Misc
|
||||
config.DefaultRoute = routeTopics
|
||||
config.DefaultGroup = 3 // Should be a setting in the database
|
||||
config.ActivationGroup = 5 // Should be a setting in the database
|
||||
config.StaffCSS = "staff_post"
|
||||
config.DefaultForum = 2
|
||||
config.MinifyTemplates = true
|
||||
config.MultiServer = false // Experimental: Enable Cross-Server Synchronisation and several other features
|
||||
|
||||
//config.Noavatar = "https://api.adorable.io/avatars/{width}/{id}@{site_url}.png"
|
||||
config.Noavatar = "https://api.adorable.io/avatars/285/{id}@{site_url}.png"
|
||||
config.ItemsPerPage = 25
|
||||
//config.Noavatar = "https://api.adorable.io/avatars/{width}/{id}@{site_url}.png"
|
||||
config.Noavatar = "https://api.adorable.io/avatars/285/{id}@{site_url}.png"
|
||||
config.ItemsPerPage = 25
|
||||
|
||||
// Developer flag
|
||||
dev.DebugMode = true
|
||||
//dev.SuperDebug = true
|
||||
//dev.TemplateDebug = true
|
||||
//dev.Profiling = true
|
||||
// Developer flags
|
||||
dev.DebugMode = true
|
||||
//dev.SuperDebug = true
|
||||
//dev.TemplateDebug = true
|
||||
//dev.Profiling = true
|
||||
}
|
||||
`)
|
||||
|
||||
@ -294,6 +300,17 @@ func getSiteDetails() bool {
|
||||
}
|
||||
fmt.Println("Set the site name to " + siteName)
|
||||
|
||||
// ? - We could compute this based on the first letter of each word in the site's name, if it's name spans multiple words. I'm not sure how to do this for single word names.
|
||||
fmt.Println("Can we have a short abbreviation for your site? Default: " + defaultSiteShortName)
|
||||
if !scanner.Scan() {
|
||||
return false
|
||||
}
|
||||
siteShortName = scanner.Text()
|
||||
if siteShortName == "" {
|
||||
siteShortName = defaultSiteShortName
|
||||
}
|
||||
fmt.Println("Set the site name to " + siteShortName)
|
||||
|
||||
fmt.Println("What's your site's url? Default: " + defaultsiteURL)
|
||||
if !scanner.Scan() {
|
||||
return false
|
||||
|
@ -23,7 +23,9 @@
|
||||
"ManageThemes": "Can manage themes",
|
||||
"ManagePlugins": "Can manage plugins",
|
||||
"ViewAdminLogs": "Can view the administrator action logs",
|
||||
"ViewIPs": "Can view IP addresses"
|
||||
"ViewIPs": "Can view IP addresses",
|
||||
|
||||
"UploadFiles": "Can upload files"
|
||||
},
|
||||
"LocalPerms": {
|
||||
"ViewTopic": "Can view topics",
|
||||
|
25
main.go
@ -27,6 +27,7 @@ const kilobyte int = 1024
|
||||
const megabyte int = kilobyte * 1024
|
||||
const gigabyte int = megabyte * 1024
|
||||
const terabyte int = gigabyte * 1024
|
||||
const petabyte int = terabyte * 1024
|
||||
const saltLength int = 32
|
||||
const sessionLength int = 80
|
||||
|
||||
@ -37,6 +38,30 @@ var startTime time.Time
|
||||
var externalSites = map[string]string{
|
||||
"YT": "https://www.youtube.com/",
|
||||
}
|
||||
|
||||
type StringList []string
|
||||
|
||||
// ? - Should we allow users to upload .php or .go files? It could cause security issues. We could store them with a mangled extension to render them inert
|
||||
// TODO: Let admins manage this from the Control Panel
|
||||
var allowedFileExts = StringList{
|
||||
"png", "jpg", "jpeg", "svg", "bmp", "gif",
|
||||
"txt", "xml", "json", "yaml", "js", "py", "rb",
|
||||
"mp3", "mp4", "avi", "wmv",
|
||||
}
|
||||
var imageFileExts = StringList{
|
||||
"png", "jpg", "jpeg", "svg", "bmp", "gif",
|
||||
}
|
||||
|
||||
// TODO: Write a test for this
|
||||
func (slice StringList) Contains(needle string) bool {
|
||||
for _, item := range slice {
|
||||
if item == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var staticFiles = make(map[string]SFile)
|
||||
var logWriter = io.MultiWriter(os.Stderr)
|
||||
|
||||
|
207
member_routes.go
@ -1,12 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"html"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -101,9 +104,17 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user User, sfid st
|
||||
|
||||
// POST functions. Authorised users only.
|
||||
func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) {
|
||||
err := r.ParseForm()
|
||||
// TODO: Reduce this to 1MB for attachments for each file?
|
||||
if r.ContentLength > int64(config.MaxRequestSize) {
|
||||
size, unit := convertByteUnit(float64(config.MaxRequestSize))
|
||||
CustomError("Your attachments are too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, user)
|
||||
return
|
||||
}
|
||||
r.Body = http.MaxBytesReader(w, r.Body, int64(config.MaxRequestSize))
|
||||
|
||||
err := r.ParseMultipartForm(int64(megabyte))
|
||||
if err != nil {
|
||||
PreError("Bad Form", w, r)
|
||||
LocalError("Unable to parse the form", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
@ -131,35 +142,110 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) {
|
||||
return
|
||||
}
|
||||
|
||||
wcount := wordCount(content)
|
||||
res, err := createTopicStmt.Exec(fid, topicName, content, parseMessage(content), user.ID, ipaddress, wcount, user.ID)
|
||||
tid, err := topics.Create(fid, topicName, content, user.ID, ipaddress)
|
||||
if err != nil {
|
||||
InternalError(err, w)
|
||||
switch err {
|
||||
case ErrNoRows:
|
||||
LocalError("Something went wrong, perhaps the forum got deleted?", w, r, user)
|
||||
case ErrNoTitle:
|
||||
LocalError("This topic doesn't have a title", w, r, user)
|
||||
case ErrNoBody:
|
||||
LocalError("This topic doesn't have a body", w, r, user)
|
||||
default:
|
||||
InternalError(err, w)
|
||||
}
|
||||
return
|
||||
}
|
||||
lastID, err := res.LastInsertId()
|
||||
|
||||
_, err = addSubscriptionStmt.Exec(user.ID, tid, "topic")
|
||||
if err != nil {
|
||||
InternalError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = addSubscriptionStmt.Exec(user.ID, lastID, "topic")
|
||||
err = user.increasePostStats(wordCount(content), true)
|
||||
if err != nil {
|
||||
InternalError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/topic/"+strconv.FormatInt(lastID, 10), http.StatusSeeOther)
|
||||
err = user.increasePostStats(wcount, true)
|
||||
if err != nil {
|
||||
InternalError(err, w)
|
||||
return
|
||||
// Handle the file attachments
|
||||
if user.Perms.UploadFiles {
|
||||
var mpartFiles = r.MultipartForm.File
|
||||
if len(mpartFiles) > 5 {
|
||||
LocalError("You can't attach more than five files", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
for _, fheaders := range r.MultipartForm.File {
|
||||
for _, hdr := range fheaders {
|
||||
log.Print("hdr.Filename ", hdr.Filename)
|
||||
extarr := strings.Split(hdr.Filename, ".")
|
||||
if len(extarr) < 2 {
|
||||
LocalError("Bad file", w, r, user)
|
||||
return
|
||||
}
|
||||
ext := extarr[len(extarr)-1]
|
||||
|
||||
// TODO: Can we do this without a regex?
|
||||
reg, err := regexp.Compile("[^A-Za-z0-9]+")
|
||||
if err != nil {
|
||||
LocalError("Bad file extension", w, r, user)
|
||||
return
|
||||
}
|
||||
ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
|
||||
if !allowedFileExts.Contains(ext) {
|
||||
LocalError("You're not allowed this upload files with this extension", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
infile, err := hdr.Open()
|
||||
if err != nil {
|
||||
LocalError("Upload failed", w, r, user)
|
||||
return
|
||||
}
|
||||
defer infile.Close()
|
||||
|
||||
hasher := sha256.New()
|
||||
_, err = io.Copy(hasher, infile)
|
||||
if err != nil {
|
||||
LocalError("Upload failed [Hashing Failed]", w, r, user)
|
||||
return
|
||||
}
|
||||
infile.Close()
|
||||
|
||||
checksum := hex.EncodeToString(hasher.Sum(nil))
|
||||
filename := checksum + "." + ext
|
||||
outfile, err := os.Create("." + "/attachs/" + filename)
|
||||
if err != nil {
|
||||
LocalError("Upload failed [File Creation Failed]", w, r, user)
|
||||
return
|
||||
}
|
||||
defer outfile.Close()
|
||||
|
||||
infile, err = hdr.Open()
|
||||
if err != nil {
|
||||
LocalError("Upload failed", w, r, user)
|
||||
return
|
||||
}
|
||||
defer infile.Close()
|
||||
|
||||
_, err = io.Copy(outfile, infile)
|
||||
if err != nil {
|
||||
LocalError("Upload failed [Copy Failed]", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = addAttachmentStmt.Exec(fid, "forums", tid, "topics", user.ID, filename)
|
||||
if err != nil {
|
||||
InternalError(err, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = fstore.AddTopic(int(lastID), user.ID, fid)
|
||||
if err != nil && err != ErrNoRows {
|
||||
InternalError(err, w)
|
||||
}
|
||||
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
|
||||
@ -201,7 +287,7 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
|
||||
}
|
||||
|
||||
wcount := wordCount(content)
|
||||
_, err = createReplyStmt.Exec(tid, content, parseMessage(content), ipaddress, wcount, user.ID)
|
||||
_, err = createReplyStmt.Exec(tid, content, parseMessage(content, topic.ParentID, "forums"), ipaddress, wcount, user.ID)
|
||||
if err != nil {
|
||||
InternalError(err, w)
|
||||
return
|
||||
@ -475,7 +561,8 @@ func routeProfileReplyCreate(w http.ResponseWriter, r *http.Request, user User)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = createProfileReplyStmt.Exec(uid, html.EscapeString(preparseMessage(r.PostFormValue("reply-content"))), parseMessage(html.EscapeString(preparseMessage(r.PostFormValue("reply-content")))), user.ID, ipaddress)
|
||||
content := html.EscapeString(preparseMessage(r.PostFormValue("reply-content")))
|
||||
_, err = createProfileReplyStmt.Exec(uid, content, parseMessage(content, 0, ""), user.ID, ipaddress)
|
||||
if err != nil {
|
||||
InternalError(err, w)
|
||||
return
|
||||
@ -605,8 +692,9 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemI
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Repost attachments in the reports forum, so that the mods can see them
|
||||
// ? - Can we do this via the TopicStore?
|
||||
res, err := createReportStmt.Exec(title, content, parseMessage(content), user.ID, itemType+"_"+strconv.Itoa(itemID))
|
||||
res, err := createReportStmt.Exec(title, content, parseMessage(content, 0, ""), user.ID, itemType+"_"+strconv.Itoa(itemID))
|
||||
if err != nil {
|
||||
InternalError(err, w)
|
||||
return
|
||||
@ -728,7 +816,8 @@ func routeAccountOwnEditAvatar(w http.ResponseWriter, r *http.Request, user User
|
||||
|
||||
func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, user User) {
|
||||
if r.ContentLength > int64(config.MaxRequestSize) {
|
||||
http.Error(w, "Request too large", http.StatusExpectationFailed)
|
||||
size, unit := convertByteUnit(float64(config.MaxRequestSize))
|
||||
CustomError("Your avatar's too big. Avatars must be smaller than "+strconv.Itoa(int(size))+unit, http.StatusExpectationFailed, "Error", w, r, user)
|
||||
return
|
||||
}
|
||||
r.Body = http.MaxBytesReader(w, r.Body, int64(config.MaxRequestSize))
|
||||
@ -742,14 +831,13 @@ func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, use
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseMultipartForm(int64(config.MaxRequestSize))
|
||||
err := r.ParseMultipartForm(int64(megabyte))
|
||||
if err != nil {
|
||||
LocalError("Upload failed", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
var filename string
|
||||
var ext string
|
||||
var filename, ext string
|
||||
for _, fheaders := range r.MultipartForm.File {
|
||||
for _, hdr := range fheaders {
|
||||
infile, err := hdr.Open()
|
||||
@ -760,6 +848,7 @@ func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, use
|
||||
defer infile.Close()
|
||||
|
||||
// We don't want multiple files
|
||||
// TODO: Check the length of r.MultipartForm.File and error rather than doing this x.x
|
||||
if filename != "" {
|
||||
if filename != hdr.Filename {
|
||||
os.Remove("./uploads/avatar_" + strconv.Itoa(user.ID) + "." + ext)
|
||||
@ -778,6 +867,7 @@ func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, use
|
||||
}
|
||||
ext = extarr[len(extarr)-1]
|
||||
|
||||
// TODO: Can we do this without a regex?
|
||||
reg, err := regexp.Compile("[^A-Za-z0-9]+")
|
||||
if err != nil {
|
||||
LocalError("Bad file extension", w, r, user)
|
||||
@ -802,16 +892,12 @@ func routeAccountOwnEditAvatarSubmit(w http.ResponseWriter, r *http.Request, use
|
||||
}
|
||||
}
|
||||
|
||||
_, err = setAvatarStmt.Exec("."+ext, strconv.Itoa(user.ID))
|
||||
err = user.ChangeAvatar("." + ext)
|
||||
if err != nil {
|
||||
InternalError(err, w)
|
||||
return
|
||||
}
|
||||
user.Avatar = "/uploads/avatar_" + strconv.Itoa(user.ID) + "." + ext
|
||||
ucache, ok := users.(UserCache)
|
||||
if ok {
|
||||
ucache.CacheRemove(user.ID)
|
||||
}
|
||||
|
||||
headerVars.NoticeList = append(headerVars.NoticeList, "Your avatar was successfully updated")
|
||||
pi := Page{"Edit Avatar", user, headerVars, tList, nil}
|
||||
@ -857,18 +943,12 @@ func routeAccountOwnEditUsernameSubmit(w http.ResponseWriter, r *http.Request, u
|
||||
}
|
||||
|
||||
newUsername := html.EscapeString(r.PostFormValue("account-new-username"))
|
||||
_, err = setUsernameStmt.Exec(newUsername, strconv.Itoa(user.ID))
|
||||
err = user.ChangeName(newUsername)
|
||||
if err != nil {
|
||||
LocalError("Unable to change the username. Does someone else already have this name?", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Use the reloaded data instead for the name?
|
||||
user.Name = newUsername
|
||||
ucache, ok := users.(UserCache)
|
||||
if ok {
|
||||
ucache.CacheRemove(user.ID)
|
||||
}
|
||||
|
||||
headerVars.NoticeList = append(headerVars.NoticeList, "Your username was successfully updated")
|
||||
pi := Page{"Edit Username", user, headerVars, tList, nil}
|
||||
@ -1022,3 +1102,60 @@ func routeLogout(w http.ResponseWriter, r *http.Request, user User) {
|
||||
auth.Logout(w, user.ID)
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func routeShowAttachment(w http.ResponseWriter, r *http.Request, user User, filename string) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
PreError("Bad Form", w, r)
|
||||
return
|
||||
}
|
||||
|
||||
filename = Stripslashes(filename)
|
||||
var ext = filepath.Ext("./attachs/" + filename)
|
||||
//log.Print("ext ", ext)
|
||||
//log.Print("filename ", filename)
|
||||
if !allowedFileExts.Contains(strings.TrimPrefix(ext, ".")) {
|
||||
LocalError("Bad extension", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
sectionID, err := strconv.Atoi(r.FormValue("sectionID"))
|
||||
if err != nil {
|
||||
LocalError("The sectionID is not an integer", w, r, user)
|
||||
return
|
||||
}
|
||||
var sectionTable = r.FormValue("sectionType")
|
||||
|
||||
var originTable string
|
||||
var originID, uploadedBy int
|
||||
err = getAttachmentStmt.QueryRow(filename, sectionID, sectionTable).Scan(§ionID, §ionTable, &originID, &originTable, &uploadedBy, &filename)
|
||||
if err == ErrNoRows {
|
||||
NotFound(w, r)
|
||||
return
|
||||
} else if err != nil {
|
||||
InternalError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
if sectionTable == "forums" {
|
||||
_, ok := SimpleForumUserCheck(w, r, &user, sectionID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !user.Perms.ViewTopic {
|
||||
NoPermissions(w, r, user)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
LocalError("Unknown section", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
if originTable != "topics" && originTable != "replies" {
|
||||
LocalError("Unknown origin", w, r, user)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Fix the problem where non-existent files aren't greeted with custom 404s on ServeFile()'s side
|
||||
http.ServeFile(w, r, "./attachs/"+filename)
|
||||
}
|
||||
|
22
misc_test.go
@ -28,7 +28,7 @@ func userStoreTest(t *testing.T) {
|
||||
var length int
|
||||
|
||||
ucache, hasCache := users.(UserCache)
|
||||
if hasCache && ucache.GetLength() != 0 {
|
||||
if hasCache && ucache.Length() != 0 {
|
||||
t.Error("Initial ucache length isn't zero")
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ func userStoreTest(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if hasCache && ucache.GetLength() != 0 {
|
||||
if hasCache && ucache.Length() != 0 {
|
||||
t.Error("There shouldn't be anything in the user cache")
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ func userStoreTest(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if hasCache && ucache.GetLength() != 0 {
|
||||
if hasCache && ucache.Length() != 0 {
|
||||
t.Error("There shouldn't be anything in the user cache")
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ func userStoreTest(t *testing.T) {
|
||||
}
|
||||
|
||||
if hasCache {
|
||||
length = ucache.GetLength()
|
||||
length = ucache.Length()
|
||||
if length != 1 {
|
||||
t.Error("User cache length should be 1, not " + strconv.Itoa(length))
|
||||
}
|
||||
@ -83,7 +83,7 @@ func userStoreTest(t *testing.T) {
|
||||
}
|
||||
|
||||
ucache.Flush()
|
||||
length = ucache.GetLength()
|
||||
length = ucache.Length()
|
||||
if length != 0 {
|
||||
t.Error("User cache length should be 0, not " + strconv.Itoa(length))
|
||||
}
|
||||
@ -97,7 +97,7 @@ func userStoreTest(t *testing.T) {
|
||||
}
|
||||
|
||||
if hasCache {
|
||||
length = ucache.GetLength()
|
||||
length = ucache.Length()
|
||||
if length != 0 {
|
||||
t.Error("User cache length should be 0, not " + strconv.Itoa(length))
|
||||
}
|
||||
@ -109,7 +109,7 @@ func userStoreTest(t *testing.T) {
|
||||
}
|
||||
|
||||
if hasCache {
|
||||
length = ucache.GetLength()
|
||||
length = ucache.Length()
|
||||
if length != 0 {
|
||||
t.Error("User cache length should be 0, not " + strconv.Itoa(length))
|
||||
}
|
||||
@ -133,7 +133,7 @@ func userStoreTest(t *testing.T) {
|
||||
}
|
||||
|
||||
if hasCache {
|
||||
length = ucache.GetLength()
|
||||
length = ucache.Length()
|
||||
if length != 1 {
|
||||
t.Error("User cache length should be 1, not " + strconv.Itoa(length))
|
||||
}
|
||||
@ -168,13 +168,13 @@ func userStoreTest(t *testing.T) {
|
||||
}
|
||||
|
||||
if hasCache {
|
||||
length = ucache.GetLength()
|
||||
length = ucache.Length()
|
||||
if length != 0 {
|
||||
t.Error("User cache length should be 0, not " + strconv.Itoa(length))
|
||||
}
|
||||
}
|
||||
|
||||
count := users.GetGlobalCount()
|
||||
count := users.GlobalCount()
|
||||
if count <= 0 {
|
||||
t.Error("The number of users should be bigger than zero")
|
||||
t.Error("count", count)
|
||||
@ -243,7 +243,7 @@ func topicStoreTest(t *testing.T) {
|
||||
t.Error("TID #1 should exist")
|
||||
}
|
||||
|
||||
count := topics.GetGlobalCount()
|
||||
count := topics.GlobalCount()
|
||||
if count <= 0 {
|
||||
t.Error("The number of topics should be bigger than zero")
|
||||
t.Error("count", count)
|
||||
|
@ -345,13 +345,6 @@ func routeReplyEditSubmit(w http.ResponseWriter, r *http.Request, user User) {
|
||||
return
|
||||
}
|
||||
|
||||
content := html.EscapeString(preparseMessage(r.PostFormValue("edit_item")))
|
||||
_, err = editReplyStmt.Exec(content, parseMessage(content), rid)
|
||||
if err != nil {
|
||||
InternalErrorJSQ(err, w, r, isJs)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the Reply ID..
|
||||
var tid int
|
||||
err = getReplyTIDStmt.QueryRow(rid).Scan(&tid)
|
||||
@ -380,6 +373,13 @@ func routeReplyEditSubmit(w http.ResponseWriter, r *http.Request, user User) {
|
||||
return
|
||||
}
|
||||
|
||||
content := html.EscapeString(preparseMessage(r.PostFormValue("edit_item")))
|
||||
_, err = editReplyStmt.Exec(content, parseMessage(content, fid, "forums"), rid)
|
||||
if err != nil {
|
||||
InternalErrorJSQ(err, w, r, isJs)
|
||||
return
|
||||
}
|
||||
|
||||
if !isJs {
|
||||
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther)
|
||||
} else {
|
||||
@ -504,7 +504,7 @@ func routeProfileReplyEditSubmit(w http.ResponseWriter, r *http.Request, user Us
|
||||
}
|
||||
|
||||
content := html.EscapeString(preparseMessage(r.PostFormValue("edit_item")))
|
||||
_, err = editProfileReplyStmt.Exec(content, parseMessage(content), rid)
|
||||
_, err = editProfileReplyStmt.Exec(content, parseMessage(content, 0, ""), rid)
|
||||
if err != nil {
|
||||
InternalErrorJSQ(err, w, r, isJs)
|
||||
return
|
||||
|
22
mysql.sql
@ -78,6 +78,17 @@ CREATE TABLE `replies`(
|
||||
primary key(`rid`)
|
||||
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `attachments`(
|
||||
`attachID` int not null AUTO_INCREMENT,
|
||||
`sectionID` int DEFAULT 0 not null, /* section ID */
|
||||
`sectionTable` varchar(200) DEFAULT 'forums' not null, /* section table */
|
||||
`originID` int not null,
|
||||
`originTable` varchar(200) DEFAULT 'replies' not null,
|
||||
`uploadedBy` int not null,
|
||||
`path` varchar(200) not null,
|
||||
primary key(`attachID`)
|
||||
) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
|
||||
|
||||
CREATE TABLE `revisions`(
|
||||
`index` int not null,
|
||||
`content` text not null,
|
||||
@ -190,6 +201,7 @@ INSERT INTO emails(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1);
|
||||
/*
|
||||
The Permissions:
|
||||
|
||||
Global Permissions:
|
||||
BanUsers
|
||||
ActivateUsers
|
||||
EditUser
|
||||
@ -210,6 +222,10 @@ ManagePlugins
|
||||
ViewAdminLogs
|
||||
ViewIPs
|
||||
|
||||
Non-staff Global Permissions:
|
||||
UploadFiles
|
||||
|
||||
Forum Permissions:
|
||||
ViewTopic
|
||||
LikeItem
|
||||
CreateTopic
|
||||
@ -222,9 +238,9 @@ PinTopic
|
||||
CloseTopic
|
||||
*/
|
||||
|
||||
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`tag`) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,1,"Admin");
|
||||
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`is_mod`,`tag`) VALUES ('Moderator','{"BanUsers":true,"ActivateUsers":false,"EditUser":true,"EditUserEmail":false,"EditUserGroup":true,"ViewIPs":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,"Mod");
|
||||
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`) VALUES ('Member','{"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"CreateReply":true}','{}');
|
||||
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`is_mod`,`is_admin`,`tag`) VALUES ('Administrator','{"BanUsers":true,"ActivateUsers":true,"EditUser":true,"EditUserEmail":true,"EditUserPassword":true,"EditUserGroup":true,"EditUserGroupSuperMod":true,"EditUserGroupAdmin":false,"EditGroup":true,"EditGroupLocalPerms":true,"EditGroupGlobalPerms":true,"EditGroupSuperMod":true,"EditGroupAdmin":false,"ManageForums":true,"EditSettings":true,"ManageThemes":true,"ManagePlugins":true,"ViewAdminLogs":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,1,"Admin");
|
||||
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`is_mod`,`tag`) VALUES ('Moderator','{"BanUsers":true,"ActivateUsers":false,"EditUser":true,"EditUserEmail":false,"EditUserGroup":true,"ViewIPs":true,"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"EditTopic":true,"DeleteTopic":true,"CreateReply":true,"EditReply":true,"DeleteReply":true,"PinTopic":true,"CloseTopic":true}','{}',1,"Mod");
|
||||
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`) VALUES ('Member','{"UploadFiles":true,"ViewTopic":true,"LikeItem":true,"CreateTopic":true,"CreateReply":true}','{}');
|
||||
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`is_banned`) VALUES ('Banned','{"ViewTopic":true}','{}',1);
|
||||
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`) VALUES ('Awaiting Activation','{"ViewTopic":true}','{}');
|
||||
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`tag`) VALUES ('Not Loggedin','{"ViewTopic":true}','{}','Guest');
|
||||
|
199
pages.go
@ -4,6 +4,7 @@ import (
|
||||
//"fmt"
|
||||
"bytes"
|
||||
"html/template"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -61,10 +62,12 @@ type TopicPage struct {
|
||||
}
|
||||
|
||||
type TopicsPage struct {
|
||||
Title string
|
||||
CurrentUser User
|
||||
Header *HeaderVars
|
||||
ItemList []*TopicsRow
|
||||
Title string
|
||||
CurrentUser User
|
||||
Header *HeaderVars
|
||||
TopicList []*TopicsRow
|
||||
ForumList []Forum
|
||||
DefaultForum int
|
||||
}
|
||||
|
||||
type ForumPage struct {
|
||||
@ -287,12 +290,16 @@ var invalidURL = []byte("<span style='color: red;'>[Invalid URL]</span>")
|
||||
var invalidTopic = []byte("<span style='color: red;'>[Invalid Topic]</span>")
|
||||
var invalidProfile = []byte("<span style='color: red;'>[Invalid Profile]</span>")
|
||||
var invalidForum = []byte("<span style='color: red;'>[Invalid Forum]</span>")
|
||||
var unknownMedia = []byte("<span style='color: red;'>[Unknown Media]</span>")
|
||||
var urlOpen = []byte("<a href='")
|
||||
var urlOpen2 = []byte("'>")
|
||||
var bytesSinglequote = []byte("'")
|
||||
var bytesGreaterthan = []byte(">")
|
||||
var urlMention = []byte(" class='mention'")
|
||||
var urlClose = []byte("</a>")
|
||||
var imageOpen = []byte("<a href=\"")
|
||||
var imageOpen2 = []byte("\"><img src='")
|
||||
var imageClose = []byte("' class='postImage' /></a>")
|
||||
var urlpattern = `(?s)([ {1}])((http|https|ftp|mailto)*)(:{??)\/\/([\.a-zA-Z\/]+)([ {1}])`
|
||||
var urlReg *regexp.Regexp
|
||||
|
||||
@ -440,7 +447,8 @@ func preparseMessage(msg string) string {
|
||||
}
|
||||
|
||||
// TODO: Write a test for this
|
||||
func parseMessage(msg string /*, user User*/) string {
|
||||
// TODO: We need a lot more hooks here. E.g. To add custom media types and handlers.
|
||||
func parseMessage(msg string, sectionID int, sectionType string /*, user User*/) string {
|
||||
msg = strings.Replace(msg, ":)", "😀", -1)
|
||||
msg = strings.Replace(msg, ":(", "😞", -1)
|
||||
msg = strings.Replace(msg, ":D", "😃", -1)
|
||||
@ -461,19 +469,13 @@ func parseMessage(msg string /*, user User*/) string {
|
||||
var msgbytes = []byte(msg)
|
||||
var outbytes []byte
|
||||
msgbytes = append(msgbytes, spaceGap...)
|
||||
//log.Print(`"`+string(msgbytes)+`"`)
|
||||
lastItem := 0
|
||||
i := 0
|
||||
//log.Printf("string(msgbytes) %+v\n", `"`+string(msgbytes)+`"`)
|
||||
var lastItem = 0
|
||||
var i = 0
|
||||
for ; len(msgbytes) > (i + 1); i++ {
|
||||
//log.Print("Index:",i)
|
||||
//log.Print("Index Item:",msgbytes[i])
|
||||
//if msgbytes[i] == 10 {
|
||||
// log.Print("NEWLINE")
|
||||
//} else if msgbytes[i] == 32 {
|
||||
// log.Print("SPACE")
|
||||
//} else {
|
||||
// log.Print("string(msgbytes[i])",string(msgbytes[i]))
|
||||
//}
|
||||
//log.Print("Index Item: ",msgbytes[i])
|
||||
//log.Print("string(msgbytes[i]): ",string(msgbytes[i]))
|
||||
//log.Print("End Index")
|
||||
if (i == 0 && (msgbytes[0] > 32)) || ((msgbytes[i] < 33) && (msgbytes[i+1] > 32)) {
|
||||
//log.Print("IN")
|
||||
@ -507,12 +509,12 @@ func parseMessage(msg string /*, user User*/) string {
|
||||
outbytes = append(outbytes, urlClose...)
|
||||
lastItem = i
|
||||
|
||||
//log.Print("string(msgbytes)",string(msgbytes))
|
||||
//log.Print(msgbytes)
|
||||
//log.Print("string(msgbytes) ",string(msgbytes))
|
||||
//log.Print("msgbytes ",msgbytes)
|
||||
//log.Print(msgbytes[lastItem - 1])
|
||||
//log.Print(lastItem - 1)
|
||||
//log.Print(msgbytes[lastItem])
|
||||
//log.Print(lastItem)
|
||||
//log.Print("lastItem ",lastItem)
|
||||
} else if bytes.Equal(msgbytes[i+1:i+5], []byte("rid-")) {
|
||||
outbytes = append(outbytes, msgbytes[lastItem:i]...)
|
||||
i += 5
|
||||
@ -611,11 +613,57 @@ func parseMessage(msg string /*, user User*/) string {
|
||||
|
||||
outbytes = append(outbytes, msgbytes[lastItem:i]...)
|
||||
urlLen := partialURLBytesLen(msgbytes[i:])
|
||||
if msgbytes[i+urlLen] != ' ' && msgbytes[i+urlLen] != 10 {
|
||||
if msgbytes[i+urlLen] > 32 { // space and invisibles
|
||||
outbytes = append(outbytes, invalidURL...)
|
||||
i += urlLen
|
||||
continue
|
||||
}
|
||||
outbytes = append(outbytes, urlOpen...)
|
||||
outbytes = append(outbytes, msgbytes[i:i+urlLen]...)
|
||||
outbytes = append(outbytes, urlOpen2...)
|
||||
outbytes = append(outbytes, msgbytes[i:i+urlLen]...)
|
||||
outbytes = append(outbytes, urlClose...)
|
||||
i += urlLen
|
||||
lastItem = i
|
||||
} else if msgbytes[i] == '/' && msgbytes[i+1] == '/' {
|
||||
outbytes = append(outbytes, msgbytes[lastItem:i]...)
|
||||
urlLen := partialURLBytesLen(msgbytes[i:])
|
||||
if msgbytes[i+urlLen] > 32 { // space and invisibles
|
||||
//log.Print("INVALID URL")
|
||||
//log.Print("msgbytes[i+urlLen]", msgbytes[i+urlLen])
|
||||
//log.Print("string(msgbytes[i+urlLen])", string(msgbytes[i+urlLen]))
|
||||
//log.Print("msgbytes[i:i+urlLen]", msgbytes[i:i+urlLen])
|
||||
//log.Print("string(msgbytes[i:i+urlLen])", string(msgbytes[i:i+urlLen]))
|
||||
outbytes = append(outbytes, invalidURL...)
|
||||
i += urlLen
|
||||
continue
|
||||
}
|
||||
|
||||
//log.Print("VALID URL")
|
||||
//log.Print("msgbytes[i:i+urlLen]", msgbytes[i:i+urlLen])
|
||||
//log.Print("string(msgbytes[i:i+urlLen])", string(msgbytes[i:i+urlLen]))
|
||||
media, ok := parseMediaBytes(msgbytes[i : i+urlLen])
|
||||
if !ok {
|
||||
outbytes = append(outbytes, invalidURL...)
|
||||
i += urlLen
|
||||
continue
|
||||
}
|
||||
|
||||
if media.Type == "image" {
|
||||
outbytes = append(outbytes, imageOpen...)
|
||||
outbytes = append(outbytes, []byte(media.URL+"?sectionID="+strconv.Itoa(sectionID)+"§ionType="+sectionType)...)
|
||||
outbytes = append(outbytes, imageOpen2...)
|
||||
outbytes = append(outbytes, []byte(media.URL+"?sectionID="+strconv.Itoa(sectionID)+"§ionType="+sectionType)...)
|
||||
outbytes = append(outbytes, imageClose...)
|
||||
i += urlLen
|
||||
lastItem = i
|
||||
continue
|
||||
} else if media.Type != "" {
|
||||
outbytes = append(outbytes, unknownMedia...)
|
||||
i += urlLen
|
||||
continue
|
||||
}
|
||||
|
||||
outbytes = append(outbytes, urlOpen...)
|
||||
outbytes = append(outbytes, msgbytes[i:i+urlLen]...)
|
||||
outbytes = append(outbytes, urlOpen2...)
|
||||
@ -628,13 +676,10 @@ func parseMessage(msg string /*, user User*/) string {
|
||||
}
|
||||
|
||||
if lastItem != i && len(outbytes) != 0 {
|
||||
//log.Print("lastItem:",msgbytes[lastItem])
|
||||
//log.Print("lastItem index:")
|
||||
//log.Print(lastItem)
|
||||
//log.Print("i:")
|
||||
//log.Print(i)
|
||||
//log.Print("lastItem to end:")
|
||||
//log.Print(msgbytes[lastItem:])
|
||||
//log.Print("lastItem: ",msgbytes[lastItem])
|
||||
//log.Print("lastItem index: ",lastItem)
|
||||
//log.Print("i: ",i)
|
||||
//log.Print("lastItem to end: ",msgbytes[lastItem:])
|
||||
//log.Print("-----")
|
||||
calclen := len(msgbytes) - 10
|
||||
if calclen <= lastItem {
|
||||
@ -666,8 +711,8 @@ func regexParseMessage(msg string) string {
|
||||
return msg
|
||||
}
|
||||
|
||||
// 6, 7, 8, 6, 7
|
||||
// ftp://, http://, https:// git://, mailto: (not a URL, just here for length comparison purposes)
|
||||
// 6, 7, 8, 6, 2, 7
|
||||
// ftp://, http://, https:// git://, //, mailto: (not a URL, just here for length comparison purposes)
|
||||
// TODO: Write a test for this
|
||||
func validateURLBytes(data []byte) bool {
|
||||
datalen := len(data)
|
||||
@ -681,10 +726,13 @@ func validateURLBytes(data []byte) bool {
|
||||
} else if datalen >= 8 && bytes.Equal(data[0:8], []byte("https://")) {
|
||||
i = 8
|
||||
}
|
||||
} else if datalen >= 2 && data[0] == '/' && data[1] == '/' {
|
||||
i = 2
|
||||
}
|
||||
|
||||
// ? - There should only be one : and that's only if the URL is on a non-standard port
|
||||
for ; datalen > i; i++ {
|
||||
if data[i] != '\\' && data[i] != '_' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
|
||||
if data[i] != '\\' && data[i] != '_' && data[i] != ':' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -704,10 +752,13 @@ func validatedURLBytes(data []byte) (url []byte) {
|
||||
} else if datalen >= 8 && bytes.Equal(data[0:8], []byte("https://")) {
|
||||
i = 8
|
||||
}
|
||||
} else if datalen >= 2 && data[0] == '/' && data[1] == '/' {
|
||||
i = 2
|
||||
}
|
||||
|
||||
// ? - There should only be one : and that's only if the URL is on a non-standard port
|
||||
for ; datalen > i; i++ {
|
||||
if data[i] != '\\' && data[i] != '_' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
|
||||
if data[i] != '\\' && data[i] != '_' && data[i] != ':' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
|
||||
return invalidURL
|
||||
}
|
||||
}
|
||||
@ -730,10 +781,13 @@ func partialURLBytes(data []byte) (url []byte) {
|
||||
} else if datalen >= 8 && bytes.Equal(data[0:8], []byte("https://")) {
|
||||
i = 8
|
||||
}
|
||||
} else if datalen >= 2 && data[0] == '/' && data[1] == '/' {
|
||||
i = 2
|
||||
}
|
||||
|
||||
// ? - There should only be one : and that's only if the URL is on a non-standard port
|
||||
for ; end >= i; i++ {
|
||||
if data[i] != '\\' && data[i] != '_' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
|
||||
if data[i] != '\\' && data[i] != '_' && data[i] != ':' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
|
||||
end = i
|
||||
}
|
||||
}
|
||||
@ -756,47 +810,80 @@ func partialURLBytesLen(data []byte) int {
|
||||
} else if datalen >= 8 && bytes.Equal(data[0:8], []byte("https://")) {
|
||||
i = 8
|
||||
}
|
||||
} else if datalen >= 2 && data[0] == '/' && data[1] == '/' {
|
||||
i = 2
|
||||
}
|
||||
|
||||
// ? - There should only be one : and that's only if the URL is on a non-standard port
|
||||
for ; datalen > i; i++ {
|
||||
if data[i] != '\\' && data[i] != '_' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
|
||||
//log.Print("Bad Character:",data[i])
|
||||
if data[i] != '\\' && data[i] != '_' && data[i] != ':' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
|
||||
//log.Print("Bad Character: ", data[i])
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
//log.Print("Data Length:",datalen)
|
||||
//log.Print("Data Length: ",datalen)
|
||||
return datalen
|
||||
}
|
||||
|
||||
type MediaEmbed struct {
|
||||
Type string //image
|
||||
URL string
|
||||
}
|
||||
|
||||
// TODO: Write a test for this
|
||||
func parseMediaBytes(data []byte) (protocol []byte, url []byte) {
|
||||
datalen := len(data)
|
||||
i := 0
|
||||
func parseMediaBytes(data []byte) (media MediaEmbed, ok bool) {
|
||||
if !validateURLBytes(data) {
|
||||
return media, false
|
||||
}
|
||||
url, err := parseURL(data)
|
||||
if err != nil {
|
||||
return media, false
|
||||
}
|
||||
|
||||
if datalen >= 6 {
|
||||
if bytes.Equal(data[0:6], []byte("ftp://")) || bytes.Equal(data[0:6], []byte("git://")) {
|
||||
i = 6
|
||||
protocol = data[0:2]
|
||||
} else if datalen >= 7 && bytes.Equal(data[0:7], httpProtBytes) {
|
||||
i = 7
|
||||
protocol = []byte("http")
|
||||
} else if datalen >= 8 && bytes.Equal(data[0:8], []byte("https://")) {
|
||||
i = 8
|
||||
protocol = []byte("https")
|
||||
//log.Print("url ", url)
|
||||
hostname := url.Hostname()
|
||||
scheme := url.Scheme
|
||||
port := url.Port()
|
||||
//log.Print("hostname ", hostname)
|
||||
//log.Print("scheme ", scheme)
|
||||
|
||||
var samesite = hostname == "localhost" || hostname == site.URL
|
||||
if samesite {
|
||||
//log.Print("samesite")
|
||||
hostname = strings.Split(site.URL, ":")[0]
|
||||
// ?- Test this as I'm not sure it'll do what it should. If someone's running SSL on port 80 or non-SSL on port 443 then... Well... They're in far worse trouble than this...
|
||||
port = site.Port
|
||||
if scheme == "" && site.EnableSsl {
|
||||
scheme = "https"
|
||||
}
|
||||
}
|
||||
if scheme == "" {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
for ; datalen > i; i++ {
|
||||
if data[i] != '\\' && data[i] != '_' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
|
||||
return []byte(""), invalidURL
|
||||
path := url.EscapedPath()
|
||||
//log.Print("path", path)
|
||||
pathFrags := strings.Split(path, "/")
|
||||
//log.Printf("pathFrags %+v\n", pathFrags)
|
||||
//log.Print("scheme ", scheme)
|
||||
//log.Print("hostname ", hostname)
|
||||
if len(pathFrags) >= 2 {
|
||||
if samesite && pathFrags[1] == "attachs" && (scheme == "http" || scheme == "https") {
|
||||
//log.Print("Attachment")
|
||||
media.Type = "image"
|
||||
var sport string
|
||||
// ? - Assumes the sysadmin hasn't mixed up the two standard ports
|
||||
if port != "443" && port != "80" {
|
||||
sport = ":" + port
|
||||
}
|
||||
media.URL = scheme + "://" + hostname + sport + path
|
||||
}
|
||||
}
|
||||
return media, true
|
||||
}
|
||||
|
||||
if len(protocol) == 0 {
|
||||
protocol = []byte("http")
|
||||
}
|
||||
return protocol, data[i:]
|
||||
func parseURL(data []byte) (*url.URL, error) {
|
||||
return url.Parse(string(data))
|
||||
}
|
||||
|
||||
// TODO: Write a test for this
|
||||
|
@ -1531,6 +1531,7 @@ func routePanelGroupsEditPerms(w http.ResponseWriter, r *http.Request, user User
|
||||
globalPerms = append(globalPerms, NameLangToggle{"ManagePlugins", GetGlobalPermPhrase("ManagePlugins"), group.Perms.ManagePlugins})
|
||||
globalPerms = append(globalPerms, NameLangToggle{"ViewAdminLogs", GetGlobalPermPhrase("ViewAdminLogs"), group.Perms.ViewAdminLogs})
|
||||
globalPerms = append(globalPerms, NameLangToggle{"ViewIPs", GetGlobalPermPhrase("ViewIPs"), group.Perms.ViewIPs})
|
||||
globalPerms = append(globalPerms, NameLangToggle{"UploadFiles", GetGlobalPermPhrase("UploadFiles"), group.Perms.UploadFiles})
|
||||
|
||||
pi := PanelEditGroupPermsPage{"Group Editor", user, headerVars, stats, group.ID, group.Name, localPerms, globalPerms}
|
||||
if preRenderHooks["pre_render_panel_edit_group_perms"] != nil {
|
||||
|
@ -16,6 +16,8 @@ var AllPerms Perms
|
||||
var AllForumPerms ForumPerms
|
||||
var AllPluginPerms = make(map[string]bool)
|
||||
|
||||
// ? - Can we avoid duplicating the items in this list in a bunch of places?
|
||||
|
||||
var LocalPermList = []string{
|
||||
"ViewTopic",
|
||||
"LikeItem",
|
||||
@ -29,6 +31,7 @@ var LocalPermList = []string{
|
||||
"CloseTopic",
|
||||
}
|
||||
|
||||
// ? - Can we avoid duplicating the items in this list in a bunch of places?
|
||||
var GlobalPermList = []string{
|
||||
"BanUsers",
|
||||
"ActivateUsers",
|
||||
@ -49,6 +52,7 @@ var GlobalPermList = []string{
|
||||
"ManagePlugins",
|
||||
"ViewAdminLogs",
|
||||
"ViewIPs",
|
||||
"UploadFiles",
|
||||
}
|
||||
|
||||
// Permission Structure: ActionComponent[Subcomponent]Flag
|
||||
@ -74,6 +78,10 @@ type Perms struct {
|
||||
ViewAdminLogs bool
|
||||
ViewIPs bool
|
||||
|
||||
// Global non-staff permissions
|
||||
UploadFiles bool
|
||||
// TODO: Add a permission for enabling avatars
|
||||
|
||||
// Forum permissions
|
||||
ViewTopic bool
|
||||
LikeItem bool
|
||||
@ -147,6 +155,8 @@ func init() {
|
||||
ViewAdminLogs: true,
|
||||
ViewIPs: true,
|
||||
|
||||
UploadFiles: true,
|
||||
|
||||
ViewTopic: true,
|
||||
LikeItem: true,
|
||||
CreateTopic: true,
|
||||
|
@ -78,6 +78,7 @@ function load_alerts(menu_alerts)
|
||||
bind_to_alerts();
|
||||
},
|
||||
error: function(magic,theStatus,error) {
|
||||
var errtxt
|
||||
try {
|
||||
var data = JSON.parse(magic.responseText);
|
||||
if("errmsg" in data) errtxt = data.errmsg;
|
||||
@ -94,16 +95,16 @@ function load_alerts(menu_alerts)
|
||||
|
||||
function SplitN(data,ch,n) {
|
||||
var out = [];
|
||||
if(data.length == 0) return out;
|
||||
if(data.length === 0) return out;
|
||||
|
||||
var lastIndex = 0;
|
||||
var j = 0;
|
||||
var lastN = 1;
|
||||
for(var i = 0; i < data.length; i++) {
|
||||
if(data[i] == ch) {
|
||||
for(let i = 0; i < data.length; i++) {
|
||||
if(data[i] === ch) {
|
||||
out[j++] = data.substring(lastIndex,i);
|
||||
lastIndex = i;
|
||||
if(lastN == n) break;
|
||||
if(lastN === n) break;
|
||||
lastN++;
|
||||
}
|
||||
}
|
||||
@ -118,19 +119,23 @@ $(document).ready(function(){
|
||||
else conn = new WebSocket("ws://" + document.location.host + "/ws/");
|
||||
|
||||
conn.onopen = function() {
|
||||
console.log("The WebSockets connection was opened");
|
||||
conn.send("page " + document.location.pathname + '\r');
|
||||
// TODO: Don't ask again, if it's denied. We could have a setting in the UCP which automatically requests this when someone flips desktop notifications on
|
||||
Notification.requestPermission();
|
||||
}
|
||||
conn.onclose = function() {
|
||||
conn = false;
|
||||
console.log("The WebSockets connection was closed");
|
||||
}
|
||||
conn.onmessage = function(event) {
|
||||
//console.log("WS_Message: ",event.data);
|
||||
if(event.data[0] == "{") {
|
||||
try {
|
||||
var data = JSON.parse(event.data);
|
||||
} catch(err) { console.log(err); }
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
if ("msg" in data) {
|
||||
var msg = data.msg
|
||||
@ -175,11 +180,11 @@ $(document).ready(function(){
|
||||
//console.log(messages[i]);
|
||||
if(messages[i].startsWith("set ")) {
|
||||
//msgblocks = messages[i].split(' ',3);
|
||||
msgblocks = SplitN(messages[i]," ",3);
|
||||
let msgblocks = SplitN(messages[i]," ",3);
|
||||
if(msgblocks.length < 3) continue;
|
||||
document.querySelector(msgblocks[1]).innerHTML = msgblocks[2];
|
||||
} else if(messages[i].startsWith("set-class ")) {
|
||||
msgblocks = SplitN(messages[i]," ",3);
|
||||
let msgblocks = SplitN(messages[i]," ",3);
|
||||
if(msgblocks.length < 3) continue;
|
||||
document.querySelector(msgblocks[1]).className = msgblocks[2];
|
||||
}
|
||||
@ -328,7 +333,7 @@ $(document).ready(function(){
|
||||
//console.log("running .submit_edit event");
|
||||
var out_data = {isJs: "1"}
|
||||
var block_parent = $(this).closest('.editable_parent');
|
||||
var block = block_parent.find('.editable_block').each(function(){
|
||||
block_parent.find('.editable_block').each(function(){
|
||||
var field_name = this.getAttribute("data-field");
|
||||
var field_type = this.getAttribute("data-type");
|
||||
if(field_type=="list") {
|
||||
@ -397,6 +402,71 @@ $(document).ready(function(){
|
||||
event.stopPropagation();
|
||||
})
|
||||
|
||||
$(".create_topic_link").click(function(event){
|
||||
event.preventDefault();
|
||||
$(".topic_create_form").show();
|
||||
});
|
||||
$(".topic_create_form .close_form").click(function(){
|
||||
event.preventDefault();
|
||||
$(".topic_create_form").hide();
|
||||
});
|
||||
|
||||
function uploadFileHandler() {
|
||||
var fileList = this.files;
|
||||
|
||||
// Truncate the number of files to 5
|
||||
let files = [];
|
||||
for(var i = 0; i < fileList.length && i < 5; i++)
|
||||
files[i] = fileList[i];
|
||||
|
||||
// Iterate over the files
|
||||
for(let i = 0; i < files.length; i++) {
|
||||
console.log("files[" + i + "]",files[i]);
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
var fileDock = document.getElementById("upload_file_dock");
|
||||
var fileItem = document.createElement("label");
|
||||
console.log("fileItem",fileItem);
|
||||
|
||||
if(!files[i]["name"].indexOf('.' > -1)) {
|
||||
// TODO: Surely, there's a prettier and more elegant way of doing this?
|
||||
alert("This file doesn't have an extension");
|
||||
return;
|
||||
}
|
||||
|
||||
var ext = files[i]["name"].split('.').pop();
|
||||
fileItem.innerText = "." + ext;
|
||||
fileItem.className = "formbutton uploadItem";
|
||||
fileItem.style.backgroundImage = "url("+e.target.result+")";
|
||||
|
||||
fileDock.appendChild(fileItem);
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
crypto.subtle.digest('SHA-256',e.target.result).then(function(hash) {
|
||||
const hashArray = Array.from(new Uint8Array(hash))
|
||||
return hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('')
|
||||
}).then(function(hash) {
|
||||
console.log("hash",hash);
|
||||
let content = document.getElementById("topic_content")
|
||||
console.log("content.value",content.value);
|
||||
|
||||
if(content.value == "") content.value = content.value + "//" + siteURL + "/attachs/" + hash + "." + ext;
|
||||
else content.value = content.value + "\r\n//" + siteURL + "/attachs/" + hash + "." + ext;
|
||||
console.log("content.value",content.value);
|
||||
});
|
||||
}
|
||||
reader.readAsArrayBuffer(files[i]);
|
||||
}
|
||||
reader.readAsDataURL(files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var uploadFiles = document.getElementById("quick_topic_upload_files");
|
||||
if(uploadFiles != null) {
|
||||
uploadFiles.addEventListener("change", uploadFileHandler, false);
|
||||
}
|
||||
|
||||
$("#themeSelectorSelect").change(function(){
|
||||
console.log("Changing the theme to " + this.options[this.selectedIndex].getAttribute("val"));
|
||||
$.ajax({
|
||||
@ -408,6 +478,7 @@ $(document).ready(function(){
|
||||
console.log("Theme successfully switched");
|
||||
console.log("data",data);
|
||||
console.log("status",status);
|
||||
console.log("xhr",xhr);
|
||||
window.location.reload();
|
||||
},
|
||||
// TODO: Use a standard error handler for the AJAX calls in here which throws up the response (if JSON) in a .notice? Might be difficult to trace errors in the console, if we reuse the same function every-time
|
||||
|
@ -1,12 +0,0 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
|
||||
<defs>
|
||||
<pattern id="wooblies" width="50" height="43.4" patternUnits="userSpaceOnUse" patternTransform="scale(0.5) rotate(30)">
|
||||
<polygon points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 2.3,33.7 12.3,29.2" id="hex" fill="rgb(240,240,240)" stroke="darkgray" stroke-width="1" />
|
||||
<use xlink:href="#hex" x="25" />
|
||||
<use xlink:href="#hex" x="-25" />
|
||||
<use xlink:href="#hex" x="12.5" y="-21.7" />
|
||||
<use xlink:href="#hex" x="-12.5" y="-21.7" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#wooblies)" />
|
||||
</svg>
|
Before Width: | Height: | Size: 686 B |
@ -1,12 +0,0 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
|
||||
<defs>
|
||||
<pattern id="wooblies" width="50" height="43.4" patternUnits="userSpaceOnUse" patternTransform="scale(0.5) rotate(30)">
|
||||
<polygon points="24.8,22 37.3,29.2 37.3,43.7 24.8,50.9 2.3,33.7 12.3,29.2" id="hex" fill="rgba(240,240,240)" stroke="darkgray" stroke-width="0.5" fill-opacity="0.1" />
|
||||
<use xlink:href="#hex" x="25" />
|
||||
<use xlink:href="#hex" x="-25" />
|
||||
<use xlink:href="#hex" x="12.5" y="-21.7" />
|
||||
<use xlink:href="#hex" x="-12.5" y="-21.7" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#wooblies)" />
|
||||
</svg>
|
Before Width: | Height: | Size: 708 B |
@ -13,11 +13,12 @@ func init() {
|
||||
}
|
||||
|
||||
type Mysql_Adapter struct {
|
||||
Name string
|
||||
Name string // ? - Do we really need this? Can't we hard-code this?
|
||||
Buffer map[string]DB_Stmt
|
||||
BufferOrder []string // Map iteration order is random, so we need this to track the order, so we don't get huge diffs every commit
|
||||
}
|
||||
|
||||
// GetName gives you the name of the database adapter. In this case, it's mysql
|
||||
func (adapter *Mysql_Adapter) GetName() string {
|
||||
return adapter.Name
|
||||
}
|
||||
@ -120,7 +121,7 @@ func (adapter *Mysql_Adapter) SimpleInsert(name string, table string, columns st
|
||||
var querystr = "INSERT INTO `" + table + "`("
|
||||
|
||||
// Escape the column names, just in case we've used a reserved keyword
|
||||
for _, column := range _process_columns(columns) {
|
||||
for _, column := range processColumns(columns) {
|
||||
if column.Type == "function" {
|
||||
querystr += column.Left + ","
|
||||
} else {
|
||||
@ -132,7 +133,7 @@ func (adapter *Mysql_Adapter) SimpleInsert(name string, table string, columns st
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
|
||||
querystr += ") VALUES ("
|
||||
for _, field := range _processFields(fields) {
|
||||
for _, field := range processFields(fields) {
|
||||
querystr += field.Name + ","
|
||||
}
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
@ -158,7 +159,7 @@ func (adapter *Mysql_Adapter) SimpleReplace(name string, table string, columns s
|
||||
var querystr = "REPLACE INTO `" + table + "`("
|
||||
|
||||
// Escape the column names, just in case we've used a reserved keyword
|
||||
for _, column := range _process_columns(columns) {
|
||||
for _, column := range processColumns(columns) {
|
||||
if column.Type == "function" {
|
||||
querystr += column.Left + ","
|
||||
} else {
|
||||
@ -169,7 +170,7 @@ func (adapter *Mysql_Adapter) SimpleReplace(name string, table string, columns s
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
|
||||
querystr += ") VALUES ("
|
||||
for _, field := range _processFields(fields) {
|
||||
for _, field := range processFields(fields) {
|
||||
querystr += field.Name + ","
|
||||
}
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
@ -190,7 +191,7 @@ func (adapter *Mysql_Adapter) SimpleUpdate(name string, table string, set string
|
||||
}
|
||||
|
||||
var querystr = "UPDATE `" + table + "` SET "
|
||||
for _, item := range _process_set(set) {
|
||||
for _, item := range processSet(set) {
|
||||
querystr += "`" + item.Column + "` ="
|
||||
for _, token := range item.Expr {
|
||||
switch token.Type {
|
||||
@ -211,7 +212,7 @@ func (adapter *Mysql_Adapter) SimpleUpdate(name string, table string, set string
|
||||
// Add support for BETWEEN x.x
|
||||
if len(where) != 0 {
|
||||
querystr += " WHERE"
|
||||
for _, loc := range _processWhere(where) {
|
||||
for _, loc := range processWhere(where) {
|
||||
for _, token := range loc.Expr {
|
||||
switch token.Type {
|
||||
case "function", "operator", "number", "substitute":
|
||||
@ -247,7 +248,7 @@ func (adapter *Mysql_Adapter) SimpleDelete(name string, table string, where stri
|
||||
var querystr = "DELETE FROM `" + table + "` WHERE"
|
||||
|
||||
// Add support for BETWEEN x.x
|
||||
for _, loc := range _processWhere(where) {
|
||||
for _, loc := range processWhere(where) {
|
||||
for _, token := range loc.Expr {
|
||||
switch token.Type {
|
||||
case "function", "operator", "number", "substitute":
|
||||
@ -308,7 +309,7 @@ func (adapter *Mysql_Adapter) SimpleSelect(name string, table string, columns st
|
||||
// Add support for BETWEEN x.x
|
||||
if len(where) != 0 {
|
||||
querystr += " WHERE"
|
||||
for _, loc := range _processWhere(where) {
|
||||
for _, loc := range processWhere(where) {
|
||||
for _, token := range loc.Expr {
|
||||
switch token.Type {
|
||||
case "function", "operator", "number", "substitute":
|
||||
@ -328,7 +329,7 @@ func (adapter *Mysql_Adapter) SimpleSelect(name string, table string, columns st
|
||||
|
||||
if len(orderby) != 0 {
|
||||
querystr += " ORDER BY "
|
||||
for _, column := range _process_orderby(orderby) {
|
||||
for _, column := range processOrderby(orderby) {
|
||||
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
|
||||
}
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
@ -362,7 +363,7 @@ func (adapter *Mysql_Adapter) SimpleLeftJoin(name string, table1 string, table2
|
||||
|
||||
var querystr = "SELECT "
|
||||
|
||||
for _, column := range _process_columns(columns) {
|
||||
for _, column := range processColumns(columns) {
|
||||
var source, alias string
|
||||
|
||||
// Escape the column names, just in case we've used a reserved keyword
|
||||
@ -384,7 +385,7 @@ func (adapter *Mysql_Adapter) SimpleLeftJoin(name string, table1 string, table2
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
|
||||
querystr += " FROM `" + table1 + "` LEFT JOIN `" + table2 + "` ON "
|
||||
for _, joiner := range _processJoiner(joiners) {
|
||||
for _, joiner := range processJoiner(joiners) {
|
||||
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
|
||||
}
|
||||
// Remove the trailing AND
|
||||
@ -393,7 +394,7 @@ func (adapter *Mysql_Adapter) SimpleLeftJoin(name string, table1 string, table2
|
||||
// Add support for BETWEEN x.x
|
||||
if len(where) != 0 {
|
||||
querystr += " WHERE"
|
||||
for _, loc := range _processWhere(where) {
|
||||
for _, loc := range processWhere(where) {
|
||||
for _, token := range loc.Expr {
|
||||
switch token.Type {
|
||||
case "function", "operator", "number", "substitute":
|
||||
@ -418,7 +419,7 @@ func (adapter *Mysql_Adapter) SimpleLeftJoin(name string, table1 string, table2
|
||||
|
||||
if len(orderby) != 0 {
|
||||
querystr += " ORDER BY "
|
||||
for _, column := range _process_orderby(orderby) {
|
||||
for _, column := range processOrderby(orderby) {
|
||||
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
|
||||
}
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
@ -452,7 +453,7 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2
|
||||
|
||||
var querystr = "SELECT "
|
||||
|
||||
for _, column := range _process_columns(columns) {
|
||||
for _, column := range processColumns(columns) {
|
||||
var source, alias string
|
||||
|
||||
// Escape the column names, just in case we've used a reserved keyword
|
||||
@ -474,7 +475,7 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
|
||||
querystr += " FROM `" + table1 + "` INNER JOIN `" + table2 + "` ON "
|
||||
for _, joiner := range _processJoiner(joiners) {
|
||||
for _, joiner := range processJoiner(joiners) {
|
||||
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
|
||||
}
|
||||
// Remove the trailing AND
|
||||
@ -483,7 +484,7 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2
|
||||
// Add support for BETWEEN x.x
|
||||
if len(where) != 0 {
|
||||
querystr += " WHERE"
|
||||
for _, loc := range _processWhere(where) {
|
||||
for _, loc := range processWhere(where) {
|
||||
for _, token := range loc.Expr {
|
||||
switch token.Type {
|
||||
case "function", "operator", "number", "substitute":
|
||||
@ -508,7 +509,7 @@ func (adapter *Mysql_Adapter) SimpleInnerJoin(name string, table1 string, table2
|
||||
|
||||
if len(orderby) != 0 {
|
||||
querystr += " ORDER BY "
|
||||
for _, column := range _process_orderby(orderby) {
|
||||
for _, column := range processOrderby(orderby) {
|
||||
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
|
||||
}
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
@ -529,7 +530,7 @@ func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel
|
||||
var querystr = "INSERT INTO `" + ins.Table + "`("
|
||||
|
||||
// Escape the column names, just in case we've used a reserved keyword
|
||||
for _, column := range _process_columns(ins.Columns) {
|
||||
for _, column := range processColumns(ins.Columns) {
|
||||
if column.Type == "function" {
|
||||
querystr += column.Left + ","
|
||||
} else {
|
||||
@ -540,7 +541,7 @@ func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel
|
||||
|
||||
/* Select Portion */
|
||||
|
||||
for _, column := range _process_columns(sel.Columns) {
|
||||
for _, column := range processColumns(sel.Columns) {
|
||||
var source, alias string
|
||||
|
||||
// Escape the column names, just in case we've used a reserved keyword
|
||||
@ -562,7 +563,7 @@ func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel
|
||||
// Add support for BETWEEN x.x
|
||||
if len(sel.Where) != 0 {
|
||||
querystr += " WHERE"
|
||||
for _, loc := range _processWhere(sel.Where) {
|
||||
for _, loc := range processWhere(sel.Where) {
|
||||
for _, token := range loc.Expr {
|
||||
switch token.Type {
|
||||
case "function", "operator", "number", "substitute":
|
||||
@ -582,7 +583,7 @@ func (adapter *Mysql_Adapter) SimpleInsertSelect(name string, ins DB_Insert, sel
|
||||
|
||||
if len(sel.Orderby) != 0 {
|
||||
querystr += " ORDER BY "
|
||||
for _, column := range _process_orderby(sel.Orderby) {
|
||||
for _, column := range processOrderby(sel.Orderby) {
|
||||
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
|
||||
}
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
@ -603,7 +604,7 @@ func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, s
|
||||
var querystr = "INSERT INTO `" + ins.Table + "`("
|
||||
|
||||
// Escape the column names, just in case we've used a reserved keyword
|
||||
for _, column := range _process_columns(ins.Columns) {
|
||||
for _, column := range processColumns(ins.Columns) {
|
||||
if column.Type == "function" {
|
||||
querystr += column.Left + ","
|
||||
} else {
|
||||
@ -614,7 +615,7 @@ func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, s
|
||||
|
||||
/* Select Portion */
|
||||
|
||||
for _, column := range _process_columns(sel.Columns) {
|
||||
for _, column := range processColumns(sel.Columns) {
|
||||
var source, alias string
|
||||
|
||||
// Escape the column names, just in case we've used a reserved keyword
|
||||
@ -634,7 +635,7 @@ func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, s
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
|
||||
querystr += " FROM `" + sel.Table1 + "` LEFT JOIN `" + sel.Table2 + "` ON "
|
||||
for _, joiner := range _processJoiner(sel.Joiners) {
|
||||
for _, joiner := range processJoiner(sel.Joiners) {
|
||||
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
|
||||
}
|
||||
querystr = querystr[0 : len(querystr)-4]
|
||||
@ -642,7 +643,7 @@ func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, s
|
||||
// Add support for BETWEEN x.x
|
||||
if len(sel.Where) != 0 {
|
||||
querystr += " WHERE"
|
||||
for _, loc := range _processWhere(sel.Where) {
|
||||
for _, loc := range processWhere(sel.Where) {
|
||||
for _, token := range loc.Expr {
|
||||
switch token.Type {
|
||||
case "function", "operator", "number", "substitute":
|
||||
@ -667,7 +668,7 @@ func (adapter *Mysql_Adapter) SimpleInsertLeftJoin(name string, ins DB_Insert, s
|
||||
|
||||
if len(sel.Orderby) != 0 {
|
||||
querystr += " ORDER BY "
|
||||
for _, column := range _process_orderby(sel.Orderby) {
|
||||
for _, column := range processOrderby(sel.Orderby) {
|
||||
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
|
||||
}
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
@ -688,7 +689,7 @@ func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert,
|
||||
var querystr = "INSERT INTO `" + ins.Table + "`("
|
||||
|
||||
// Escape the column names, just in case we've used a reserved keyword
|
||||
for _, column := range _process_columns(ins.Columns) {
|
||||
for _, column := range processColumns(ins.Columns) {
|
||||
if column.Type == "function" {
|
||||
querystr += column.Left + ","
|
||||
} else {
|
||||
@ -699,7 +700,7 @@ func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert,
|
||||
|
||||
/* Select Portion */
|
||||
|
||||
for _, column := range _process_columns(sel.Columns) {
|
||||
for _, column := range processColumns(sel.Columns) {
|
||||
var source, alias string
|
||||
|
||||
// Escape the column names, just in case we've used a reserved keyword
|
||||
@ -719,7 +720,7 @@ func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert,
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
|
||||
querystr += " FROM `" + sel.Table1 + "` INNER JOIN `" + sel.Table2 + "` ON "
|
||||
for _, joiner := range _processJoiner(sel.Joiners) {
|
||||
for _, joiner := range processJoiner(sel.Joiners) {
|
||||
querystr += "`" + joiner.LeftTable + "`.`" + joiner.LeftColumn + "` " + joiner.Operator + " `" + joiner.RightTable + "`.`" + joiner.RightColumn + "` AND "
|
||||
}
|
||||
querystr = querystr[0 : len(querystr)-4]
|
||||
@ -727,7 +728,7 @@ func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert,
|
||||
// Add support for BETWEEN x.x
|
||||
if len(sel.Where) != 0 {
|
||||
querystr += " WHERE"
|
||||
for _, loc := range _processWhere(sel.Where) {
|
||||
for _, loc := range processWhere(sel.Where) {
|
||||
for _, token := range loc.Expr {
|
||||
switch token.Type {
|
||||
case "function", "operator", "number", "substitute":
|
||||
@ -752,7 +753,7 @@ func (adapter *Mysql_Adapter) SimpleInsertInnerJoin(name string, ins DB_Insert,
|
||||
|
||||
if len(sel.Orderby) != 0 {
|
||||
querystr += " ORDER BY "
|
||||
for _, column := range _process_orderby(sel.Orderby) {
|
||||
for _, column := range processOrderby(sel.Orderby) {
|
||||
querystr += column.Column + " " + strings.ToUpper(column.Order) + ","
|
||||
}
|
||||
querystr = querystr[0 : len(querystr)-1]
|
||||
@ -783,7 +784,7 @@ func (adapter *Mysql_Adapter) SimpleCount(name string, table string, where strin
|
||||
//fmt.Println("SimpleCount:",name)
|
||||
//fmt.Println("where:",where)
|
||||
//fmt.Println("_process_where:",_process_where(where))
|
||||
for _, loc := range _processWhere(where) {
|
||||
for _, loc := range processWhere(where) {
|
||||
for _, token := range loc.Expr {
|
||||
switch token.Type {
|
||||
case "function", "operator", "number", "substitute":
|
||||
|
@ -12,11 +12,12 @@ func init() {
|
||||
}
|
||||
|
||||
type Pgsql_Adapter struct {
|
||||
Name string
|
||||
Name string // ? - Do we really need this? Can't we hard-code this?
|
||||
Buffer map[string]DB_Stmt
|
||||
BufferOrder []string // Map iteration order is random, so we need this to track the order, so we don't get huge diffs every commit
|
||||
}
|
||||
|
||||
// GetName gives you the name of the database adapter. In this case, it's pgsql
|
||||
func (adapter *Pgsql_Adapter) GetName() string {
|
||||
return adapter.Name
|
||||
}
|
||||
@ -139,7 +140,7 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
|
||||
return "", errors.New("You need to set data in this update statement")
|
||||
}
|
||||
var querystr = "UPDATE `" + table + "` SET "
|
||||
for _, item := range _process_set(set) {
|
||||
for _, item := range processSet(set) {
|
||||
querystr += "`" + item.Column + "` ="
|
||||
for _, token := range item.Expr {
|
||||
switch token.Type {
|
||||
@ -166,7 +167,7 @@ func (adapter *Pgsql_Adapter) SimpleUpdate(name string, table string, set string
|
||||
// Add support for BETWEEN x.x
|
||||
if len(where) != 0 {
|
||||
querystr += " WHERE"
|
||||
for _, loc := range _processWhere(where) {
|
||||
for _, loc := range processWhere(where) {
|
||||
for _, token := range loc.Expr {
|
||||
switch token.Type {
|
||||
case "function":
|
||||
|
@ -1,25 +1,31 @@
|
||||
/* WIP Under Construction */
|
||||
/*
|
||||
*
|
||||
* Query Generator Library
|
||||
* WIP Under Construction
|
||||
* Copyright Azareal 2017 - 2018
|
||||
*
|
||||
*/
|
||||
package qgen
|
||||
|
||||
//import "fmt"
|
||||
import "strings"
|
||||
import "os"
|
||||
|
||||
func _process_columns(colstr string) (columns []DB_Column) {
|
||||
func processColumns(colstr string) (columns []DB_Column) {
|
||||
if colstr == "" {
|
||||
return columns
|
||||
}
|
||||
colstr = strings.Replace(colstr, " as ", " AS ", -1)
|
||||
for _, segment := range strings.Split(colstr, ",") {
|
||||
var outcol DB_Column
|
||||
dothalves := strings.Split(strings.TrimSpace(segment), ".")
|
||||
dotHalves := strings.Split(strings.TrimSpace(segment), ".")
|
||||
|
||||
var halves []string
|
||||
if len(dothalves) == 2 {
|
||||
outcol.Table = dothalves[0]
|
||||
halves = strings.Split(dothalves[1], " AS ")
|
||||
if len(dotHalves) == 2 {
|
||||
outcol.Table = dotHalves[0]
|
||||
halves = strings.Split(dotHalves[1], " AS ")
|
||||
} else {
|
||||
halves = strings.Split(dothalves[0], " AS ")
|
||||
halves = strings.Split(dotHalves[0], " AS ")
|
||||
}
|
||||
|
||||
halves[0] = strings.TrimSpace(halves[0])
|
||||
@ -40,7 +46,7 @@ func _process_columns(colstr string) (columns []DB_Column) {
|
||||
return columns
|
||||
}
|
||||
|
||||
func _process_orderby(orderstr string) (order []DB_Order) {
|
||||
func processOrderby(orderstr string) (order []DB_Order) {
|
||||
if orderstr == "" {
|
||||
return order
|
||||
}
|
||||
@ -57,7 +63,7 @@ func _process_orderby(orderstr string) (order []DB_Order) {
|
||||
return order
|
||||
}
|
||||
|
||||
func _processJoiner(joinstr string) (joiner []DB_Joiner) {
|
||||
func processJoiner(joinstr string) (joiner []DB_Joiner) {
|
||||
if joinstr == "" {
|
||||
return joiner
|
||||
}
|
||||
@ -68,9 +74,9 @@ func _processJoiner(joinstr string) (joiner []DB_Joiner) {
|
||||
var parseOffset int
|
||||
var left, right string
|
||||
|
||||
left, parseOffset = _getIdentifier(segment, parseOffset)
|
||||
outjoin.Operator, parseOffset = _getOperator(segment, parseOffset+1)
|
||||
right, parseOffset = _getIdentifier(segment, parseOffset+1)
|
||||
left, parseOffset = getIdentifier(segment, parseOffset)
|
||||
outjoin.Operator, parseOffset = getOperator(segment, parseOffset+1)
|
||||
right, parseOffset = getIdentifier(segment, parseOffset+1)
|
||||
|
||||
left_column := strings.Split(left, ".")
|
||||
right_column := strings.Split(right, ".")
|
||||
@ -84,7 +90,7 @@ func _processJoiner(joinstr string) (joiner []DB_Joiner) {
|
||||
return joiner
|
||||
}
|
||||
|
||||
func _processWhere(wherestr string) (where []DB_Where) {
|
||||
func processWhere(wherestr string) (where []DB_Where) {
|
||||
if wherestr == "" {
|
||||
return where
|
||||
}
|
||||
@ -144,7 +150,7 @@ func _processWhere(wherestr string) (where []DB_Where) {
|
||||
//fmt.Println("len(halves)",len(halves[1]))
|
||||
//fmt.Println("preI",string(halves[1][preI]))
|
||||
//fmt.Println("msg prior to preI",halves[1][0:preI])
|
||||
i = _skipFunctionCall(segment, i-1)
|
||||
i = skipFunctionCall(segment, i-1)
|
||||
//fmt.Println("i",i)
|
||||
//fmt.Println("msg prior to i-1",halves[1][0:i-1])
|
||||
//fmt.Println("string(i-1)",string(halves[1][i-1]))
|
||||
@ -179,7 +185,7 @@ func _processWhere(wherestr string) (where []DB_Where) {
|
||||
return where
|
||||
}
|
||||
|
||||
func _process_set(setstr string) (setter []DB_Setter) {
|
||||
func processSet(setstr string) (setter []DB_Setter) {
|
||||
if setstr == "" {
|
||||
return setter
|
||||
}
|
||||
@ -192,7 +198,7 @@ func _process_set(setstr string) (setter []DB_Setter) {
|
||||
setstr += ","
|
||||
for i := 0; i < len(setstr); i++ {
|
||||
if setstr[i] == '(' {
|
||||
i = _skipFunctionCall(setstr, i-1)
|
||||
i = skipFunctionCall(setstr, i-1)
|
||||
setset = append(setset, setstr[lastItem:i+1])
|
||||
buffer = ""
|
||||
lastItem = i + 2
|
||||
@ -208,12 +214,12 @@ func _process_set(setstr string) (setter []DB_Setter) {
|
||||
// Second pass. Break this setitem into manageable chunks
|
||||
buffer = ""
|
||||
for _, setitem := range setset {
|
||||
var tmp_setter DB_Setter
|
||||
var tmpSetter DB_Setter
|
||||
halves := strings.Split(setitem, "=")
|
||||
if len(halves) != 2 {
|
||||
continue
|
||||
}
|
||||
tmp_setter.Column = strings.TrimSpace(halves[0])
|
||||
tmpSetter.Column = strings.TrimSpace(halves[0])
|
||||
|
||||
halves[1] += ")"
|
||||
var optype int // 0: None, 1: Number, 2: Column, 3: Function, 4: String, 5: Operator
|
||||
@ -237,7 +243,7 @@ func _process_set(setstr string) (setter []DB_Setter) {
|
||||
buffer = string(char)
|
||||
} else if char == '?' {
|
||||
//fmt.Println("Expr:","?")
|
||||
tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{"?", "substitute"})
|
||||
tmpSetter.Expr = append(tmpSetter.Expr, DB_Token{"?", "substitute"})
|
||||
}
|
||||
case 1: // number
|
||||
if '0' <= char && char <= '9' {
|
||||
@ -246,7 +252,7 @@ func _process_set(setstr string) (setter []DB_Setter) {
|
||||
optype = 0
|
||||
i--
|
||||
//fmt.Println("Expr:",buffer)
|
||||
tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{buffer, "number"})
|
||||
tmpSetter.Expr = append(tmpSetter.Expr, DB_Token{buffer, "number"})
|
||||
}
|
||||
case 2: // column
|
||||
if ('a' <= char && char <= 'z') || ('A' <= char && char <= 'Z') || char == '_' {
|
||||
@ -258,7 +264,7 @@ func _process_set(setstr string) (setter []DB_Setter) {
|
||||
optype = 0
|
||||
i--
|
||||
//fmt.Println("Expr:",buffer)
|
||||
tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{buffer, "column"})
|
||||
tmpSetter.Expr = append(tmpSetter.Expr, DB_Token{buffer, "column"})
|
||||
}
|
||||
case 3: // function
|
||||
var preI = i
|
||||
@ -266,14 +272,14 @@ func _process_set(setstr string) (setter []DB_Setter) {
|
||||
//fmt.Println("len(halves)",len(halves[1]))
|
||||
//fmt.Println("preI",string(halves[1][preI]))
|
||||
//fmt.Println("msg prior to preI",halves[1][0:preI])
|
||||
i = _skipFunctionCall(halves[1], i-1)
|
||||
i = skipFunctionCall(halves[1], i-1)
|
||||
//fmt.Println("i",i)
|
||||
//fmt.Println("msg prior to i-1",halves[1][0:i-1])
|
||||
//fmt.Println("string(i-1)",string(halves[1][i-1]))
|
||||
//fmt.Println("string(i)",string(halves[1][i]))
|
||||
buffer += halves[1][preI:i] + string(halves[1][i])
|
||||
//fmt.Println("Expr:",buffer)
|
||||
tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{buffer, "function"})
|
||||
tmpSetter.Expr = append(tmpSetter.Expr, DB_Token{buffer, "function"})
|
||||
optype = 0
|
||||
case 4: // string
|
||||
if char != '\'' {
|
||||
@ -281,7 +287,7 @@ func _process_set(setstr string) (setter []DB_Setter) {
|
||||
} else {
|
||||
optype = 0
|
||||
//fmt.Println("Expr:",buffer)
|
||||
tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{buffer, "string"})
|
||||
tmpSetter.Expr = append(tmpSetter.Expr, DB_Token{buffer, "string"})
|
||||
}
|
||||
case 5: // operator
|
||||
if _is_op_byte(char) {
|
||||
@ -290,19 +296,19 @@ func _process_set(setstr string) (setter []DB_Setter) {
|
||||
optype = 0
|
||||
i--
|
||||
//fmt.Println("Expr:",buffer)
|
||||
tmp_setter.Expr = append(tmp_setter.Expr, DB_Token{buffer, "operator"})
|
||||
tmpSetter.Expr = append(tmpSetter.Expr, DB_Token{buffer, "operator"})
|
||||
}
|
||||
default:
|
||||
panic("Bad optype in _process_set")
|
||||
}
|
||||
}
|
||||
setter = append(setter, tmp_setter)
|
||||
setter = append(setter, tmpSetter)
|
||||
}
|
||||
//fmt.Println("setter",setter)
|
||||
return setter
|
||||
}
|
||||
|
||||
func _processLimit(limitstr string) (limiter DB_Limit) {
|
||||
func processLimit(limitstr string) (limiter DB_Limit) {
|
||||
halves := strings.Split(limitstr, ",")
|
||||
if len(halves) == 2 {
|
||||
limiter.Offset = halves[0]
|
||||
@ -321,7 +327,7 @@ func _is_op_rune(char rune) bool {
|
||||
return char == '<' || char == '>' || char == '=' || char == '!' || char == '*' || char == '%' || char == '+' || char == '-' || char == '/'
|
||||
}
|
||||
|
||||
func _processFields(fieldstr string) (fields []DB_Field) {
|
||||
func processFields(fieldstr string) (fields []DB_Field) {
|
||||
if fieldstr == "" {
|
||||
return fields
|
||||
}
|
||||
@ -330,12 +336,12 @@ func _processFields(fieldstr string) (fields []DB_Field) {
|
||||
fieldstr += ","
|
||||
for i := 0; i < len(fieldstr); i++ {
|
||||
if fieldstr[i] == '(' {
|
||||
i = _skipFunctionCall(fieldstr, i-1)
|
||||
fields = append(fields, DB_Field{Name: fieldstr[lastItem : i+1], Type: _getIdentifierType(fieldstr[lastItem : i+1])})
|
||||
i = skipFunctionCall(fieldstr, i-1)
|
||||
fields = append(fields, DB_Field{Name: fieldstr[lastItem : i+1], Type: getIdentifierType(fieldstr[lastItem : i+1])})
|
||||
buffer = ""
|
||||
lastItem = i + 2
|
||||
} else if fieldstr[i] == ',' && buffer != "" {
|
||||
fields = append(fields, DB_Field{Name: buffer, Type: _getIdentifierType(buffer)})
|
||||
fields = append(fields, DB_Field{Name: buffer, Type: getIdentifierType(buffer)})
|
||||
buffer = ""
|
||||
lastItem = i + 1
|
||||
} else if (fieldstr[i] > 32) && fieldstr[i] != ',' && fieldstr[i] != ')' {
|
||||
@ -345,7 +351,7 @@ func _processFields(fieldstr string) (fields []DB_Field) {
|
||||
return fields
|
||||
}
|
||||
|
||||
func _getIdentifierType(identifier string) string {
|
||||
func getIdentifierType(identifier string) string {
|
||||
if ('a' <= identifier[0] && identifier[0] <= 'z') || ('A' <= identifier[0] && identifier[0] <= 'Z') {
|
||||
if identifier[len(identifier)-1] == ')' {
|
||||
return "function"
|
||||
@ -358,12 +364,12 @@ func _getIdentifierType(identifier string) string {
|
||||
return "literal"
|
||||
}
|
||||
|
||||
func _getIdentifier(segment string, startOffset int) (out string, i int) {
|
||||
func getIdentifier(segment string, startOffset int) (out string, i int) {
|
||||
segment = strings.TrimSpace(segment)
|
||||
segment += " " // Avoid overflow bugs with slicing
|
||||
for i = startOffset; i < len(segment); i++ {
|
||||
if segment[i] == '(' {
|
||||
i = _skipFunctionCall(segment, i)
|
||||
i = skipFunctionCall(segment, i)
|
||||
return strings.TrimSpace(segment[startOffset:i]), (i - 1)
|
||||
}
|
||||
if (segment[i] == ' ' || _is_op_byte(segment[i])) && i != startOffset {
|
||||
@ -373,7 +379,7 @@ func _getIdentifier(segment string, startOffset int) (out string, i int) {
|
||||
return strings.TrimSpace(segment[startOffset:]), (i - 1)
|
||||
}
|
||||
|
||||
func _getOperator(segment string, startOffset int) (out string, i int) {
|
||||
func getOperator(segment string, startOffset int) (out string, i int) {
|
||||
segment = strings.TrimSpace(segment)
|
||||
segment += " " // Avoid overflow bugs with slicing
|
||||
for i = startOffset; i < len(segment); i++ {
|
||||
@ -384,7 +390,7 @@ func _getOperator(segment string, startOffset int) (out string, i int) {
|
||||
return strings.TrimSpace(segment[startOffset:]), (i - 1)
|
||||
}
|
||||
|
||||
func _skipFunctionCall(data string, index int) int {
|
||||
func skipFunctionCall(data string, index int) int {
|
||||
var braceCount int
|
||||
for ; index < len(data); index++ {
|
||||
char := data[index]
|
||||
|
@ -269,6 +269,8 @@ func write_selects(adapter qgen.DB_Adapter) error {
|
||||
|
||||
adapter.SimpleSelect("getSync", "sync", "last_update", "", "", "")
|
||||
|
||||
adapter.SimpleSelect("getAttachment", "attachments", "sectionID, sectionTable, originID, originTable, uploadedBy, path", "path = ? AND sectionID = ? AND sectionTable = ?", "", "")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -334,6 +336,8 @@ func write_inserts(adapter qgen.DB_Adapter) error {
|
||||
|
||||
adapter.SimpleInsert("addAdminlogEntry", "administration_logs", "action, elementID, elementType, ipaddress, actorID, doneAt", "?,?,?,?,?,UTC_TIMESTAMP()")
|
||||
|
||||
adapter.SimpleInsert("addAttachment", "attachments", "sectionID, sectionTable, originID, originTable, uploadedBy, path", "?,?,?,?,?,?")
|
||||
|
||||
adapter.SimpleInsert("createWordFilter", "word_filters", "find, replacement", "?,?")
|
||||
|
||||
return nil
|
||||
|
1
reply.go
@ -50,6 +50,7 @@ type Reply struct {
|
||||
LikeCount int
|
||||
}
|
||||
|
||||
// Copy gives you a non-pointer concurrency safe copy of the reply
|
||||
func (reply *Reply) Copy() Reply {
|
||||
return *reply
|
||||
}
|
||||
|
@ -6,8 +6,8 @@ import "log"
|
||||
//import "strings"
|
||||
import "os"
|
||||
|
||||
var route_list []Route
|
||||
var route_groups []RouteGroup
|
||||
var routeList []Route
|
||||
var routeGroups []RouteGroup
|
||||
|
||||
func main() {
|
||||
log.Println("Generating the router...")
|
||||
@ -16,9 +16,9 @@ func main() {
|
||||
routes()
|
||||
|
||||
var out string
|
||||
var fdata = "// Code generated by. DO NOT EDIT.\n/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */\n"
|
||||
var fileData = "// Code generated by. DO NOT EDIT.\n/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */\n"
|
||||
|
||||
for _, route := range route_list {
|
||||
for _, route := range routeList {
|
||||
var end int
|
||||
if route.Path[len(route.Path)-1] == '/' {
|
||||
end = len(route.Path) - 1
|
||||
@ -36,7 +36,7 @@ func main() {
|
||||
out += ")\n\t\t\treturn"
|
||||
}
|
||||
|
||||
for _, group := range route_groups {
|
||||
for _, group := range routeGroups {
|
||||
var end int
|
||||
if group.Path[len(group.Path)-1] == '/' {
|
||||
end = len(group.Path) - 1
|
||||
@ -46,10 +46,10 @@ func main() {
|
||||
out += `
|
||||
case "` + group.Path[0:end] + `":
|
||||
switch(req.URL.Path) {`
|
||||
var default_route Route
|
||||
var defaultRoute Route
|
||||
for _, route := range group.Routes {
|
||||
if group.Path == route.Path {
|
||||
default_route = route
|
||||
defaultRoute = route
|
||||
continue
|
||||
}
|
||||
|
||||
@ -64,13 +64,13 @@ func main() {
|
||||
out += ")\n\t\t\t\t\treturn"
|
||||
}
|
||||
|
||||
if default_route.Name != "" {
|
||||
if defaultRoute.Name != "" {
|
||||
out += "\n\t\t\t\tdefault:"
|
||||
if default_route.Before != "" {
|
||||
out += "\n\t\t\t\t\t" + default_route.Before
|
||||
if defaultRoute.Before != "" {
|
||||
out += "\n\t\t\t\t\t" + defaultRoute.Before
|
||||
}
|
||||
out += "\n\t\t\t\t\t" + default_route.Name + "(w,req,user"
|
||||
for _, item := range default_route.Vars {
|
||||
out += "\n\t\t\t\t\t" + defaultRoute.Name + "(w,req,user"
|
||||
for _, item := range defaultRoute.Vars {
|
||||
out += ", " + item
|
||||
}
|
||||
out += ")\n\t\t\t\t\treturn"
|
||||
@ -78,7 +78,7 @@ func main() {
|
||||
out += "\n\t\t\t}"
|
||||
}
|
||||
|
||||
fdata += `package main
|
||||
fileData += `package main
|
||||
|
||||
import "log"
|
||||
import "strings"
|
||||
@ -209,11 +209,11 @@ func (router *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
NotFound(w,req)
|
||||
}
|
||||
`
|
||||
write_file("./gen_router.go", fdata)
|
||||
writeFile("./gen_router.go", fileData)
|
||||
log.Println("Successfully generated the router")
|
||||
}
|
||||
|
||||
func write_file(name string, content string) {
|
||||
func writeFile(name string, content string) {
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -13,11 +13,11 @@ type RouteGroup struct {
|
||||
}
|
||||
|
||||
func addRoute(fname string, path string, before string, vars ...string) {
|
||||
route_list = append(route_list, Route{fname, path, before, vars})
|
||||
routeList = append(routeList, Route{fname, path, before, vars})
|
||||
}
|
||||
|
||||
func addRouteGroup(path string, routes ...Route) {
|
||||
route_groups = append(route_groups, RouteGroup{path, routes})
|
||||
routeGroups = append(routeGroups, RouteGroup{path, routes})
|
||||
}
|
||||
|
||||
func routes() {
|
||||
@ -31,6 +31,7 @@ func routes() {
|
||||
//addRoute("routeTopicCreate","/topics/create/","","extra_data")
|
||||
//addRoute("routeTopics","/topics/",""/*,"&groups","&forums"*/)
|
||||
addRoute("routeChangeTheme", "/theme/", "")
|
||||
addRoute("routeShowAttachment", "/attachs/", "", "extra_data")
|
||||
|
||||
addRouteGroup("/report/",
|
||||
Route{"routeReportSubmit", "/report/submit/", "", []string{"extra_data"}},
|
||||
|
24
routes.go
@ -26,7 +26,7 @@ var tList []interface{}
|
||||
|
||||
//var nList []string
|
||||
var successJSONBytes = []byte(`{"success":"1"}`)
|
||||
var cacheControlMaxAge = "max-age=" + strconv.Itoa(day)
|
||||
var cacheControlMaxAge = "max-age=" + strconv.Itoa(day) // TODO: Make this a config value
|
||||
|
||||
// HTTPSRedirect is a connection handler which redirects all HTTP requests to HTTPS
|
||||
type HTTPSRedirect struct {
|
||||
@ -171,11 +171,25 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user User) {
|
||||
canSee = group.CanSee
|
||||
}
|
||||
|
||||
// We need a list of the visible forums for Quick Topic
|
||||
var forumList []Forum
|
||||
|
||||
for _, fid := range canSee {
|
||||
forum := fstore.DirtyGet(fid)
|
||||
if forum.Name != "" && forum.Active {
|
||||
if forum.ParentType == "" || forum.ParentType == "forum" {
|
||||
// Optimise Quick Topic away for guests
|
||||
if user.Loggedin {
|
||||
fcopy := forum.Copy()
|
||||
// TODO: Add a hook here for plugin_socialgroups
|
||||
forumList = append(forumList, fcopy)
|
||||
}
|
||||
}
|
||||
// ? - Should we be showing plugin_socialgroups posts on /topics/?
|
||||
// ? - Would it be useful, if we could post in social groups from /topics/?
|
||||
fidList = append(fidList, strconv.Itoa(fid))
|
||||
qlist += "?,"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,7 +279,7 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user User) {
|
||||
topicItem.LastUser = userList[topicItem.LastReplyBy]
|
||||
}
|
||||
|
||||
pi := TopicsPage{"Topic List", user, headerVars, topicList}
|
||||
pi := TopicsPage{"All Topics", user, headerVars, topicList, forumList, config.DefaultForum}
|
||||
if preRenderHooks["pre_render_topic_list"] != nil {
|
||||
if runPreRenderHook("pre_render_topic_list", w, r, &user, &pi) {
|
||||
return
|
||||
@ -495,7 +509,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
|
||||
|
||||
BuildWidgets("view_topic", &topic, headerVars, r)
|
||||
|
||||
topic.ContentHTML = parseMessage(topic.Content)
|
||||
topic.ContentHTML = parseMessage(topic.Content, topic.ParentID, "forums")
|
||||
topic.ContentLines = strings.Count(topic.Content, "\n")
|
||||
|
||||
// We don't want users posting in locked topics...
|
||||
@ -573,7 +587,7 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user User) {
|
||||
|
||||
replyItem.UserLink = buildProfileURL(nameToSlug(replyItem.CreatedByName), replyItem.CreatedBy)
|
||||
replyItem.ParentID = topic.ID
|
||||
replyItem.ContentHtml = parseMessage(replyItem.Content)
|
||||
replyItem.ContentHtml = parseMessage(replyItem.Content, topic.ParentID, "forums")
|
||||
replyItem.ContentLines = strings.Count(replyItem.Content, "\n")
|
||||
|
||||
postGroup, err = gstore.Get(replyItem.Group)
|
||||
@ -744,7 +758,7 @@ func routeProfile(w http.ResponseWriter, r *http.Request, user User) {
|
||||
|
||||
// TODO: Add a hook here
|
||||
|
||||
replyList = append(replyList, ReplyUser{rid, puser.ID, replyContent, parseMessage(replyContent), replyCreatedBy, buildProfileURL(nameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
|
||||
replyList = append(replyList, ReplyUser{rid, puser.ID, replyContent, parseMessage(replyContent, 0, ""), replyCreatedBy, buildProfileURL(nameToSlug(replyCreatedByName), replyCreatedBy), replyCreatedByName, replyGroup, replyCreatedAt, replyLastEdit, replyLastEditBy, replyAvatar, replyClassName, replyLines, replyTag, "", "", "", 0, "", replyLiked, replyLikeCount, "", ""})
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
|
@ -192,8 +192,8 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV
|
||||
return headerVars, stats, false
|
||||
}
|
||||
|
||||
stats.Users = users.GetGlobalCount()
|
||||
stats.Forums = fstore.GetGlobalCount() // TODO: Stop it from showing the blanked forums
|
||||
stats.Users = users.GlobalCount()
|
||||
stats.Forums = fstore.GlobalCount() // TODO: Stop it from showing the blanked forums
|
||||
stats.Settings = len(headerVars.Settings)
|
||||
stats.WordFilters = len(wordFilterBox.Load().(WordFilterBox))
|
||||
stats.Themes = len(themes)
|
||||
|
1
site.go
@ -12,6 +12,7 @@ var config Config
|
||||
var dev DevConfig
|
||||
|
||||
type Site struct {
|
||||
ShortName string
|
||||
Name string // ? - Move this into the settings table? Should we make a second version of this for the abbreviation shown in the navbar?
|
||||
Email string // ? - Move this into the settings table?
|
||||
URL string
|
||||
|
@ -20,33 +20,37 @@ func template_forum(tmpl_forum_vars ForumPage, w http.ResponseWriter) {
|
||||
w.Write(header_0)
|
||||
w.Write([]byte(tmpl_forum_vars.Title))
|
||||
w.Write(header_1)
|
||||
w.Write([]byte(tmpl_forum_vars.Header.ThemeName))
|
||||
w.Write([]byte(tmpl_forum_vars.Header.Site.Name))
|
||||
w.Write(header_2)
|
||||
w.Write([]byte(tmpl_forum_vars.Header.ThemeName))
|
||||
w.Write(header_3)
|
||||
if len(tmpl_forum_vars.Header.Stylesheets) != 0 {
|
||||
for _, item := range tmpl_forum_vars.Header.Stylesheets {
|
||||
w.Write(header_3)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_4)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_5)
|
||||
}
|
||||
}
|
||||
w.Write(header_6)
|
||||
if len(tmpl_forum_vars.Header.Scripts) != 0 {
|
||||
for _, item := range tmpl_forum_vars.Header.Scripts {
|
||||
w.Write(header_6)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_7)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_8)
|
||||
w.Write([]byte(tmpl_forum_vars.CurrentUser.Session))
|
||||
w.Write(header_9)
|
||||
if !tmpl_forum_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_10)
|
||||
}
|
||||
}
|
||||
w.Write(header_9)
|
||||
w.Write([]byte(tmpl_forum_vars.CurrentUser.Session))
|
||||
w.Write(header_10)
|
||||
w.Write([]byte(tmpl_forum_vars.Header.Site.URL))
|
||||
w.Write(header_11)
|
||||
if !tmpl_forum_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_12)
|
||||
}
|
||||
w.Write(header_13)
|
||||
w.Write(menu_0)
|
||||
w.Write(menu_1)
|
||||
w.Write([]byte(tmpl_forum_vars.Header.Site.Name))
|
||||
w.Write([]byte(tmpl_forum_vars.Header.Site.ShortName))
|
||||
w.Write(menu_2)
|
||||
if tmpl_forum_vars.CurrentUser.Loggedin {
|
||||
w.Write(menu_3)
|
||||
@ -58,16 +62,16 @@ w.Write(menu_5)
|
||||
w.Write(menu_6)
|
||||
}
|
||||
w.Write(menu_7)
|
||||
w.Write(header_12)
|
||||
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_13)
|
||||
}
|
||||
w.Write(header_14)
|
||||
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_15)
|
||||
}
|
||||
w.Write(header_16)
|
||||
if len(tmpl_forum_vars.Header.NoticeList) != 0 {
|
||||
for _, item := range tmpl_forum_vars.Header.NoticeList {
|
||||
w.Write(header_15)
|
||||
w.Write(header_17)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_16)
|
||||
w.Write(header_18)
|
||||
}
|
||||
}
|
||||
if tmpl_forum_vars.Page > 1 {
|
||||
@ -106,65 +110,75 @@ w.Write(forum_14)
|
||||
w.Write(forum_15)
|
||||
}
|
||||
w.Write(forum_16)
|
||||
if len(tmpl_forum_vars.ItemList) != 0 {
|
||||
for _, item := range tmpl_forum_vars.ItemList {
|
||||
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
|
||||
w.Write(forum_17)
|
||||
if item.Sticky {
|
||||
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
|
||||
w.Write(forum_18)
|
||||
} else {
|
||||
if item.IsClosed {
|
||||
if tmpl_forum_vars.CurrentUser.Perms.UploadFiles {
|
||||
w.Write(forum_19)
|
||||
}
|
||||
}
|
||||
w.Write(forum_20)
|
||||
if item.Creator.Avatar != "" {
|
||||
w.Write(forum_21)
|
||||
w.Write([]byte(item.Creator.Avatar))
|
||||
w.Write(forum_22)
|
||||
}
|
||||
w.Write(forum_21)
|
||||
if len(tmpl_forum_vars.ItemList) != 0 {
|
||||
for _, item := range tmpl_forum_vars.ItemList {
|
||||
w.Write(forum_22)
|
||||
if item.Sticky {
|
||||
w.Write(forum_23)
|
||||
w.Write([]byte(strconv.Itoa(item.PostCount)))
|
||||
w.Write(forum_24)
|
||||
w.Write([]byte(item.LastReplyAt))
|
||||
w.Write(forum_25)
|
||||
w.Write([]byte(item.Link))
|
||||
w.Write(forum_26)
|
||||
w.Write([]byte(item.Title))
|
||||
w.Write(forum_27)
|
||||
w.Write([]byte(item.Creator.Link))
|
||||
w.Write(forum_28)
|
||||
w.Write([]byte(item.Creator.Name))
|
||||
w.Write(forum_29)
|
||||
} else {
|
||||
if item.IsClosed {
|
||||
w.Write(forum_24)
|
||||
}
|
||||
}
|
||||
w.Write(forum_25)
|
||||
if item.Creator.Avatar != "" {
|
||||
w.Write(forum_26)
|
||||
w.Write([]byte(item.Creator.Avatar))
|
||||
w.Write(forum_27)
|
||||
}
|
||||
w.Write(forum_28)
|
||||
w.Write([]byte(strconv.Itoa(item.PostCount)))
|
||||
w.Write(forum_29)
|
||||
w.Write([]byte(item.LastReplyAt))
|
||||
w.Write(forum_30)
|
||||
w.Write([]byte(item.Link))
|
||||
w.Write(forum_31)
|
||||
w.Write([]byte(item.Title))
|
||||
w.Write(forum_32)
|
||||
w.Write([]byte(item.Creator.Link))
|
||||
w.Write(forum_33)
|
||||
w.Write([]byte(item.Creator.Name))
|
||||
w.Write(forum_34)
|
||||
if item.IsClosed {
|
||||
w.Write(forum_35)
|
||||
}
|
||||
if item.Sticky {
|
||||
w.Write(forum_31)
|
||||
}
|
||||
w.Write(forum_32)
|
||||
if item.LastUser.Avatar != "" {
|
||||
w.Write(forum_33)
|
||||
w.Write([]byte(item.LastUser.Avatar))
|
||||
w.Write(forum_34)
|
||||
}
|
||||
w.Write(forum_35)
|
||||
w.Write([]byte(item.LastUser.Link))
|
||||
w.Write(forum_36)
|
||||
w.Write([]byte(item.LastUser.Name))
|
||||
}
|
||||
w.Write(forum_37)
|
||||
w.Write([]byte(item.LastReplyAt))
|
||||
if item.LastUser.Avatar != "" {
|
||||
w.Write(forum_38)
|
||||
w.Write([]byte(item.LastUser.Avatar))
|
||||
w.Write(forum_39)
|
||||
}
|
||||
w.Write(forum_40)
|
||||
w.Write([]byte(item.LastUser.Link))
|
||||
w.Write(forum_41)
|
||||
w.Write([]byte(item.LastUser.Name))
|
||||
w.Write(forum_42)
|
||||
w.Write([]byte(item.LastReplyAt))
|
||||
w.Write(forum_43)
|
||||
}
|
||||
} else {
|
||||
w.Write(forum_39)
|
||||
w.Write(forum_44)
|
||||
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
|
||||
w.Write(forum_40)
|
||||
w.Write(forum_45)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
|
||||
w.Write(forum_41)
|
||||
w.Write(forum_46)
|
||||
}
|
||||
w.Write(forum_42)
|
||||
w.Write(forum_47)
|
||||
}
|
||||
w.Write(forum_43)
|
||||
w.Write(forum_48)
|
||||
w.Write(footer_0)
|
||||
if len(tmpl_forum_vars.Header.Themes) != 0 {
|
||||
for _, item := range tmpl_forum_vars.Header.Themes {
|
||||
|
@ -19,33 +19,37 @@ func template_forums(tmpl_forums_vars ForumsPage, w http.ResponseWriter) {
|
||||
w.Write(header_0)
|
||||
w.Write([]byte(tmpl_forums_vars.Title))
|
||||
w.Write(header_1)
|
||||
w.Write([]byte(tmpl_forums_vars.Header.ThemeName))
|
||||
w.Write([]byte(tmpl_forums_vars.Header.Site.Name))
|
||||
w.Write(header_2)
|
||||
w.Write([]byte(tmpl_forums_vars.Header.ThemeName))
|
||||
w.Write(header_3)
|
||||
if len(tmpl_forums_vars.Header.Stylesheets) != 0 {
|
||||
for _, item := range tmpl_forums_vars.Header.Stylesheets {
|
||||
w.Write(header_3)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_4)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_5)
|
||||
}
|
||||
}
|
||||
w.Write(header_6)
|
||||
if len(tmpl_forums_vars.Header.Scripts) != 0 {
|
||||
for _, item := range tmpl_forums_vars.Header.Scripts {
|
||||
w.Write(header_6)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_7)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_8)
|
||||
w.Write([]byte(tmpl_forums_vars.CurrentUser.Session))
|
||||
w.Write(header_9)
|
||||
if !tmpl_forums_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_10)
|
||||
}
|
||||
}
|
||||
w.Write(header_9)
|
||||
w.Write([]byte(tmpl_forums_vars.CurrentUser.Session))
|
||||
w.Write(header_10)
|
||||
w.Write([]byte(tmpl_forums_vars.Header.Site.URL))
|
||||
w.Write(header_11)
|
||||
if !tmpl_forums_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_12)
|
||||
}
|
||||
w.Write(header_13)
|
||||
w.Write(menu_0)
|
||||
w.Write(menu_1)
|
||||
w.Write([]byte(tmpl_forums_vars.Header.Site.Name))
|
||||
w.Write([]byte(tmpl_forums_vars.Header.Site.ShortName))
|
||||
w.Write(menu_2)
|
||||
if tmpl_forums_vars.CurrentUser.Loggedin {
|
||||
w.Write(menu_3)
|
||||
@ -57,16 +61,16 @@ w.Write(menu_5)
|
||||
w.Write(menu_6)
|
||||
}
|
||||
w.Write(menu_7)
|
||||
w.Write(header_12)
|
||||
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_13)
|
||||
}
|
||||
w.Write(header_14)
|
||||
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_15)
|
||||
}
|
||||
w.Write(header_16)
|
||||
if len(tmpl_forums_vars.Header.NoticeList) != 0 {
|
||||
for _, item := range tmpl_forums_vars.Header.NoticeList {
|
||||
w.Write(header_15)
|
||||
w.Write(header_17)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_16)
|
||||
w.Write(header_18)
|
||||
}
|
||||
}
|
||||
w.Write(forums_0)
|
||||
|
@ -82,6 +82,7 @@ var template_create_topic_handle func(CreateTopicPage, http.ResponseWriter) = fu
|
||||
}
|
||||
}
|
||||
|
||||
// ? - Add template hooks?
|
||||
func compileTemplates() error {
|
||||
var c CTemplateSet
|
||||
|
||||
@ -128,6 +129,7 @@ func compileTemplates() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Use a dummy forum list to avoid o(n) problems
|
||||
var forumList []Forum
|
||||
forums, err := fstore.GetAll()
|
||||
if err != nil {
|
||||
@ -147,7 +149,7 @@ func compileTemplates() error {
|
||||
|
||||
var topicsList []*TopicsRow
|
||||
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, "Date", "Date", user3.ID, 1, "", "127.0.0.1", 0, 1, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"})
|
||||
topicsPage := TopicsPage{"Topic List", user, headerVars, topicsList}
|
||||
topicsPage := TopicsPage{"Topic List", user, headerVars, topicsList, forumList, config.DefaultForum}
|
||||
topicsTmpl, err := c.compileTemplate("topics.html", "templates/", "TopicsPage", topicsPage, varList)
|
||||
if err != nil {
|
||||
return err
|
||||
|
404
template_list.go
@ -5,31 +5,36 @@ var header_0 = []byte(`<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>`)
|
||||
var header_1 = []byte(`</title>
|
||||
var header_1 = []byte(` | `)
|
||||
var header_2 = []byte(`</title>
|
||||
<link href="/static/`)
|
||||
var header_2 = []byte(`/main.css" rel="stylesheet" type="text/css">
|
||||
var header_3 = []byte(`/main.css" rel="stylesheet" type="text/css">
|
||||
`)
|
||||
var header_3 = []byte(`
|
||||
var header_4 = []byte(`
|
||||
<link href="/static/`)
|
||||
var header_4 = []byte(`" rel="stylesheet" type="text/css">
|
||||
`)
|
||||
var header_5 = []byte(`
|
||||
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script>
|
||||
var header_5 = []byte(`" rel="stylesheet" type="text/css">
|
||||
`)
|
||||
var header_6 = []byte(`
|
||||
<script type="text/javascript" src="/static/`)
|
||||
var header_7 = []byte(`"></script>
|
||||
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script>
|
||||
`)
|
||||
var header_8 = []byte(`
|
||||
<script type="text/javascript">var session = "`)
|
||||
var header_9 = []byte(`";</script>
|
||||
var header_7 = []byte(`
|
||||
<script type="text/javascript" src="/static/`)
|
||||
var header_8 = []byte(`"></script>
|
||||
`)
|
||||
var header_9 = []byte(`
|
||||
<script type="text/javascript">
|
||||
var session = "`)
|
||||
var header_10 = []byte(`";
|
||||
var siteURL = "`)
|
||||
var header_11 = []byte(`";
|
||||
</script>
|
||||
<script type="text/javascript" src="/static/global.js"></script>
|
||||
<meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" />
|
||||
</head>
|
||||
<body>
|
||||
<style>`)
|
||||
var header_10 = []byte(`.supermod_only { display: none !important; }`)
|
||||
var header_11 = []byte(`</style>
|
||||
var header_12 = []byte(`.supermod_only { display: none !important; }`)
|
||||
var header_13 = []byte(`</style>
|
||||
<div class="container">
|
||||
`)
|
||||
var menu_0 = []byte(`<nav class="nav">
|
||||
@ -41,7 +46,6 @@ var menu_1 = []byte(`
|
||||
var menu_2 = []byte(`</a></li>
|
||||
<li class="menu_left menu_forums"><a href="/forums/">Forums</a></li>
|
||||
<li class="menu_left menu_topics"><a href="/">Topics</a></li>
|
||||
<li class="menu_left menu_create_topic"><a href="/topics/create/">Create Topic</a></li>
|
||||
<li id="general_alerts" class="menu_right menu_alerts">
|
||||
<div class="alert_bell"></div>
|
||||
<div class="alert_counter"></div>
|
||||
@ -68,13 +72,13 @@ var menu_7 = []byte(`
|
||||
<div style="clear: both;"></div>
|
||||
</nav>
|
||||
`)
|
||||
var header_12 = []byte(`
|
||||
var header_14 = []byte(`
|
||||
<div id="back"><div id="main" `)
|
||||
var header_13 = []byte(`class="shrink_main"`)
|
||||
var header_14 = []byte(`>
|
||||
var header_15 = []byte(`class="shrink_main"`)
|
||||
var header_16 = []byte(`>
|
||||
`)
|
||||
var header_15 = []byte(`<div class="alert">`)
|
||||
var header_16 = []byte(`</div>`)
|
||||
var header_17 = []byte(`<div class="alert">`)
|
||||
var header_18 = []byte(`</div>`)
|
||||
var topic_0 = []byte(`
|
||||
|
||||
<form id="edit_topic_form" action='/topic/edit/submit/`)
|
||||
@ -117,120 +121,122 @@ var topic_18 = []byte(`
|
||||
<div class="rowitem passive editable_parent post_item `)
|
||||
var topic_19 = []byte(`" style="`)
|
||||
var topic_20 = []byte(`background-image:url(`)
|
||||
var topic_21 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `)
|
||||
var topic_22 = []byte(`-1`)
|
||||
var topic_23 = []byte(`0px;background-repeat:no-repeat, repeat-y;`)
|
||||
var topic_24 = []byte(`">
|
||||
var topic_21 = []byte(`), url(/static/`)
|
||||
var topic_22 = []byte(`/post-avatar-bg.jpg);background-position: 0px `)
|
||||
var topic_23 = []byte(`-1`)
|
||||
var topic_24 = []byte(`0px;background-repeat:no-repeat, repeat-y;`)
|
||||
var topic_25 = []byte(`">
|
||||
<p class="hide_on_edit topic_content user_content" style="margin:0;padding:0;">`)
|
||||
var topic_25 = []byte(`</p>
|
||||
var topic_26 = []byte(`</p>
|
||||
<textarea name="topic_content" class="show_on_edit topic_content_input">`)
|
||||
var topic_26 = []byte(`</textarea>
|
||||
var topic_27 = []byte(`</textarea>
|
||||
|
||||
<span class="controls">
|
||||
|
||||
<a href="`)
|
||||
var topic_27 = []byte(`" class="username real_username">`)
|
||||
var topic_28 = []byte(`</a>
|
||||
var topic_28 = []byte(`" class="username real_username">`)
|
||||
var topic_29 = []byte(`</a>
|
||||
`)
|
||||
var topic_29 = []byte(`<a href="/topic/like/submit/`)
|
||||
var topic_30 = []byte(`" class="mod_button" title="Love it" style="color:#202020;">
|
||||
var topic_30 = []byte(`<a href="/topic/like/submit/`)
|
||||
var topic_31 = []byte(`" class="mod_button" title="Love it" style="color:#202020;">
|
||||
<button class="username like_label"`)
|
||||
var topic_31 = []byte(` style="background-color:#D6FFD6;"`)
|
||||
var topic_32 = []byte(`></button></a>`)
|
||||
var topic_33 = []byte(`<a href='/topic/edit/`)
|
||||
var topic_34 = []byte(`' class="mod_button open_edit" style="font-weight:normal;" title="Edit Topic"><button class="username edit_label"></button></a>`)
|
||||
var topic_35 = []byte(`<a href='/topic/delete/submit/`)
|
||||
var topic_36 = []byte(`' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username trash_label"></button></a>`)
|
||||
var topic_37 = []byte(`<a class="mod_button" href='/topic/unlock/submit/`)
|
||||
var topic_38 = []byte(`' style="font-weight:normal;" title="Unlock Topic"><button class="username unlock_label"></button></a>`)
|
||||
var topic_39 = []byte(`<a href='/topic/lock/submit/`)
|
||||
var topic_40 = []byte(`' class="mod_button" style="font-weight:normal;" title="Lock Topic"><button class="username lock_label"></button></a>`)
|
||||
var topic_41 = []byte(`<a class="mod_button" href='/topic/unstick/submit/`)
|
||||
var topic_42 = []byte(`' style="font-weight:normal;" title="Unpin Topic"><button class="username unpin_label"></button></a>`)
|
||||
var topic_43 = []byte(`<a href='/topic/stick/submit/`)
|
||||
var topic_44 = []byte(`' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username pin_label"></button></a>`)
|
||||
var topic_45 = []byte(`<a class="mod_button" href='/users/ips/?ip=`)
|
||||
var topic_46 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`)
|
||||
var topic_47 = []byte(`
|
||||
var topic_32 = []byte(` style="background-color:#D6FFD6;"`)
|
||||
var topic_33 = []byte(`></button></a>`)
|
||||
var topic_34 = []byte(`<a href='/topic/edit/`)
|
||||
var topic_35 = []byte(`' class="mod_button open_edit" style="font-weight:normal;" title="Edit Topic"><button class="username edit_label"></button></a>`)
|
||||
var topic_36 = []byte(`<a href='/topic/delete/submit/`)
|
||||
var topic_37 = []byte(`' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username trash_label"></button></a>`)
|
||||
var topic_38 = []byte(`<a class="mod_button" href='/topic/unlock/submit/`)
|
||||
var topic_39 = []byte(`' style="font-weight:normal;" title="Unlock Topic"><button class="username unlock_label"></button></a>`)
|
||||
var topic_40 = []byte(`<a href='/topic/lock/submit/`)
|
||||
var topic_41 = []byte(`' class="mod_button" style="font-weight:normal;" title="Lock Topic"><button class="username lock_label"></button></a>`)
|
||||
var topic_42 = []byte(`<a class="mod_button" href='/topic/unstick/submit/`)
|
||||
var topic_43 = []byte(`' style="font-weight:normal;" title="Unpin Topic"><button class="username unpin_label"></button></a>`)
|
||||
var topic_44 = []byte(`<a href='/topic/stick/submit/`)
|
||||
var topic_45 = []byte(`' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username pin_label"></button></a>`)
|
||||
var topic_46 = []byte(`<a class="mod_button" href='/users/ips/?ip=`)
|
||||
var topic_47 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`)
|
||||
var topic_48 = []byte(`
|
||||
<a href="/report/submit/`)
|
||||
var topic_48 = []byte(`?session=`)
|
||||
var topic_49 = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username flag_label"></button></a>
|
||||
var topic_49 = []byte(`?session=`)
|
||||
var topic_50 = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username flag_label"></button></a>
|
||||
|
||||
`)
|
||||
var topic_50 = []byte(`<a class="username hide_on_micro like_count">`)
|
||||
var topic_51 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
|
||||
var topic_52 = []byte(`<a class="username hide_on_micro user_tag">`)
|
||||
var topic_53 = []byte(`</a>`)
|
||||
var topic_54 = []byte(`<a class="username hide_on_micro level">`)
|
||||
var topic_55 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
|
||||
var topic_56 = []byte(`
|
||||
var topic_51 = []byte(`<a class="username hide_on_micro like_count">`)
|
||||
var topic_52 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
|
||||
var topic_53 = []byte(`<a class="username hide_on_micro user_tag">`)
|
||||
var topic_54 = []byte(`</a>`)
|
||||
var topic_55 = []byte(`<a class="username hide_on_micro level">`)
|
||||
var topic_56 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
|
||||
var topic_57 = []byte(`
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</article>
|
||||
<div class="rowblock post_container" style="overflow: hidden;">`)
|
||||
var topic_57 = []byte(`
|
||||
var topic_58 = []byte(`
|
||||
<article class="rowitem passive deletable_block editable_parent post_item action_item">
|
||||
<span class="action_icon" style="font-size: 18px;padding-right: 5px;">`)
|
||||
var topic_58 = []byte(`</span>
|
||||
<span>`)
|
||||
var topic_59 = []byte(`</span>
|
||||
<span>`)
|
||||
var topic_60 = []byte(`</span>
|
||||
</article>
|
||||
`)
|
||||
var topic_60 = []byte(`
|
||||
var topic_61 = []byte(`
|
||||
<article class="rowitem passive deletable_block editable_parent post_item `)
|
||||
var topic_61 = []byte(`" style="`)
|
||||
var topic_62 = []byte(`background-image:url(`)
|
||||
var topic_63 = []byte(`), url(/static/post-avatar-bg.jpg);background-position: 0px `)
|
||||
var topic_64 = []byte(`-1`)
|
||||
var topic_65 = []byte(`0px;background-repeat:no-repeat, repeat-y;`)
|
||||
var topic_66 = []byte(`">
|
||||
var topic_62 = []byte(`" style="`)
|
||||
var topic_63 = []byte(`background-image:url(`)
|
||||
var topic_64 = []byte(`), url(/static/`)
|
||||
var topic_65 = []byte(`/post-avatar-bg.jpg);background-position: 0px `)
|
||||
var topic_66 = []byte(`-1`)
|
||||
var topic_67 = []byte(`0px;background-repeat:no-repeat, repeat-y;`)
|
||||
var topic_68 = []byte(`">
|
||||
`)
|
||||
var topic_67 = []byte(`
|
||||
var topic_69 = []byte(`
|
||||
<p class="editable_block user_content" style="margin:0;padding:0;">`)
|
||||
var topic_68 = []byte(`</p>
|
||||
var topic_70 = []byte(`</p>
|
||||
|
||||
<span class="controls">
|
||||
|
||||
<a href="`)
|
||||
var topic_69 = []byte(`" class="username real_username">`)
|
||||
var topic_70 = []byte(`</a>
|
||||
var topic_71 = []byte(`" class="username real_username">`)
|
||||
var topic_72 = []byte(`</a>
|
||||
`)
|
||||
var topic_71 = []byte(`<a href="/reply/like/submit/`)
|
||||
var topic_72 = []byte(`" class="mod_button" title="Love it" style="color:#202020;"><button class="username like_label"`)
|
||||
var topic_73 = []byte(` style="background-color:#D6FFD6;"`)
|
||||
var topic_74 = []byte(`></button></a>`)
|
||||
var topic_75 = []byte(`<a href="/reply/edit/submit/`)
|
||||
var topic_76 = []byte(`" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>`)
|
||||
var topic_77 = []byte(`<a href="/reply/delete/submit/`)
|
||||
var topic_78 = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>`)
|
||||
var topic_79 = []byte(`<a class="mod_button" href='/users/ips/?ip=`)
|
||||
var topic_80 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`)
|
||||
var topic_81 = []byte(`
|
||||
var topic_73 = []byte(`<a href="/reply/like/submit/`)
|
||||
var topic_74 = []byte(`" class="mod_button" title="Love it" style="color:#202020;"><button class="username like_label"`)
|
||||
var topic_75 = []byte(` style="background-color:#D6FFD6;"`)
|
||||
var topic_76 = []byte(`></button></a>`)
|
||||
var topic_77 = []byte(`<a href="/reply/edit/submit/`)
|
||||
var topic_78 = []byte(`" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>`)
|
||||
var topic_79 = []byte(`<a href="/reply/delete/submit/`)
|
||||
var topic_80 = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>`)
|
||||
var topic_81 = []byte(`<a class="mod_button" href='/users/ips/?ip=`)
|
||||
var topic_82 = []byte(`' style="font-weight:normal;" title="View IP"><button class="username ip_label"></button></a>`)
|
||||
var topic_83 = []byte(`
|
||||
<a href="/report/submit/`)
|
||||
var topic_82 = []byte(`?session=`)
|
||||
var topic_83 = []byte(`&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a>
|
||||
var topic_84 = []byte(`?session=`)
|
||||
var topic_85 = []byte(`&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a>
|
||||
|
||||
`)
|
||||
var topic_84 = []byte(`<a class="username hide_on_micro like_count">`)
|
||||
var topic_85 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
|
||||
var topic_86 = []byte(`<a class="username hide_on_micro user_tag">`)
|
||||
var topic_87 = []byte(`</a>`)
|
||||
var topic_88 = []byte(`<a class="username hide_on_micro level">`)
|
||||
var topic_89 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
|
||||
var topic_90 = []byte(`
|
||||
var topic_86 = []byte(`<a class="username hide_on_micro like_count">`)
|
||||
var topic_87 = []byte(`</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>`)
|
||||
var topic_88 = []byte(`<a class="username hide_on_micro user_tag">`)
|
||||
var topic_89 = []byte(`</a>`)
|
||||
var topic_90 = []byte(`<a class="username hide_on_micro level">`)
|
||||
var topic_91 = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
|
||||
var topic_92 = []byte(`
|
||||
|
||||
</span>
|
||||
</article>
|
||||
`)
|
||||
var topic_91 = []byte(`</div>
|
||||
var topic_93 = []byte(`</div>
|
||||
|
||||
`)
|
||||
var topic_92 = []byte(`
|
||||
var topic_94 = []byte(`
|
||||
<div class="rowblock topic_reply_form">
|
||||
<form action="/reply/create/" method="post">
|
||||
<input name="tid" value='`)
|
||||
var topic_93 = []byte(`' type="hidden" />
|
||||
var topic_95 = []byte(`' type="hidden" />
|
||||
<div class="formrow real_first_child">
|
||||
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here" required></textarea></div>
|
||||
</div>
|
||||
@ -240,7 +246,7 @@ var topic_93 = []byte(`' type="hidden" />
|
||||
</form>
|
||||
</div>
|
||||
`)
|
||||
var topic_94 = []byte(`
|
||||
var topic_96 = []byte(`
|
||||
|
||||
</main>
|
||||
|
||||
@ -654,60 +660,111 @@ var topics_0 = []byte(`
|
||||
<main>
|
||||
|
||||
<div class="rowblock rowhead">
|
||||
<div class="rowitem"><h1>Topic List</h1></div>
|
||||
</div>
|
||||
<div id="topic_list" class="rowblock topic_list" aria-label="The main topic list">
|
||||
<div class="rowitem topic_list_title`)
|
||||
var topics_1 = []byte(` has_opt`)
|
||||
var topics_2 = []byte(`"><h1>All Topics</h1></div>
|
||||
`)
|
||||
var topics_1 = []byte(`<div class="rowitem topic_left passive datarow `)
|
||||
var topics_2 = []byte(`topic_sticky`)
|
||||
var topics_3 = []byte(`topic_closed`)
|
||||
var topics_4 = []byte(`" style="`)
|
||||
var topics_5 = []byte(`background-image: url(`)
|
||||
var topics_6 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
|
||||
var topics_7 = []byte(`">
|
||||
var topics_3 = []byte(`
|
||||
<div class="opt create_topic_opt" title="Create Topic"><a class="create_topic_link" href="/topics/create/"></a></div>
|
||||
`)
|
||||
var topics_4 = []byte(`<div class="opt locked_opt" title="You don't have the permissions needed to create a topic"><a></a></div>`)
|
||||
var topics_5 = []byte(`
|
||||
<div style="clear: both;"></div>
|
||||
`)
|
||||
var topics_6 = []byte(`
|
||||
</div>
|
||||
`)
|
||||
var topics_7 = []byte(`
|
||||
<div class="rowblock topic_create_form" style="display: none;">
|
||||
<form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
|
||||
<div class="formrow topic_board_row real_first_child">
|
||||
<div class="formitem"><select form="topic_create_form_form" id="topic_board_input" name="topic-board">
|
||||
`)
|
||||
var topics_8 = []byte(`<option `)
|
||||
var topics_9 = []byte(`selected`)
|
||||
var topics_10 = []byte(` value="`)
|
||||
var topics_11 = []byte(`">`)
|
||||
var topics_12 = []byte(`</option>`)
|
||||
var topics_13 = []byte(`
|
||||
</select></div>
|
||||
</div>
|
||||
<div class="formrow topic_name_row">
|
||||
<div class="formitem">
|
||||
<input form="topic_create_form_form" name="topic-name" placeholder="Topic title" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formrow topic_content_row">
|
||||
<div class="formitem">
|
||||
<textarea form="topic_create_form_form" id="topic_content" name="topic-content" placeholder="Insert post here" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formrow topic_button_row">
|
||||
<div class="formitem">
|
||||
<button form="topic_create_form_form" class="formbutton">Create Topic</button>
|
||||
`)
|
||||
var topics_14 = []byte(`
|
||||
<input name="quick_topic_upload_files" form="topic_create_form_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" />
|
||||
<label for="quick_topic_upload_files" class="formbutton add_file_button">Add File</label>`)
|
||||
var topics_15 = []byte(`
|
||||
<div id="upload_file_dock"></div>
|
||||
<button class="formbutton close_form">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`)
|
||||
var topics_16 = []byte(`
|
||||
<div id="topic_list" class="rowblock topic_list" aria-label="A list containing topics from every forum">
|
||||
`)
|
||||
var topics_17 = []byte(`<div class="rowitem topic_left passive datarow `)
|
||||
var topics_18 = []byte(`topic_sticky`)
|
||||
var topics_19 = []byte(`topic_closed`)
|
||||
var topics_20 = []byte(`" style="`)
|
||||
var topics_21 = []byte(`background-image: url(`)
|
||||
var topics_22 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
|
||||
var topics_23 = []byte(`">
|
||||
<span class="topic_inner_right rowsmall" style="float: right;">
|
||||
<span class="replyCount">`)
|
||||
var topics_8 = []byte(` replies</span><br />
|
||||
var topics_24 = []byte(` replies</span><br />
|
||||
<span class="lastReplyAt">`)
|
||||
var topics_9 = []byte(`</span>
|
||||
var topics_25 = []byte(`</span>
|
||||
</span>
|
||||
<span>
|
||||
<a class="rowtopic" href="`)
|
||||
var topics_10 = []byte(`">`)
|
||||
var topics_11 = []byte(`</a> `)
|
||||
var topics_12 = []byte(`<a class="rowsmall" href="`)
|
||||
var topics_13 = []byte(`">`)
|
||||
var topics_14 = []byte(`</a>`)
|
||||
var topics_15 = []byte(`
|
||||
var topics_26 = []byte(`">`)
|
||||
var topics_27 = []byte(`</a> `)
|
||||
var topics_28 = []byte(`<a class="rowsmall" href="`)
|
||||
var topics_29 = []byte(`">`)
|
||||
var topics_30 = []byte(`</a>`)
|
||||
var topics_31 = []byte(`
|
||||
<br /><a class="rowsmall" href="`)
|
||||
var topics_16 = []byte(`">Starter: `)
|
||||
var topics_17 = []byte(`</a>
|
||||
var topics_32 = []byte(`">Starter: `)
|
||||
var topics_33 = []byte(`</a>
|
||||
`)
|
||||
var topics_18 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | 🔒︎</span>`)
|
||||
var topics_19 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | 📍︎</span>`)
|
||||
var topics_20 = []byte(`
|
||||
var topics_34 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | 🔒︎</span>`)
|
||||
var topics_35 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | 📍︎</span>`)
|
||||
var topics_36 = []byte(`
|
||||
</span>
|
||||
</div>
|
||||
<div class="rowitem topic_right passive datarow `)
|
||||
var topics_21 = []byte(`topic_sticky`)
|
||||
var topics_22 = []byte(`topic_closed`)
|
||||
var topics_23 = []byte(`" style="`)
|
||||
var topics_24 = []byte(`background-image: url(`)
|
||||
var topics_25 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
|
||||
var topics_26 = []byte(`">
|
||||
var topics_37 = []byte(`topic_sticky`)
|
||||
var topics_38 = []byte(`topic_closed`)
|
||||
var topics_39 = []byte(`" style="`)
|
||||
var topics_40 = []byte(`background-image: url(`)
|
||||
var topics_41 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
|
||||
var topics_42 = []byte(`">
|
||||
<span>
|
||||
<a href="`)
|
||||
var topics_27 = []byte(`" class="lastName" style="font-size: 14px;">`)
|
||||
var topics_28 = []byte(`</a><br>
|
||||
var topics_43 = []byte(`" class="lastName" style="font-size: 14px;">`)
|
||||
var topics_44 = []byte(`</a><br>
|
||||
<span class="rowsmall lastReplyAt">Last: `)
|
||||
var topics_29 = []byte(`</span>
|
||||
var topics_45 = []byte(`</span>
|
||||
</span>
|
||||
</div>
|
||||
`)
|
||||
var topics_30 = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
|
||||
var topics_31 = []byte(` <a href="/topics/create/">Start one?</a>`)
|
||||
var topics_32 = []byte(`</div>`)
|
||||
var topics_33 = []byte(`
|
||||
var topics_46 = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
|
||||
var topics_47 = []byte(` <a href="/topics/create/">Start one?</a>`)
|
||||
var topics_48 = []byte(`</div>`)
|
||||
var topics_49 = []byte(`
|
||||
</div>
|
||||
|
||||
</main>
|
||||
@ -733,7 +790,7 @@ var forum_11 = []byte(`</h1>
|
||||
</div>
|
||||
`)
|
||||
var forum_12 = []byte(`
|
||||
<div class="opt create_topic_opt" title="Create Topic"><a href="/topics/create/`)
|
||||
<div class="opt create_topic_opt" title="Create Topic"><a class="create_topic_link" href="/topics/create/`)
|
||||
var forum_13 = []byte(`"></a></div>
|
||||
`)
|
||||
var forum_14 = []byte(`<div class="opt locked_opt" title="You don't have the permissions needed to create a topic"><a></a></div>`)
|
||||
@ -742,52 +799,83 @@ var forum_15 = []byte(`
|
||||
`)
|
||||
var forum_16 = []byte(`
|
||||
</div>
|
||||
`)
|
||||
var forum_17 = []byte(`
|
||||
<div class="rowblock topic_create_form" style="display: none;">
|
||||
<form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
|
||||
<input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="`)
|
||||
var forum_18 = []byte(`" type="hidden">
|
||||
<div class="formrow topic_name_row real_first_child">
|
||||
<div class="formitem">
|
||||
<input form="topic_create_form_form" name="topic-name" placeholder="Topic title" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formrow topic_content_row">
|
||||
<div class="formitem">
|
||||
<textarea form="topic_create_form_form" id="topic_content" name="topic-content" placeholder="Insert post here" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formrow topic_button_row">
|
||||
<div class="formitem">
|
||||
<button form="topic_create_form_form" name="topic-button" class="formbutton">Create Topic</button>
|
||||
`)
|
||||
var forum_19 = []byte(`
|
||||
<input name="quick_topic_upload_files" form="topic_create_form_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" />
|
||||
<label for="quick_topic_upload_files" class="formbutton add_file_button">Add File</label>`)
|
||||
var forum_20 = []byte(`
|
||||
<div id="upload_file_dock"></div>
|
||||
<button class="formbutton close_form">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`)
|
||||
var forum_21 = []byte(`
|
||||
<div id="forum_topic_list" class="rowblock topic_list">
|
||||
`)
|
||||
var forum_17 = []byte(`<div class="rowitem topic_left passive datarow `)
|
||||
var forum_18 = []byte(`topic_sticky`)
|
||||
var forum_19 = []byte(`topic_closed`)
|
||||
var forum_20 = []byte(`" style="`)
|
||||
var forum_21 = []byte(`background-image: url(`)
|
||||
var forum_22 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
|
||||
var forum_23 = []byte(`">
|
||||
var forum_22 = []byte(`<div class="rowitem topic_left passive datarow `)
|
||||
var forum_23 = []byte(`topic_sticky`)
|
||||
var forum_24 = []byte(`topic_closed`)
|
||||
var forum_25 = []byte(`" style="`)
|
||||
var forum_26 = []byte(`background-image: url(`)
|
||||
var forum_27 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
|
||||
var forum_28 = []byte(`">
|
||||
<span class="topic_inner_right rowsmall" style="float: right;">
|
||||
<span class="replyCount">`)
|
||||
var forum_24 = []byte(` replies</span><br />
|
||||
var forum_29 = []byte(` replies</span><br />
|
||||
<span class="lastReplyAt">`)
|
||||
var forum_25 = []byte(`</span>
|
||||
var forum_30 = []byte(`</span>
|
||||
</span>
|
||||
<span>
|
||||
<a class="rowtopic" href="`)
|
||||
var forum_26 = []byte(`">`)
|
||||
var forum_27 = []byte(`</a>
|
||||
var forum_31 = []byte(`">`)
|
||||
var forum_32 = []byte(`</a>
|
||||
<br /><a class="rowsmall" href="`)
|
||||
var forum_28 = []byte(`">Starter: `)
|
||||
var forum_29 = []byte(`</a>
|
||||
var forum_33 = []byte(`">Starter: `)
|
||||
var forum_34 = []byte(`</a>
|
||||
`)
|
||||
var forum_30 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | 🔒︎</span>`)
|
||||
var forum_31 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | 📍︎</span>`)
|
||||
var forum_32 = []byte(`
|
||||
var forum_35 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | 🔒︎</span>`)
|
||||
var forum_36 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | 📍︎</span>`)
|
||||
var forum_37 = []byte(`
|
||||
</span>
|
||||
</div>
|
||||
<div class="rowitem topic_right passive datarow" style="`)
|
||||
var forum_33 = []byte(`background-image: url(`)
|
||||
var forum_34 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
|
||||
var forum_35 = []byte(`">
|
||||
var forum_38 = []byte(`background-image: url(`)
|
||||
var forum_39 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`)
|
||||
var forum_40 = []byte(`">
|
||||
<span>
|
||||
<a href="`)
|
||||
var forum_36 = []byte(`" class="lastName" style="font-size: 14px;">`)
|
||||
var forum_37 = []byte(`</a><br>
|
||||
var forum_41 = []byte(`" class="lastName" style="font-size: 14px;">`)
|
||||
var forum_42 = []byte(`</a><br>
|
||||
<span class="rowsmall lastReplyAt">Last: `)
|
||||
var forum_38 = []byte(`</span>
|
||||
var forum_43 = []byte(`</span>
|
||||
</span>
|
||||
</div>
|
||||
`)
|
||||
var forum_39 = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
|
||||
var forum_40 = []byte(` <a href="/topics/create/`)
|
||||
var forum_41 = []byte(`">Start one?</a>`)
|
||||
var forum_42 = []byte(`</div>`)
|
||||
var forum_43 = []byte(`
|
||||
var forum_44 = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
|
||||
var forum_45 = []byte(` <a href="/topics/create/`)
|
||||
var forum_46 = []byte(`">Start one?</a>`)
|
||||
var forum_47 = []byte(`</div>`)
|
||||
var forum_48 = []byte(`
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
@ -20,33 +20,37 @@ func template_profile(tmpl_profile_vars ProfilePage, w http.ResponseWriter) {
|
||||
w.Write(header_0)
|
||||
w.Write([]byte(tmpl_profile_vars.Title))
|
||||
w.Write(header_1)
|
||||
w.Write([]byte(tmpl_profile_vars.Header.ThemeName))
|
||||
w.Write([]byte(tmpl_profile_vars.Header.Site.Name))
|
||||
w.Write(header_2)
|
||||
w.Write([]byte(tmpl_profile_vars.Header.ThemeName))
|
||||
w.Write(header_3)
|
||||
if len(tmpl_profile_vars.Header.Stylesheets) != 0 {
|
||||
for _, item := range tmpl_profile_vars.Header.Stylesheets {
|
||||
w.Write(header_3)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_4)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_5)
|
||||
}
|
||||
}
|
||||
w.Write(header_6)
|
||||
if len(tmpl_profile_vars.Header.Scripts) != 0 {
|
||||
for _, item := range tmpl_profile_vars.Header.Scripts {
|
||||
w.Write(header_6)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_7)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_8)
|
||||
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
|
||||
w.Write(header_9)
|
||||
if !tmpl_profile_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_10)
|
||||
}
|
||||
}
|
||||
w.Write(header_9)
|
||||
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
|
||||
w.Write(header_10)
|
||||
w.Write([]byte(tmpl_profile_vars.Header.Site.URL))
|
||||
w.Write(header_11)
|
||||
if !tmpl_profile_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_12)
|
||||
}
|
||||
w.Write(header_13)
|
||||
w.Write(menu_0)
|
||||
w.Write(menu_1)
|
||||
w.Write([]byte(tmpl_profile_vars.Header.Site.Name))
|
||||
w.Write([]byte(tmpl_profile_vars.Header.Site.ShortName))
|
||||
w.Write(menu_2)
|
||||
if tmpl_profile_vars.CurrentUser.Loggedin {
|
||||
w.Write(menu_3)
|
||||
@ -58,16 +62,16 @@ w.Write(menu_5)
|
||||
w.Write(menu_6)
|
||||
}
|
||||
w.Write(menu_7)
|
||||
w.Write(header_12)
|
||||
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_13)
|
||||
}
|
||||
w.Write(header_14)
|
||||
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_15)
|
||||
}
|
||||
w.Write(header_16)
|
||||
if len(tmpl_profile_vars.Header.NoticeList) != 0 {
|
||||
for _, item := range tmpl_profile_vars.Header.NoticeList {
|
||||
w.Write(header_15)
|
||||
w.Write(header_17)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_16)
|
||||
w.Write(header_18)
|
||||
}
|
||||
}
|
||||
w.Write(profile_0)
|
||||
|
@ -20,33 +20,37 @@ func template_topic(tmpl_topic_vars TopicPage, w http.ResponseWriter) {
|
||||
w.Write(header_0)
|
||||
w.Write([]byte(tmpl_topic_vars.Title))
|
||||
w.Write(header_1)
|
||||
w.Write([]byte(tmpl_topic_vars.Header.ThemeName))
|
||||
w.Write([]byte(tmpl_topic_vars.Header.Site.Name))
|
||||
w.Write(header_2)
|
||||
w.Write([]byte(tmpl_topic_vars.Header.ThemeName))
|
||||
w.Write(header_3)
|
||||
if len(tmpl_topic_vars.Header.Stylesheets) != 0 {
|
||||
for _, item := range tmpl_topic_vars.Header.Stylesheets {
|
||||
w.Write(header_3)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_4)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_5)
|
||||
}
|
||||
}
|
||||
w.Write(header_6)
|
||||
if len(tmpl_topic_vars.Header.Scripts) != 0 {
|
||||
for _, item := range tmpl_topic_vars.Header.Scripts {
|
||||
w.Write(header_6)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_7)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_8)
|
||||
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
|
||||
w.Write(header_9)
|
||||
if !tmpl_topic_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_10)
|
||||
}
|
||||
}
|
||||
w.Write(header_9)
|
||||
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
|
||||
w.Write(header_10)
|
||||
w.Write([]byte(tmpl_topic_vars.Header.Site.URL))
|
||||
w.Write(header_11)
|
||||
if !tmpl_topic_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_12)
|
||||
}
|
||||
w.Write(header_13)
|
||||
w.Write(menu_0)
|
||||
w.Write(menu_1)
|
||||
w.Write([]byte(tmpl_topic_vars.Header.Site.Name))
|
||||
w.Write([]byte(tmpl_topic_vars.Header.Site.ShortName))
|
||||
w.Write(menu_2)
|
||||
if tmpl_topic_vars.CurrentUser.Loggedin {
|
||||
w.Write(menu_3)
|
||||
@ -58,16 +62,16 @@ w.Write(menu_5)
|
||||
w.Write(menu_6)
|
||||
}
|
||||
w.Write(menu_7)
|
||||
w.Write(header_12)
|
||||
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_13)
|
||||
}
|
||||
w.Write(header_14)
|
||||
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_15)
|
||||
}
|
||||
w.Write(header_16)
|
||||
if len(tmpl_topic_vars.Header.NoticeList) != 0 {
|
||||
for _, item := range tmpl_topic_vars.Header.NoticeList {
|
||||
w.Write(header_15)
|
||||
w.Write(header_17)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_16)
|
||||
w.Write(header_18)
|
||||
}
|
||||
}
|
||||
w.Write(topic_0)
|
||||
@ -117,169 +121,173 @@ if tmpl_topic_vars.Topic.Avatar != "" {
|
||||
w.Write(topic_20)
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.Avatar))
|
||||
w.Write(topic_21)
|
||||
if tmpl_topic_vars.Topic.ContentLines <= 5 {
|
||||
w.Write([]byte(tmpl_topic_vars.Header.ThemeName))
|
||||
w.Write(topic_22)
|
||||
}
|
||||
if tmpl_topic_vars.Topic.ContentLines <= 5 {
|
||||
w.Write(topic_23)
|
||||
}
|
||||
w.Write(topic_24)
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.ContentHTML))
|
||||
w.Write(topic_25)
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.Content))
|
||||
w.Write(topic_26)
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.UserLink))
|
||||
w.Write(topic_27)
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.CreatedByName))
|
||||
w.Write(topic_28)
|
||||
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
|
||||
w.Write(topic_29)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_30)
|
||||
if tmpl_topic_vars.Topic.Liked {
|
||||
w.Write(topic_31)
|
||||
}
|
||||
w.Write(topic_25)
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.ContentHTML))
|
||||
w.Write(topic_26)
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.Content))
|
||||
w.Write(topic_27)
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.UserLink))
|
||||
w.Write(topic_28)
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.CreatedByName))
|
||||
w.Write(topic_29)
|
||||
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
|
||||
w.Write(topic_30)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_31)
|
||||
if tmpl_topic_vars.Topic.Liked {
|
||||
w.Write(topic_32)
|
||||
}
|
||||
if tmpl_topic_vars.CurrentUser.Perms.EditTopic {
|
||||
w.Write(topic_33)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
}
|
||||
if tmpl_topic_vars.CurrentUser.Perms.EditTopic {
|
||||
w.Write(topic_34)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_35)
|
||||
}
|
||||
if tmpl_topic_vars.CurrentUser.Perms.DeleteTopic {
|
||||
w.Write(topic_35)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_36)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_37)
|
||||
}
|
||||
if tmpl_topic_vars.CurrentUser.Perms.CloseTopic {
|
||||
if tmpl_topic_vars.Topic.IsClosed {
|
||||
w.Write(topic_37)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_38)
|
||||
} else {
|
||||
w.Write(topic_39)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_39)
|
||||
} else {
|
||||
w.Write(topic_40)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_41)
|
||||
}
|
||||
}
|
||||
if tmpl_topic_vars.CurrentUser.Perms.PinTopic {
|
||||
if tmpl_topic_vars.Topic.Sticky {
|
||||
w.Write(topic_41)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_42)
|
||||
} else {
|
||||
w.Write(topic_43)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_43)
|
||||
} else {
|
||||
w.Write(topic_44)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_45)
|
||||
}
|
||||
}
|
||||
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
|
||||
w.Write(topic_45)
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.IPAddress))
|
||||
w.Write(topic_46)
|
||||
}
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.IPAddress))
|
||||
w.Write(topic_47)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
}
|
||||
w.Write(topic_48)
|
||||
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_49)
|
||||
if tmpl_topic_vars.Topic.LikeCount > 0 {
|
||||
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
|
||||
w.Write(topic_50)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.LikeCount)))
|
||||
if tmpl_topic_vars.Topic.LikeCount > 0 {
|
||||
w.Write(topic_51)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.LikeCount)))
|
||||
w.Write(topic_52)
|
||||
}
|
||||
if tmpl_topic_vars.Topic.Tag != "" {
|
||||
w.Write(topic_52)
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.Tag))
|
||||
w.Write(topic_53)
|
||||
} else {
|
||||
w.Write([]byte(tmpl_topic_vars.Topic.Tag))
|
||||
w.Write(topic_54)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.Level)))
|
||||
} else {
|
||||
w.Write(topic_55)
|
||||
}
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.Level)))
|
||||
w.Write(topic_56)
|
||||
}
|
||||
w.Write(topic_57)
|
||||
if len(tmpl_topic_vars.ItemList) != 0 {
|
||||
for _, item := range tmpl_topic_vars.ItemList {
|
||||
if item.ActionType != "" {
|
||||
w.Write(topic_57)
|
||||
w.Write([]byte(item.ActionIcon))
|
||||
w.Write(topic_58)
|
||||
w.Write([]byte(item.ActionType))
|
||||
w.Write([]byte(item.ActionIcon))
|
||||
w.Write(topic_59)
|
||||
} else {
|
||||
w.Write([]byte(item.ActionType))
|
||||
w.Write(topic_60)
|
||||
w.Write([]byte(item.ClassName))
|
||||
} else {
|
||||
w.Write(topic_61)
|
||||
if item.Avatar != "" {
|
||||
w.Write([]byte(item.ClassName))
|
||||
w.Write(topic_62)
|
||||
w.Write([]byte(item.Avatar))
|
||||
if item.Avatar != "" {
|
||||
w.Write(topic_63)
|
||||
if item.ContentLines <= 5 {
|
||||
w.Write([]byte(item.Avatar))
|
||||
w.Write(topic_64)
|
||||
}
|
||||
w.Write([]byte(tmpl_topic_vars.Header.ThemeName))
|
||||
w.Write(topic_65)
|
||||
}
|
||||
if item.ContentLines <= 5 {
|
||||
w.Write(topic_66)
|
||||
}
|
||||
w.Write(topic_67)
|
||||
w.Write([]byte(item.ContentHtml))
|
||||
}
|
||||
w.Write(topic_68)
|
||||
w.Write([]byte(item.UserLink))
|
||||
w.Write(topic_69)
|
||||
w.Write([]byte(item.CreatedByName))
|
||||
w.Write([]byte(item.ContentHtml))
|
||||
w.Write(topic_70)
|
||||
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
|
||||
w.Write([]byte(item.UserLink))
|
||||
w.Write(topic_71)
|
||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||
w.Write([]byte(item.CreatedByName))
|
||||
w.Write(topic_72)
|
||||
if item.Liked {
|
||||
if tmpl_topic_vars.CurrentUser.Perms.LikeItem {
|
||||
w.Write(topic_73)
|
||||
}
|
||||
w.Write(topic_74)
|
||||
}
|
||||
if tmpl_topic_vars.CurrentUser.Perms.EditReply {
|
||||
w.Write(topic_75)
|
||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||
w.Write(topic_74)
|
||||
if item.Liked {
|
||||
w.Write(topic_75)
|
||||
}
|
||||
w.Write(topic_76)
|
||||
}
|
||||
if tmpl_topic_vars.CurrentUser.Perms.DeleteReply {
|
||||
if tmpl_topic_vars.CurrentUser.Perms.EditReply {
|
||||
w.Write(topic_77)
|
||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||
w.Write(topic_78)
|
||||
}
|
||||
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
|
||||
if tmpl_topic_vars.CurrentUser.Perms.DeleteReply {
|
||||
w.Write(topic_79)
|
||||
w.Write([]byte(item.IPAddress))
|
||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||
w.Write(topic_80)
|
||||
}
|
||||
if tmpl_topic_vars.CurrentUser.Perms.ViewIPs {
|
||||
w.Write(topic_81)
|
||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||
w.Write([]byte(item.IPAddress))
|
||||
w.Write(topic_82)
|
||||
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
|
||||
}
|
||||
w.Write(topic_83)
|
||||
if item.LikeCount > 0 {
|
||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||
w.Write(topic_84)
|
||||
w.Write([]byte(strconv.Itoa(item.LikeCount)))
|
||||
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
|
||||
w.Write(topic_85)
|
||||
if item.LikeCount > 0 {
|
||||
w.Write(topic_86)
|
||||
w.Write([]byte(strconv.Itoa(item.LikeCount)))
|
||||
w.Write(topic_87)
|
||||
}
|
||||
if item.Tag != "" {
|
||||
w.Write(topic_86)
|
||||
w.Write([]byte(item.Tag))
|
||||
w.Write(topic_87)
|
||||
} else {
|
||||
w.Write(topic_88)
|
||||
w.Write([]byte(strconv.Itoa(item.Level)))
|
||||
w.Write([]byte(item.Tag))
|
||||
w.Write(topic_89)
|
||||
}
|
||||
} else {
|
||||
w.Write(topic_90)
|
||||
}
|
||||
}
|
||||
}
|
||||
w.Write([]byte(strconv.Itoa(item.Level)))
|
||||
w.Write(topic_91)
|
||||
if tmpl_topic_vars.CurrentUser.Perms.CreateReply {
|
||||
w.Write(topic_92)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_93)
|
||||
}
|
||||
w.Write(topic_92)
|
||||
}
|
||||
}
|
||||
}
|
||||
w.Write(topic_93)
|
||||
if tmpl_topic_vars.CurrentUser.Perms.CreateReply {
|
||||
w.Write(topic_94)
|
||||
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
|
||||
w.Write(topic_95)
|
||||
}
|
||||
w.Write(topic_96)
|
||||
w.Write(footer_0)
|
||||
if len(tmpl_topic_vars.Header.Themes) != 0 {
|
||||
for _, item := range tmpl_topic_vars.Header.Themes {
|
||||
|
@ -20,33 +20,37 @@ func template_topic_alt(tmpl_topic_alt_vars TopicPage, w http.ResponseWriter) {
|
||||
w.Write(header_0)
|
||||
w.Write([]byte(tmpl_topic_alt_vars.Title))
|
||||
w.Write(header_1)
|
||||
w.Write([]byte(tmpl_topic_alt_vars.Header.ThemeName))
|
||||
w.Write([]byte(tmpl_topic_alt_vars.Header.Site.Name))
|
||||
w.Write(header_2)
|
||||
w.Write([]byte(tmpl_topic_alt_vars.Header.ThemeName))
|
||||
w.Write(header_3)
|
||||
if len(tmpl_topic_alt_vars.Header.Stylesheets) != 0 {
|
||||
for _, item := range tmpl_topic_alt_vars.Header.Stylesheets {
|
||||
w.Write(header_3)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_4)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_5)
|
||||
}
|
||||
}
|
||||
w.Write(header_6)
|
||||
if len(tmpl_topic_alt_vars.Header.Scripts) != 0 {
|
||||
for _, item := range tmpl_topic_alt_vars.Header.Scripts {
|
||||
w.Write(header_6)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_7)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_8)
|
||||
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
|
||||
w.Write(header_9)
|
||||
if !tmpl_topic_alt_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_10)
|
||||
}
|
||||
}
|
||||
w.Write(header_9)
|
||||
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
|
||||
w.Write(header_10)
|
||||
w.Write([]byte(tmpl_topic_alt_vars.Header.Site.URL))
|
||||
w.Write(header_11)
|
||||
if !tmpl_topic_alt_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_12)
|
||||
}
|
||||
w.Write(header_13)
|
||||
w.Write(menu_0)
|
||||
w.Write(menu_1)
|
||||
w.Write([]byte(tmpl_topic_alt_vars.Header.Site.Name))
|
||||
w.Write([]byte(tmpl_topic_alt_vars.Header.Site.ShortName))
|
||||
w.Write(menu_2)
|
||||
if tmpl_topic_alt_vars.CurrentUser.Loggedin {
|
||||
w.Write(menu_3)
|
||||
@ -58,16 +62,16 @@ w.Write(menu_5)
|
||||
w.Write(menu_6)
|
||||
}
|
||||
w.Write(menu_7)
|
||||
w.Write(header_12)
|
||||
if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_13)
|
||||
}
|
||||
w.Write(header_14)
|
||||
if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_15)
|
||||
}
|
||||
w.Write(header_16)
|
||||
if len(tmpl_topic_alt_vars.Header.NoticeList) != 0 {
|
||||
for _, item := range tmpl_topic_alt_vars.Header.NoticeList {
|
||||
w.Write(header_15)
|
||||
w.Write(header_17)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_16)
|
||||
w.Write(header_18)
|
||||
}
|
||||
}
|
||||
if tmpl_topic_alt_vars.Page > 1 {
|
||||
|
@ -20,33 +20,37 @@ func template_topics(tmpl_topics_vars TopicsPage, w http.ResponseWriter) {
|
||||
w.Write(header_0)
|
||||
w.Write([]byte(tmpl_topics_vars.Title))
|
||||
w.Write(header_1)
|
||||
w.Write([]byte(tmpl_topics_vars.Header.ThemeName))
|
||||
w.Write([]byte(tmpl_topics_vars.Header.Site.Name))
|
||||
w.Write(header_2)
|
||||
w.Write([]byte(tmpl_topics_vars.Header.ThemeName))
|
||||
w.Write(header_3)
|
||||
if len(tmpl_topics_vars.Header.Stylesheets) != 0 {
|
||||
for _, item := range tmpl_topics_vars.Header.Stylesheets {
|
||||
w.Write(header_3)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_4)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_5)
|
||||
}
|
||||
}
|
||||
w.Write(header_6)
|
||||
if len(tmpl_topics_vars.Header.Scripts) != 0 {
|
||||
for _, item := range tmpl_topics_vars.Header.Scripts {
|
||||
w.Write(header_6)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_7)
|
||||
}
|
||||
}
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_8)
|
||||
w.Write([]byte(tmpl_topics_vars.CurrentUser.Session))
|
||||
w.Write(header_9)
|
||||
if !tmpl_topics_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_10)
|
||||
}
|
||||
}
|
||||
w.Write(header_9)
|
||||
w.Write([]byte(tmpl_topics_vars.CurrentUser.Session))
|
||||
w.Write(header_10)
|
||||
w.Write([]byte(tmpl_topics_vars.Header.Site.URL))
|
||||
w.Write(header_11)
|
||||
if !tmpl_topics_vars.CurrentUser.IsSuperMod {
|
||||
w.Write(header_12)
|
||||
}
|
||||
w.Write(header_13)
|
||||
w.Write(menu_0)
|
||||
w.Write(menu_1)
|
||||
w.Write([]byte(tmpl_topics_vars.Header.Site.Name))
|
||||
w.Write([]byte(tmpl_topics_vars.Header.Site.ShortName))
|
||||
w.Write(menu_2)
|
||||
if tmpl_topics_vars.CurrentUser.Loggedin {
|
||||
w.Write(menu_3)
|
||||
@ -58,92 +62,129 @@ w.Write(menu_5)
|
||||
w.Write(menu_6)
|
||||
}
|
||||
w.Write(menu_7)
|
||||
w.Write(header_12)
|
||||
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_13)
|
||||
}
|
||||
w.Write(header_14)
|
||||
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
|
||||
w.Write(header_15)
|
||||
}
|
||||
w.Write(header_16)
|
||||
if len(tmpl_topics_vars.Header.NoticeList) != 0 {
|
||||
for _, item := range tmpl_topics_vars.Header.NoticeList {
|
||||
w.Write(header_15)
|
||||
w.Write(header_17)
|
||||
w.Write([]byte(item))
|
||||
w.Write(header_16)
|
||||
w.Write(header_18)
|
||||
}
|
||||
}
|
||||
w.Write(topics_0)
|
||||
if len(tmpl_topics_vars.ItemList) != 0 {
|
||||
for _, item := range tmpl_topics_vars.ItemList {
|
||||
if tmpl_topics_vars.CurrentUser.ID != 0 {
|
||||
w.Write(topics_1)
|
||||
if item.Sticky {
|
||||
}
|
||||
w.Write(topics_2)
|
||||
} else {
|
||||
if item.IsClosed {
|
||||
if tmpl_topics_vars.CurrentUser.ID != 0 {
|
||||
if len(tmpl_topics_vars.ForumList) != 0 {
|
||||
w.Write(topics_3)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
w.Write(topics_4)
|
||||
if item.Creator.Avatar != "" {
|
||||
w.Write(topics_5)
|
||||
w.Write([]byte(item.Creator.Avatar))
|
||||
w.Write(topics_6)
|
||||
}
|
||||
w.Write(topics_5)
|
||||
}
|
||||
w.Write(topics_6)
|
||||
if tmpl_topics_vars.CurrentUser.ID != 0 {
|
||||
if len(tmpl_topics_vars.ForumList) != 0 {
|
||||
w.Write(topics_7)
|
||||
w.Write([]byte(strconv.Itoa(item.PostCount)))
|
||||
if len(tmpl_topics_vars.ForumList) != 0 {
|
||||
for _, item := range tmpl_topics_vars.ForumList {
|
||||
w.Write(topics_8)
|
||||
w.Write([]byte(item.LastReplyAt))
|
||||
if item.ID == tmpl_topics_vars.DefaultForum {
|
||||
w.Write(topics_9)
|
||||
w.Write([]byte(item.Link))
|
||||
}
|
||||
w.Write(topics_10)
|
||||
w.Write([]byte(item.Title))
|
||||
w.Write([]byte(strconv.Itoa(item.ID)))
|
||||
w.Write(topics_11)
|
||||
if item.ForumName != "" {
|
||||
w.Write([]byte(item.Name))
|
||||
w.Write(topics_12)
|
||||
w.Write([]byte(item.ForumLink))
|
||||
}
|
||||
}
|
||||
w.Write(topics_13)
|
||||
w.Write([]byte(item.ForumName))
|
||||
if tmpl_topics_vars.CurrentUser.Perms.UploadFiles {
|
||||
w.Write(topics_14)
|
||||
}
|
||||
w.Write(topics_15)
|
||||
w.Write([]byte(item.Creator.Link))
|
||||
w.Write(topics_16)
|
||||
w.Write([]byte(item.Creator.Name))
|
||||
w.Write(topics_17)
|
||||
if item.IsClosed {
|
||||
w.Write(topics_18)
|
||||
}
|
||||
}
|
||||
w.Write(topics_16)
|
||||
if len(tmpl_topics_vars.TopicList) != 0 {
|
||||
for _, item := range tmpl_topics_vars.TopicList {
|
||||
w.Write(topics_17)
|
||||
if item.Sticky {
|
||||
w.Write(topics_18)
|
||||
} else {
|
||||
if item.IsClosed {
|
||||
w.Write(topics_19)
|
||||
}
|
||||
}
|
||||
w.Write(topics_20)
|
||||
if item.Sticky {
|
||||
if item.Creator.Avatar != "" {
|
||||
w.Write(topics_21)
|
||||
} else {
|
||||
if item.IsClosed {
|
||||
w.Write([]byte(item.Creator.Avatar))
|
||||
w.Write(topics_22)
|
||||
}
|
||||
}
|
||||
w.Write(topics_23)
|
||||
if item.LastUser.Avatar != "" {
|
||||
w.Write([]byte(strconv.Itoa(item.PostCount)))
|
||||
w.Write(topics_24)
|
||||
w.Write([]byte(item.LastUser.Avatar))
|
||||
w.Write(topics_25)
|
||||
}
|
||||
w.Write(topics_26)
|
||||
w.Write([]byte(item.LastUser.Link))
|
||||
w.Write(topics_27)
|
||||
w.Write([]byte(item.LastUser.Name))
|
||||
w.Write(topics_28)
|
||||
w.Write([]byte(item.LastReplyAt))
|
||||
w.Write(topics_25)
|
||||
w.Write([]byte(item.Link))
|
||||
w.Write(topics_26)
|
||||
w.Write([]byte(item.Title))
|
||||
w.Write(topics_27)
|
||||
if item.ForumName != "" {
|
||||
w.Write(topics_28)
|
||||
w.Write([]byte(item.ForumLink))
|
||||
w.Write(topics_29)
|
||||
w.Write([]byte(item.ForumName))
|
||||
w.Write(topics_30)
|
||||
}
|
||||
w.Write(topics_31)
|
||||
w.Write([]byte(item.Creator.Link))
|
||||
w.Write(topics_32)
|
||||
w.Write([]byte(item.Creator.Name))
|
||||
w.Write(topics_33)
|
||||
if item.IsClosed {
|
||||
w.Write(topics_34)
|
||||
}
|
||||
if item.Sticky {
|
||||
w.Write(topics_35)
|
||||
}
|
||||
w.Write(topics_36)
|
||||
if item.Sticky {
|
||||
w.Write(topics_37)
|
||||
} else {
|
||||
if item.IsClosed {
|
||||
w.Write(topics_38)
|
||||
}
|
||||
}
|
||||
w.Write(topics_39)
|
||||
if item.LastUser.Avatar != "" {
|
||||
w.Write(topics_40)
|
||||
w.Write([]byte(item.LastUser.Avatar))
|
||||
w.Write(topics_41)
|
||||
}
|
||||
w.Write(topics_42)
|
||||
w.Write([]byte(item.LastUser.Link))
|
||||
w.Write(topics_43)
|
||||
w.Write([]byte(item.LastUser.Name))
|
||||
w.Write(topics_44)
|
||||
w.Write([]byte(item.LastReplyAt))
|
||||
w.Write(topics_45)
|
||||
}
|
||||
} else {
|
||||
w.Write(topics_30)
|
||||
w.Write(topics_46)
|
||||
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
|
||||
w.Write(topics_31)
|
||||
w.Write(topics_47)
|
||||
}
|
||||
w.Write(topics_32)
|
||||
w.Write(topics_48)
|
||||
}
|
||||
w.Write(topics_33)
|
||||
w.Write(topics_49)
|
||||
w.Write(footer_0)
|
||||
if len(tmpl_topics_vars.Header.Themes) != 0 {
|
||||
for _, item := range tmpl_topics_vars.Header.Themes {
|
||||
|
@ -876,14 +876,15 @@ func (c *CTemplateSet) compileBoolsub(varname string, varholder string, template
|
||||
fmt.Println("in compileBoolsub")
|
||||
}
|
||||
out, val := c.compileIfVarsub(varname, varholder, templateName, val)
|
||||
// TODO: What if it's a pointer or an interface? I *think* we've got pointers handled somewhere, but not interfaces which we don't know the types of at compile time
|
||||
switch val.Kind() {
|
||||
case reflect.Int:
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
|
||||
out += " > 0"
|
||||
case reflect.Bool: // Do nothing
|
||||
case reflect.String:
|
||||
out += " != \"\""
|
||||
case reflect.Int64:
|
||||
out += " > 0"
|
||||
case reflect.Slice, reflect.Map:
|
||||
out = "len(" + out + ") != 0"
|
||||
default:
|
||||
fmt.Println("Variable Name:", varname)
|
||||
fmt.Println("Variable Holder:", varholder)
|
||||
|
@ -4,25 +4,29 @@
|
||||
<div class="rowitem"><h1>Create Topic</h1></div>
|
||||
</div>
|
||||
<div class="rowblock">
|
||||
<form action="/topic/create/submit/" method="post">
|
||||
<div class="formrow real_first_child">
|
||||
<div class="formitem formlabel"><a>Board</a></div>
|
||||
<div class="formitem"><select name="topic-board">
|
||||
{{range .ItemList}}<option {{if eq .ID $.FID}}selected{{end}} value="{{.ID}}">{{.Name}}</option>{{end}}
|
||||
</select></div>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>Topic Name</a></div>
|
||||
<div class="formitem"><input name="topic-name" type="text" placeholder="Topic Name" required /></div>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>Content</a></div>
|
||||
<div class="formitem"><textarea class="large" name="topic-content" placeholder="Insert content here" required></textarea></div>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<div class="formitem"><button name="topic-button" class="formbutton form_middle_button">Create Topic</button></div>
|
||||
</div>
|
||||
</form>
|
||||
<form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
|
||||
<div class="formrow real_first_child">
|
||||
<div class="formitem formlabel"><a>Board</a></div>
|
||||
<div class="formitem"><select form="topic_create_form_form" id="topic_board_input" name="topic-board">
|
||||
{{range .ItemList}}<option {{if eq .ID $.FID}}selected{{end}} value="{{.ID}}">{{.Name}}</option>{{end}}
|
||||
</select></div>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>Topic Name</a></div>
|
||||
<div class="formitem"><input form="topic_create_form_form" name="topic-name" type="text" placeholder="Topic Name" required /></div>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>Content</a></div>
|
||||
<div class="formitem"><textarea form="topic_create_form_form" class="large" id="topic_content" name="topic-content" placeholder="Insert content here" required></textarea></div>
|
||||
</div>
|
||||
<div class="formrow">
|
||||
<button form="topic_create_form_form" name="topic-button" class="formbutton">Create Topic</button>
|
||||
{{if .CurrentUser.Perms.UploadFiles}}
|
||||
<input name="quick_topic_upload_files" form="topic_create_form_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" />
|
||||
<label for="quick_topic_upload_files" class="formbutton add_file_button">Add File</label>{{end}}
|
||||
<div id="upload_file_dock"></div>
|
||||
<button class="formbutton close_form">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{{template "footer.html" . }}
|
||||
|
@ -11,11 +11,37 @@
|
||||
</div>
|
||||
{{if ne .CurrentUser.ID 0}}
|
||||
{{if .CurrentUser.Perms.CreateTopic}}
|
||||
<div class="opt create_topic_opt" title="Create Topic"><a href="/topics/create/{{.Forum.ID}}"></a></div>
|
||||
<div class="opt create_topic_opt" title="Create Topic"><a class="create_topic_link" href="/topics/create/{{.Forum.ID}}"></a></div>
|
||||
{{else}}<div class="opt locked_opt" title="You don't have the permissions needed to create a topic"><a></a></div>{{end}}
|
||||
<div style="clear: both;"></div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .CurrentUser.Perms.CreateTopic}}
|
||||
<div class="rowblock topic_create_form" style="display: none;">
|
||||
<form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
|
||||
<input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="{{.Forum.ID}}" type="hidden">
|
||||
<div class="formrow topic_name_row real_first_child">
|
||||
<div class="formitem">
|
||||
<input form="topic_create_form_form" name="topic-name" placeholder="Topic title" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formrow topic_content_row">
|
||||
<div class="formitem">
|
||||
<textarea form="topic_create_form_form" id="topic_content" name="topic-content" placeholder="Insert post here" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formrow topic_button_row">
|
||||
<div class="formitem">
|
||||
<button form="topic_create_form_form" name="topic-button" class="formbutton">Create Topic</button>
|
||||
{{if .CurrentUser.Perms.UploadFiles}}
|
||||
<input name="quick_topic_upload_files" form="topic_create_form_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" />
|
||||
<label for="quick_topic_upload_files" class="formbutton add_file_button">Add File</label>{{end}}
|
||||
<div id="upload_file_dock"></div>
|
||||
<button class="formbutton close_form">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div id="forum_topic_list" class="rowblock topic_list">
|
||||
{{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
|
||||
<span class="topic_inner_right rowsmall" style="float: right;">
|
||||
|
@ -1,7 +1,7 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{.Title}}</title>
|
||||
<title>{{.Title}} | {{.Header.Site.Name}}</title>
|
||||
<link href="/static/{{.Header.ThemeName}}/main.css" rel="stylesheet" type="text/css">
|
||||
{{range .Header.Stylesheets}}
|
||||
<link href="/static/{{.}}" rel="stylesheet" type="text/css">
|
||||
@ -10,7 +10,10 @@
|
||||
{{range .Header.Scripts}}
|
||||
<script type="text/javascript" src="/static/{{.}}"></script>
|
||||
{{end}}
|
||||
<script type="text/javascript">var session = "{{.CurrentUser.Session}}";</script>
|
||||
<script type="text/javascript">
|
||||
var session = "{{.CurrentUser.Session}}";
|
||||
var siteURL = "{{.Header.Site.URL}}";
|
||||
</script>
|
||||
<script type="text/javascript" src="/static/global.js"></script>
|
||||
<meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" />
|
||||
</head>
|
||||
|
@ -2,10 +2,9 @@
|
||||
<div class="move_left">
|
||||
<div class="move_right">
|
||||
<ul>{{/** Add a menu manager **/}}
|
||||
<li class="menu_left menu_overview"><a href="/" rel="home">{{.Header.Site.Name}}</a></li>
|
||||
<li class="menu_left menu_overview"><a href="/" rel="home">{{.Header.Site.ShortName}}</a></li>
|
||||
<li class="menu_left menu_forums"><a href="/forums/">Forums</a></li>
|
||||
<li class="menu_left menu_topics"><a href="/">Topics</a></li>
|
||||
<li class="menu_left menu_create_topic"><a href="/topics/create/">Create Topic</a></li>
|
||||
<li id="general_alerts" class="menu_right menu_alerts">
|
||||
<div class="alert_bell"></div>
|
||||
<div class="alert_counter"></div>
|
||||
|
@ -22,7 +22,7 @@
|
||||
</div>
|
||||
|
||||
<article class="rowblock post_container top_post">
|
||||
<div class="rowitem passive editable_parent post_item {{.Topic.ClassName}}" style="{{if .Topic.Avatar}}background-image:url({{.Topic.Avatar}}), url(/static/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}">
|
||||
<div class="rowitem passive editable_parent post_item {{.Topic.ClassName}}" style="{{if .Topic.Avatar}}background-image:url({{.Topic.Avatar}}), url(/static/{{.Header.ThemeName}}/post-avatar-bg.jpg);background-position: 0px {{if le .Topic.ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}">
|
||||
<p class="hide_on_edit topic_content user_content" style="margin:0;padding:0;">{{.Topic.ContentHTML}}</p>
|
||||
<textarea name="topic_content" class="show_on_edit topic_content_input">{{.Topic.Content}}</textarea>
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
<span>{{.ActionType}}</span>
|
||||
</article>
|
||||
{{else}}
|
||||
<article class="rowitem passive deletable_block editable_parent post_item {{.ClassName}}" style="{{if .Avatar}}background-image:url({{.Avatar}}), url(/static/post-avatar-bg.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}">
|
||||
<article class="rowitem passive deletable_block editable_parent post_item {{.ClassName}}" style="{{if .Avatar}}background-image:url({{.Avatar}}), url(/static/{{$.Header.ThemeName}}/post-avatar-bg.jpg);background-position: 0px {{if le .ContentLines 5}}-1{{end}}0px;background-repeat:no-repeat, repeat-y;{{end}}">
|
||||
{{/** TODO: We might end up with <br>s in the inline editor, fix this **/}}
|
||||
<p class="editable_block user_content" style="margin:0;padding:0;">{{.ContentHtml}}</p>
|
||||
|
||||
|
@ -2,10 +2,48 @@
|
||||
<main>
|
||||
|
||||
<div class="rowblock rowhead">
|
||||
<div class="rowitem"><h1>Topic List</h1></div>
|
||||
<div class="rowitem topic_list_title{{if ne .CurrentUser.ID 0}} has_opt{{end}}"><h1>All Topics</h1></div>
|
||||
{{if ne .CurrentUser.ID 0}}
|
||||
{{if .ForumList}}
|
||||
<div class="opt create_topic_opt" title="Create Topic"><a class="create_topic_link" href="/topics/create/"></a></div>
|
||||
{{else}}<div class="opt locked_opt" title="You don't have the permissions needed to create a topic"><a></a></div>{{end}}
|
||||
<div style="clear: both;"></div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div id="topic_list" class="rowblock topic_list" aria-label="The main topic list">
|
||||
{{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
|
||||
{{if ne .CurrentUser.ID 0}}
|
||||
{{if .ForumList}}
|
||||
<div class="rowblock topic_create_form" style="display: none;">
|
||||
<form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
|
||||
<div class="formrow topic_board_row real_first_child">
|
||||
<div class="formitem"><select form="topic_create_form_form" id="topic_board_input" name="topic-board">
|
||||
{{range .ForumList}}<option {{if eq .ID $.DefaultForum}}selected{{end}} value="{{.ID}}">{{.Name}}</option>{{end}}
|
||||
</select></div>
|
||||
</div>
|
||||
<div class="formrow topic_name_row">
|
||||
<div class="formitem">
|
||||
<input form="topic_create_form_form" name="topic-name" placeholder="Topic title" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formrow topic_content_row">
|
||||
<div class="formitem">
|
||||
<textarea form="topic_create_form_form" id="topic_content" name="topic-content" placeholder="Insert post here" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formrow topic_button_row">
|
||||
<div class="formitem">
|
||||
<button form="topic_create_form_form" class="formbutton">Create Topic</button>
|
||||
{{if .CurrentUser.Perms.UploadFiles}}
|
||||
<input name="quick_topic_upload_files" form="topic_create_form_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" />
|
||||
<label for="quick_topic_upload_files" class="formbutton add_file_button">Add File</label>{{end}}
|
||||
<div id="upload_file_dock"></div>
|
||||
<button class="formbutton close_form">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<div id="topic_list" class="rowblock topic_list" aria-label="A list containing topics from every forum">
|
||||
{{range .TopicList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}">
|
||||
<span class="topic_inner_right rowsmall" style="float: right;">
|
||||
<span class="replyCount">{{.PostCount}} replies</span><br />
|
||||
<span class="lastReplyAt">{{.LastReplyAt}}</span>
|
||||
|
13
themes.go
@ -139,6 +139,19 @@ func initThemes() error {
|
||||
|
||||
theme.Active = false // Set this to false, just in case someone explicitly overrode this value in the JSON file
|
||||
|
||||
// TODO: Let the theme specify where it's resources are via the JSON file?
|
||||
// TODO: Let the theme inherit CSS from another theme?
|
||||
// ? - This might not be too helpful, as it only searches for /public/ and not if /public/ is empty. Still, it might help some people with a slightly less cryptic error
|
||||
_, err = os.Stat("./themes/" + theme.Name + "/public/")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.New("We couldn't find this theme's resources. E.g. the /public/ folder.")
|
||||
} else {
|
||||
log.Print("We weren't able to access this theme's resources due to a permissions issue or some other problem")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if theme.FullImage != "" {
|
||||
if dev.DebugMode {
|
||||
log.Print("Adding theme image")
|
||||
|
@ -796,7 +796,7 @@ blockquote p {
|
||||
}
|
||||
|
||||
@media(max-width: 620px) {
|
||||
.menu_create_topic, .menu_overview, .hide_on_mobile { display: none; }
|
||||
.menu_overview, .hide_on_mobile { display: none; }
|
||||
}
|
||||
|
||||
/* This one is specifically for small mobiles.. */
|
||||
|
@ -807,7 +807,7 @@ blockquote p {
|
||||
}
|
||||
|
||||
@media(max-width: 620px) {
|
||||
.menu_create_topic, .menu_overview, .hide_on_mobile { display: none; }
|
||||
.menu_overview, .hide_on_mobile { display: none; }
|
||||
}
|
||||
|
||||
/* This one is specifically for small mobiles.. */
|
||||
|
0
themes/cosora/public/panel.css
Normal file
9
themes/cosora/theme.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Name": "cosora",
|
||||
"FriendlyName": "Cosora",
|
||||
"Version": "0.0.1",
|
||||
"Creator": "Azareal",
|
||||
"URL": "github.com/Azareal/Gosora",
|
||||
"HideFromThemes":true,
|
||||
"Tag": "WIP"
|
||||
}
|
@ -1,10 +1,21 @@
|
||||
/* Patch for Edge, until they fix emojis in arial x.x */
|
||||
@supports (-ms-ime-align:auto) { .user_content { font-family: Segoe UI Emoji, arial; } }
|
||||
|
||||
:root {
|
||||
--main-block-color: rgb(61,61,61);
|
||||
--main-text-color: white;
|
||||
--dim-text-color: rgb(205,205,205);
|
||||
--main-background-color: #222222;
|
||||
--inner-background-color: #333333;
|
||||
--input-background-color: #444444;
|
||||
--input-border-color: #555555;
|
||||
--input-text-color: #999999;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: arial;
|
||||
color: white;
|
||||
background-color: #222222;
|
||||
color: var(--main-text-color);
|
||||
background-color: var(--main-background-color);
|
||||
margin: 0;
|
||||
}
|
||||
p::selection, span::selection, a::selection {
|
||||
@ -17,15 +28,15 @@ p::selection, span::selection, a::selection {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 70%;
|
||||
background-color: #333333;
|
||||
background-color: var(--inner-background-color);
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
background-color: rgb(61,61,61);
|
||||
border-bottom: 1px solid #222222;
|
||||
background-color: var(--main-block-color);
|
||||
border-bottom: 1px solid var(--main-background-color);
|
||||
padding-left: 15%;
|
||||
padding-right: 15%;
|
||||
margin: 0;
|
||||
@ -89,7 +100,6 @@ li {
|
||||
.menu_alerts .alertList {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selectedAlert .alertList {
|
||||
display: block;
|
||||
position: absolute;
|
||||
@ -99,7 +109,7 @@ li {
|
||||
z-index: 50;
|
||||
right: 15%;
|
||||
font-size: 13px;
|
||||
background-color: #333333;
|
||||
background-color: var(--inner-background-color);
|
||||
}
|
||||
|
||||
.alertItem {
|
||||
@ -109,19 +119,19 @@ li {
|
||||
height: 40px;
|
||||
background-size: 48px;
|
||||
background-repeat: no-repeat;
|
||||
background-color: rgb(61,61,61);
|
||||
background-color: var(--main-block-color);
|
||||
padding-left: 56px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding-bottom: 12px;
|
||||
background-color: rgb(61,61,61);
|
||||
background-color: var(--main-block-color);
|
||||
padding: 12px;
|
||||
display: block;
|
||||
}
|
||||
@ -147,7 +157,7 @@ a {
|
||||
|
||||
.rowitem, .formitem {
|
||||
padding-bottom: 12px;
|
||||
background-color: rgb(61,61,61);
|
||||
background-color: var(--main-block-color);
|
||||
margin-top: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
@ -179,7 +189,7 @@ a {
|
||||
|
||||
.colline {
|
||||
font-size: 14px;
|
||||
background-color: rgb(61,61,61);
|
||||
background-color: var(--main-block-color);
|
||||
margin-top: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
@ -218,7 +228,7 @@ a {
|
||||
|
||||
.user_tag {
|
||||
float: right;
|
||||
color: rgb(205,205,205);
|
||||
color: var(--dim-text-color);
|
||||
}
|
||||
|
||||
.real_username {
|
||||
@ -234,7 +244,7 @@ a {
|
||||
.mod_button button {
|
||||
border: none;
|
||||
background: none;
|
||||
color: white;
|
||||
color: var(--main-text-color);
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
@ -292,7 +302,7 @@ a {
|
||||
}
|
||||
|
||||
.level_label, .level {
|
||||
color: rgb(205,205,205);
|
||||
color: var(--dim-text-color);
|
||||
float: right;
|
||||
}
|
||||
.level {
|
||||
@ -310,17 +320,15 @@ a {
|
||||
}
|
||||
|
||||
textarea {
|
||||
background-color: #444444;
|
||||
border-color: #555555;
|
||||
color: #999999;
|
||||
background-color: var(--input-background-color);
|
||||
border-color: var(--input-border-color);
|
||||
color: var(--input-text-color);
|
||||
width: calc(100% - 15px);
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
textarea:focus, input:focus, select:focus {
|
||||
textarea:focus, input:focus, select:focus, button:focus {
|
||||
outline-color: rgb(95,95,95);
|
||||
}
|
||||
|
||||
textarea.large {
|
||||
min-height: 120px;
|
||||
margin-top: 1px;
|
||||
@ -328,15 +336,10 @@ textarea.large {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.topic_reply_form textarea {
|
||||
width: calc(100% - 5px);
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.formitem button, .formbutton {
|
||||
background-color: #444444;
|
||||
border: 1px solid #555555;
|
||||
color: #999999;
|
||||
background-color: var(--input-background-color);
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--input-text-color);
|
||||
padding: 7px;
|
||||
padding-bottom: 6px;
|
||||
font-size: 13px;
|
||||
@ -402,7 +405,7 @@ textarea.large {
|
||||
flex-direction: row;
|
||||
}
|
||||
.pageitem {
|
||||
background-color: rgb(61,61,61);
|
||||
background-color: var(--main-block-color);
|
||||
padding: 10px;
|
||||
margin-right: 4px;
|
||||
font-size: 13px;
|
||||
@ -423,9 +426,9 @@ textarea.large {
|
||||
}
|
||||
|
||||
.formitem input {
|
||||
background-color: #444444;
|
||||
border: 1px solid #555555;
|
||||
color: #999999;
|
||||
background-color: var(--input-background-color);
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--input-text-color);
|
||||
padding-bottom: 6px;
|
||||
font-size: 13px;
|
||||
|
||||
@ -434,9 +437,9 @@ textarea.large {
|
||||
}
|
||||
|
||||
.formitem select {
|
||||
background-color: #444444;
|
||||
border: 1px solid #555555;
|
||||
color: #999999;
|
||||
background-color: var(--input-background-color);
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--input-text-color);
|
||||
font-size: 13px;
|
||||
padding: 4px;
|
||||
}
|
||||
@ -458,8 +461,50 @@ input, select, textarea {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.topic_create_form .topic_board_row .formitem, .topic_create_form .topic_name_row .formitem {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.topic_create_form input, .topic_create_form select {
|
||||
padding: 7px;
|
||||
font-family: monospace;
|
||||
}
|
||||
.topic_create_form select {
|
||||
padding: 6px;
|
||||
}
|
||||
.topic_create_form input {
|
||||
width: calc(100% - 14px);
|
||||
}
|
||||
.topic_create_form textarea, .topic_reply_form textarea {
|
||||
width: calc(100% - 5px);
|
||||
min-height: 80px;
|
||||
}
|
||||
.topic_create_form textarea {
|
||||
padding: 7px;
|
||||
width: calc(100% - 14px);
|
||||
}
|
||||
|
||||
.topic_button_row .formitem {
|
||||
display: flex;
|
||||
}
|
||||
.topic_create_form .add_file_button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.topic_create_form .close_form {
|
||||
margin-left: auto;
|
||||
}
|
||||
.topic_create_form .upload_file_dock {
|
||||
display: flex;
|
||||
}
|
||||
.topic_create_form .uploadItem {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
background-size: 25px 30px;
|
||||
background-repeat: no-repeat;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: rgb(61,61,61);
|
||||
background-color: var(--main-block-color);
|
||||
margin-top: 5px;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
@ -469,9 +514,9 @@ input, select, textarea {
|
||||
height: 25px;
|
||||
}
|
||||
.footer select {
|
||||
background-color: #444444;
|
||||
border: 1px solid #555555;
|
||||
color: #999999;
|
||||
background-color: var(--input-background-color);
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--input-text-color);
|
||||
font-size: 13px;
|
||||
padding: 4px;
|
||||
}
|
||||
@ -505,7 +550,7 @@ input, select, textarea {
|
||||
height: 30.4px;
|
||||
padding-left: 5px;
|
||||
width: 100%;
|
||||
background-color: rgb(61,61,61);
|
||||
background-color: var(--main-block-color);
|
||||
padding-top: 11px;
|
||||
}
|
||||
.opt a {
|
||||
@ -545,9 +590,9 @@ input, select, textarea {
|
||||
.topic_name_input {
|
||||
width: 100%;
|
||||
margin-right: 10px;
|
||||
background-color: #444444;
|
||||
border: 1px solid #555555;
|
||||
color: #999999;
|
||||
background-color: var(--input-background-color);
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--input-text-color);
|
||||
padding-bottom: 6px;
|
||||
font-size: 13px;
|
||||
padding: 5px;
|
||||
@ -561,6 +606,13 @@ input, select, textarea {
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.postImage {
|
||||
max-width: 100%;
|
||||
max-height: 200px;/*300px;*/
|
||||
background-color: rgb(71,71,71);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Profiles */
|
||||
#profile_left_lane {
|
||||
width: 220px;
|
||||
@ -608,9 +660,9 @@ input, select, textarea {
|
||||
}
|
||||
|
||||
.ip_search_block input {
|
||||
background-color: #444444;
|
||||
border: 1px solid #555555;
|
||||
color: #999999;
|
||||
background-color: var(--input-background-color);
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--input-text-color);
|
||||
margin-top: -3px;
|
||||
margin-bottom: -3px;
|
||||
padding: 4px;
|
||||
@ -640,7 +692,7 @@ input, select, textarea {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
font-size: 13px;
|
||||
background-color: rgb(61,61,61);
|
||||
background-color: var(--main-block-color);
|
||||
}
|
||||
|
||||
#panel_dashboard_right .colstack_head .rowitem {
|
||||
@ -808,7 +860,7 @@ input, select, textarea {
|
||||
}
|
||||
|
||||
@media(max-width: 470px) {
|
||||
.menu_create_topic, .like_count_label, .like_count {
|
||||
.like_count_label, .like_count {
|
||||
display: none;
|
||||
}
|
||||
.post_item {
|
||||
|
@ -4,6 +4,5 @@
|
||||
"Version": "0.0.1",
|
||||
"Creator": "Azareal",
|
||||
"FullImage": "shadow.png",
|
||||
"URL": "github.com/Azareal/Gosora",
|
||||
"Tag": "WIP"
|
||||
"URL": "github.com/Azareal/Gosora"
|
||||
}
|
||||
|
@ -4,6 +4,11 @@
|
||||
-webkit-box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* TODO: Run a find and replacer in Gosora to support browsers without CSS Variable support */
|
||||
:root {
|
||||
--main-border-color: hsl(0,0%,80%);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: arial;
|
||||
padding-bottom: 8px;
|
||||
@ -23,11 +28,11 @@ ul {
|
||||
padding-right: 0px;
|
||||
height: 36px;
|
||||
list-style-type: none;
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
border: 1px solid var(--main-border-color);
|
||||
background: hsl(0, 0%, 97%);
|
||||
margin-bottom: 12px;
|
||||
margin-top: 0px;
|
||||
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||
border-bottom: 1.5px inset var(--main-border-color);
|
||||
margin-left: -8px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
@ -48,14 +53,14 @@ li a {
|
||||
}
|
||||
.menu_left {
|
||||
float: left;
|
||||
border-right: 1px solid hsl(0,0%,80%);
|
||||
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||
border-right: 1px solid var(--main-border-color);
|
||||
border-bottom: 1.5px inset var(--main-border-color);
|
||||
padding-right: 10px;
|
||||
background: hsl(0, 0%, 98%);
|
||||
}
|
||||
.menu_right {
|
||||
float: right;
|
||||
border-left: 1px solid hsl(0,0%,80%);
|
||||
border-left: 1px solid var(--main-border-color);
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
@ -109,10 +114,10 @@ li a {
|
||||
line-height: 16px;
|
||||
width: 300px;
|
||||
right: calc(5% + 7px);
|
||||
border-top: 1px solid hsl(0,0%,80%);
|
||||
border-left: 1px solid hsl(0,0%,80%);
|
||||
border-right: 1px solid hsl(0,0%,80%);
|
||||
border-bottom: 1px solid hsl(0,0%,80%);
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
border-left: 1px solid var(--main-border-color);
|
||||
border-right: 1px solid var(--main-border-color);
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.alertItem {
|
||||
@ -150,7 +155,7 @@ li a {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background: hsl(0, 0%, 98%);
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
border: 1px solid var(--main-border-color);
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
@ -161,14 +166,14 @@ li a {
|
||||
|
||||
/* Explict declaring each border direction to fix a bug in Chrome where an override to .rowhead was also applying to .rowblock in some cases */
|
||||
.rowblock {
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
border: 1px solid var(--main-border-color);
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
padding-top: 0px;
|
||||
border-top: 1px solid hsl(0,0%,80%);
|
||||
border-left: 1px solid hsl(0,0%,80%);
|
||||
border-right: 1px solid hsl(0,0%,80%);
|
||||
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
border-left: 1px solid var(--main-border-color);
|
||||
border-right: 1px solid var(--main-border-color);
|
||||
border-bottom: 1.5px inset var(--main-border-color);
|
||||
}
|
||||
.rowblock:empty {
|
||||
display: none;
|
||||
@ -211,7 +216,7 @@ li a {
|
||||
width: calc(70% - 13px);
|
||||
}
|
||||
.colstack_item {
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
border: 1px solid var(--main-border-color);
|
||||
padding: 0px;
|
||||
padding-top: 0px;
|
||||
width: 100%;
|
||||
@ -230,7 +235,7 @@ li a {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.grid_item {
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
border: 1px solid var(--main-border-color);
|
||||
word-wrap: break-word;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
@ -291,6 +296,34 @@ li a {
|
||||
}
|
||||
|
||||
.opthead { display: none; }
|
||||
.rowitem.has_opt {
|
||||
float: left;
|
||||
width: calc(100% - 50px);
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: none;
|
||||
}
|
||||
.opt {
|
||||
float: left;
|
||||
font-size: 32px;
|
||||
height: 100%;
|
||||
background-color: hsl(0, 0%, 99%);
|
||||
width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
.create_topic_opt a.create_topic_link:before {
|
||||
content: '🖊︎';
|
||||
}
|
||||
.create_topic_opt, .create_topic_opt a {
|
||||
color: rgb(120,120,120);
|
||||
text-decoration: none;
|
||||
}
|
||||
.locked_opt {
|
||||
color: rgb(80,80,80);
|
||||
}
|
||||
.locked_opt:before {
|
||||
content: '🔒︎';
|
||||
}
|
||||
|
||||
.datarow {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
@ -310,7 +343,7 @@ li a {
|
||||
clear: both;
|
||||
}
|
||||
.formrow:not(:last-child) {
|
||||
border-bottom: 1px dotted hsl(0,0%,80%);
|
||||
border-bottom: 1px dotted var(--main-border-color);
|
||||
}
|
||||
|
||||
.formitem {
|
||||
@ -321,7 +354,7 @@ li a {
|
||||
font-weight: normal;
|
||||
}
|
||||
.formitem:not(:last-child) {
|
||||
border-right: 1px dotted hsl(0,0%,80%);
|
||||
border-right: 1px dotted var(--main-border-color);
|
||||
}
|
||||
.formitem.invisible_border {
|
||||
border: none;
|
||||
@ -346,18 +379,18 @@ li a {
|
||||
padding-bottom: 12px;/*16px;*/
|
||||
/*padding-left: 15px;*/
|
||||
}
|
||||
|
||||
.formbutton, button {
|
||||
background: white;
|
||||
border: 1px solid #8e8e8e;
|
||||
}
|
||||
.formbutton {
|
||||
padding: 7px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-size: 15px;
|
||||
border-color: hsl(0,0%,80%);
|
||||
}
|
||||
|
||||
button {
|
||||
background: white;
|
||||
border: 1px solid #8e8e8e;
|
||||
border-color: var(--main-border-color);
|
||||
}
|
||||
|
||||
/* Topics */
|
||||
@ -386,6 +419,39 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.postImage {
|
||||
max-width: 100%;
|
||||
max-height: 200px;
|
||||
background-color: white;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.topic_create_form .topic_button_row .formitem {
|
||||
display: flex;
|
||||
}
|
||||
.topic_create_form .formbutton:first-child {
|
||||
margin-left: 0px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.topic_create_form .formbutton:not(:first-child) {
|
||||
margin-left: 0px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.topic_create_form .formbutton:last-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
.topic_create_form .upload_file_dock {
|
||||
display: flex;
|
||||
}
|
||||
.topic_create_form .uploadItem {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
background-size: 25px 35px;
|
||||
background-repeat: no-repeat;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.topic_status_sticky {
|
||||
display: none;
|
||||
}
|
||||
@ -467,7 +533,7 @@ button {
|
||||
color: #505050; /* 80,80,80 */
|
||||
background-color: #FFFFFF;
|
||||
border-style: solid;
|
||||
border-color: hsl(0,0%,80%);
|
||||
border-color: var(--main-border-color);
|
||||
border-width: 1px;
|
||||
font-size: 15px;
|
||||
}
|
||||
@ -499,7 +565,7 @@ button.username {
|
||||
display: block;
|
||||
padding: 5px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
border: 1px solid var(--main-border-color);
|
||||
background-color: white;
|
||||
}
|
||||
.alert_success {
|
||||
@ -580,10 +646,10 @@ button.username {
|
||||
}
|
||||
|
||||
#profile_left_lane {
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
border: 1px solid var(--main-border-color);
|
||||
width: 220px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||
border-bottom: 1.5px inset var(--main-border-color);
|
||||
}
|
||||
#profile_left_lane .avatarRow {
|
||||
overflow: hidden;
|
||||
@ -612,7 +678,7 @@ button.username {
|
||||
margin-top: 20px;
|
||||
}
|
||||
#profile_right_lane .topic_reply_form {
|
||||
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||
border-bottom: 1.5px inset var(--main-border-color);
|
||||
}
|
||||
|
||||
.simple {
|
||||
@ -652,7 +718,7 @@ button.username {
|
||||
position: sticky;
|
||||
top: 4px;
|
||||
/*box-shadow: 0 1px 2px rgba(0,0,0,.1);*/
|
||||
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||
border-bottom: 1.5px inset var(--main-border-color);
|
||||
}
|
||||
.userinfo .avatar_item {
|
||||
background-repeat: no-repeat, repeat-y;
|
||||
@ -672,10 +738,12 @@ button.username {
|
||||
margin-bottom: 0;
|
||||
margin-right: 3px;
|
||||
/*box-shadow: 0 1px 2px rgba(0,0,0,.1);*/
|
||||
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||
border-bottom: 1.5px inset var(--main-border-color);
|
||||
}
|
||||
|
||||
.action_item .userinfo { display: none; }
|
||||
.action_item .userinfo {
|
||||
display: none;
|
||||
}
|
||||
.action_item .content_container {
|
||||
min-height: auto;
|
||||
padding: 15px;
|
||||
@ -689,7 +757,7 @@ button.username {
|
||||
border-width: 1px;
|
||||
background-color: #FFFFFF;
|
||||
border-style: solid;
|
||||
border-color: hsl(0,0%,80%);
|
||||
border-color: var(--main-border-color);
|
||||
padding: 0px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
@ -720,7 +788,7 @@ button.username {
|
||||
border-bottom: none;
|
||||
}
|
||||
.topic_reply_form {
|
||||
border-top: 1px solid hsl(0,0%,80%);
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
}
|
||||
.post_container .post_item {
|
||||
background-color: #eaeaea;
|
||||
@ -739,7 +807,7 @@ button.username {
|
||||
}
|
||||
|
||||
.footer {
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
border: 1px solid var(--main-border-color);
|
||||
margin-top: 12px;
|
||||
clear: both;
|
||||
height: 40px;
|
||||
@ -747,7 +815,7 @@ button.username {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
background-color: white;
|
||||
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||
border-bottom: 1.5px inset var(--main-border-color);
|
||||
}
|
||||
.footer select {
|
||||
padding: 2px;
|
||||
@ -767,7 +835,7 @@ button.username {
|
||||
margin-top: -5px;
|
||||
}
|
||||
.pageitem {
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
border: 1px solid var(--main-border-color);
|
||||
background-color: white;
|
||||
padding: 5px;
|
||||
margin-right: 5px;
|
||||
|
@ -12,7 +12,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
@media(max-width: 950px) {
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
li {
|
||||
height: 29px;
|
||||
font-size: 15px;
|
||||
@ -20,11 +26,20 @@
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
li, li a {
|
||||
font-size: 15px;
|
||||
}
|
||||
ul {
|
||||
height: 30px;
|
||||
margin-top: 8px;
|
||||
margin-left: -1px;
|
||||
margin-right: -1px;
|
||||
border-bottom: 1px solid hsl(0,0%,80%);
|
||||
}
|
||||
.menu_left, .menu_right {
|
||||
padding-right: 9px;
|
||||
border-bottom: 1px solid hsl(0,0%,80%);
|
||||
}
|
||||
.menu_left, .menu_right { padding-right: 9px; }
|
||||
.menu_alerts {
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
@ -39,9 +54,16 @@
|
||||
height: 100% !important;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.container { width: auto; }
|
||||
.sidebar { display: none; }
|
||||
.selectedAlert .alertList { top: 37px; right: 4px; }
|
||||
.container {
|
||||
width: auto;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.selectedAlert .alertList {
|
||||
top: 37px;
|
||||
right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
@ -54,7 +76,7 @@
|
||||
li a { font-size: 14px; }
|
||||
ul { height: 26px; }
|
||||
.menu_left, .menu_right { padding-right: 7px; }
|
||||
.menu_create_topic, .hide_on_mobile { display: none; }
|
||||
.hide_on_mobile { display: none; }
|
||||
|
||||
.menu_alerts {
|
||||
padding-left: 4px;
|
||||
|
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 301 KiB |
@ -588,7 +588,6 @@ button.username {
|
||||
ul { height: 26px; }
|
||||
.menu_left { padding-right: 5px; padding-top: 1px; }
|
||||
.menu_right { padding-right: 5px; }
|
||||
.menu_create_topic { display: none;}
|
||||
|
||||
.menu_alerts {
|
||||
padding-left: 4px;
|
||||
@ -596,8 +595,13 @@ button.username {
|
||||
font-size: 16px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
.menu_alerts .alert_counter { top: -23px; left: 8px; }
|
||||
.selectedAlert .alertList { top: 33px; }
|
||||
.menu_alerts .alert_counter {
|
||||
top: -23px;
|
||||
left: 8px;
|
||||
}
|
||||
.selectedAlert .alertList {
|
||||
top: 33px;
|
||||
}
|
||||
|
||||
.hide_on_mobile { display: none; }
|
||||
.prev_button, .next_button { top: auto; bottom: 5px; }
|
||||
|
@ -17,7 +17,7 @@ ul {
|
||||
padding-right: 0px;
|
||||
height: 36px;
|
||||
list-style-type: none;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
background-color: rgb(252,252,252);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@ -27,7 +27,7 @@ li {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
background: white;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-bottom: 1px solid hsl(0, 0%, 80%);
|
||||
}
|
||||
li:hover { background: rgb(252,252,252); }
|
||||
li a {
|
||||
@ -38,12 +38,12 @@ li a {
|
||||
}
|
||||
.menu_left {
|
||||
float: left;
|
||||
border-right: 1px solid #ccc;
|
||||
border-right: 1px solid hsl(0, 0%, 80%);
|
||||
padding-right: 10px;
|
||||
}
|
||||
.menu_right {
|
||||
float: right;
|
||||
border-left: 1px solid #ccc;
|
||||
border-left: 1px solid hsl(0, 0%, 80%);
|
||||
padding-right: 10px;
|
||||
}
|
||||
.menu_overview {
|
||||
@ -105,10 +105,10 @@ li a {
|
||||
line-height: 16px;
|
||||
width: 300px;
|
||||
right: calc(5% + 7px);
|
||||
border-top: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-top: 1px solid hsl(0, 0%, 80%);
|
||||
border-left: 1px solid hsl(0, 0%, 80%);
|
||||
border-right: 1px solid hsl(0, 0%, 80%);
|
||||
border-bottom: 1px solid hsl(0, 0%, 80%);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.alertItem {
|
||||
@ -150,7 +150,7 @@ li a {
|
||||
}
|
||||
|
||||
.rowblock {
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
padding-top: 0px;
|
||||
@ -159,14 +159,14 @@ li a {
|
||||
display: none;
|
||||
}
|
||||
.rowmenu {
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
}
|
||||
.rowsmall {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/*.colblock_left {
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
padding: 0px;
|
||||
padding-top: 0px;
|
||||
width: 30%;
|
||||
@ -174,7 +174,7 @@ li a {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.colblock_right {
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
padding: 0px;
|
||||
padding-top: 0px;
|
||||
width: 65%;
|
||||
@ -195,7 +195,7 @@ li a {
|
||||
width: calc(70% - 15px);
|
||||
}
|
||||
.colstack_item {
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
padding: 0px;
|
||||
padding-top: 0px;
|
||||
width: 100%;
|
||||
@ -215,7 +215,7 @@ li a {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.grid_item {
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
word-wrap: break-word;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
@ -260,7 +260,7 @@ li a {
|
||||
background-color: white;
|
||||
}
|
||||
/*.rowitem:not(.passive) { font-size: 17px; }*/
|
||||
.rowitem:not(:last-child) { border-bottom: 1px dotted #ccc; }
|
||||
.rowitem:not(:last-child) { border-bottom: 1px dotted hsl(0, 0%, 80%); }
|
||||
.rowitem a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
@ -272,7 +272,7 @@ li a {
|
||||
.rowitem.has_opt {
|
||||
float: left;
|
||||
width: calc(100% - 50px);
|
||||
border-right: 1px solid #ccc;
|
||||
border-right: 1px solid hsl(0, 0%, 80%);
|
||||
border-bottom: none;
|
||||
}
|
||||
.opt {
|
||||
@ -283,8 +283,8 @@ li a {
|
||||
width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
.create_topic_opt a:before {
|
||||
content '🖊︎';
|
||||
.create_topic_opt a.create_topic_link:before {
|
||||
content: '🖊︎';
|
||||
}
|
||||
.create_topic_opt, .create_topic_opt a {
|
||||
color: rgb(120,120,120);
|
||||
@ -321,7 +321,7 @@ li a {
|
||||
display: table;
|
||||
}
|
||||
.formrow:after { clear: both; }
|
||||
.formrow:not(:last-child) { border-bottom: 1px dotted #ccc; }
|
||||
.formrow:not(:last-child) { border-bottom: 1px dotted hsl(0, 0%, 80%); }
|
||||
|
||||
.formitem {
|
||||
float: left;
|
||||
@ -330,8 +330,12 @@ li a {
|
||||
/*font-size: 17px;*/
|
||||
font-weight: normal;
|
||||
}
|
||||
.formitem:not(:last-child) { border-right: 1px dotted #ccc; }
|
||||
.formitem.invisible_border { border: none; }
|
||||
.formitem:not(:last-child) {
|
||||
border-right: 1px dotted hsl(0, 0%, 80%);
|
||||
}
|
||||
.formitem.invisible_border {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Mostly for textareas */
|
||||
.formitem:only-child { width: 100%; }
|
||||
@ -344,24 +348,26 @@ li a {
|
||||
margin: 0 auto;
|
||||
float: none;
|
||||
}
|
||||
.formitem:not(:only-child) input, .formitem:not(:only-child) select { padding: 3px;/*5px;*/ }
|
||||
.formitem:not(:only-child) input, .formitem:not(:only-child) select {
|
||||
padding: 3px;/*5px;*/
|
||||
}
|
||||
.formitem:not(:only-child).formlabel {
|
||||
padding-top: 15px;/*18px;*/
|
||||
padding-bottom: 12px;/*16px;*/
|
||||
/*padding-left: 15px;*/
|
||||
}
|
||||
|
||||
.formbutton, button {
|
||||
background: white;
|
||||
border: 1px solid #8e8e8e;
|
||||
}
|
||||
.formbutton {
|
||||
padding: 7px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-size: 15px;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
button {
|
||||
background: white;
|
||||
border: 1px solid #8e8e8e;
|
||||
border-color: hsl(0, 0%, 80%);
|
||||
}
|
||||
|
||||
/* Topics */
|
||||
@ -390,6 +396,39 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
.postImage {
|
||||
max-width: 100%;
|
||||
max-height: 200px;
|
||||
background-color: white;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.topic_create_form .topic_button_row .formitem {
|
||||
display: flex;
|
||||
}
|
||||
.topic_create_form .formbutton:first-child {
|
||||
margin-left: 0px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.topic_create_form .formbutton:not(:first-child) {
|
||||
margin-left: 0px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.topic_create_form .formbutton:last-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
.topic_create_form .upload_file_dock {
|
||||
display: flex;
|
||||
}
|
||||
.topic_create_form .uploadItem {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
background-size: 25px 35px;
|
||||
background-repeat: no-repeat;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.username, .panel_tag {
|
||||
text-transform: none;
|
||||
margin-left: 0px;
|
||||
@ -400,7 +439,7 @@ button {
|
||||
color: #505050; /* 80,80,80 */
|
||||
background-color: #FFFFFF;
|
||||
border-style: solid;
|
||||
border-color: #ccc;
|
||||
border-color: hsl(0, 0%, 80%);
|
||||
border-width: 1px;
|
||||
font-size: 15px;
|
||||
}
|
||||
@ -553,7 +592,7 @@ button.username {
|
||||
}
|
||||
|
||||
.postQuote {
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
background: white;
|
||||
padding: 5px;
|
||||
margin: 0px;
|
||||
@ -578,7 +617,7 @@ button.username {
|
||||
display: block;
|
||||
padding: 5px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
}
|
||||
.alert_success {
|
||||
display: block;
|
||||
@ -616,8 +655,12 @@ button.username {
|
||||
text-decoration: none;
|
||||
color: #505050;
|
||||
}
|
||||
.prev_button { left: 14px; }
|
||||
.next_button { right: 14px; }
|
||||
.prev_button {
|
||||
left: 14px;
|
||||
}
|
||||
.next_button {
|
||||
right: 14px;
|
||||
}
|
||||
.head_tag_upshift {
|
||||
float: right;
|
||||
position: relative;
|
||||
@ -625,7 +668,7 @@ button.username {
|
||||
}
|
||||
|
||||
.footer {
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
margin-top: 12px;
|
||||
clear: both;
|
||||
height: 40px;
|
||||
@ -697,7 +740,7 @@ button.username {
|
||||
padding: 5px;
|
||||
margin-right: 5px;
|
||||
padding-bottom: 4px;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
}
|
||||
.pageitem a {
|
||||
color: black;
|
||||
|
@ -52,7 +52,6 @@
|
||||
li a { font-size: 14px; }
|
||||
ul { height: 26px; }
|
||||
.menu_left, .menu_right { padding-right: 7px; }
|
||||
.menu_create_topic { display: none; }
|
||||
|
||||
.menu_alerts {
|
||||
padding-left: 4px;
|
||||
|
5
topic.go
@ -146,7 +146,7 @@ func (topic *Topic) RemoveLike(uid int) error {
|
||||
|
||||
func (topic *Topic) Update(name string, content string) error {
|
||||
content = preparseMessage(content)
|
||||
parsed_content := parseMessage(html.EscapeString(content))
|
||||
parsed_content := parseMessage(html.EscapeString(content), topic.ParentID, "forums")
|
||||
_, err := editTopicStmt.Exec(name, content, parsed_content, topic.ID)
|
||||
|
||||
tcache, ok := topics.(TopicCache)
|
||||
@ -170,6 +170,7 @@ func (topic *Topic) CreateActionReply(action string, ipaddress string, user User
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy gives you a non-pointer concurrency safe copy of the topic
|
||||
func (topic *Topic) Copy() Topic {
|
||||
return *topic
|
||||
}
|
||||
@ -188,7 +189,7 @@ func getTopicUser(tid int) (TopicUser, error) {
|
||||
|
||||
// We might be better off just passing seperate topic and user structs to the caller?
|
||||
return copyTopicToTopicUser(topic, user), nil
|
||||
} else if ucache.GetLength() < ucache.GetCapacity() {
|
||||
} else if ucache.Length() < ucache.GetCapacity() {
|
||||
topic, err = topics.Get(tid)
|
||||
if err != nil {
|
||||
return TopicUser{ID: tid}, err
|
||||
|
@ -8,7 +8,9 @@ package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
@ -20,17 +22,20 @@ import (
|
||||
// TODO: Add some sort of update method
|
||||
// ? - Should we add stick, lock, unstick, and unlock methods? These might be better on the Topics not the TopicStore
|
||||
var topics TopicStore
|
||||
var ErrNoTitle = errors.New("This message is missing a title")
|
||||
var ErrNoBody = errors.New("This message is missing a body")
|
||||
|
||||
type TopicStore interface {
|
||||
Get(id int) (*Topic, error)
|
||||
BypassGet(id int) (*Topic, error)
|
||||
Delete(id int) error
|
||||
Exists(id int) bool
|
||||
Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error)
|
||||
AddLastTopic(item *Topic, fid int) error // unimplemented
|
||||
// TODO: Implement these two methods
|
||||
//GetReplies() ([]*Reply, error)
|
||||
//GetRepliesRange(lower int, higher int) ([]*Reply, error)
|
||||
GetGlobalCount() int
|
||||
//Replies(tid int) ([]*Reply, error)
|
||||
//RepliesRange(tid int, lower int, higher int) ([]*Reply, error)
|
||||
GlobalCount() int
|
||||
}
|
||||
|
||||
type TopicCache interface {
|
||||
@ -43,7 +48,7 @@ type TopicCache interface {
|
||||
CacheRemoveUnsafe(id int) error
|
||||
Flush()
|
||||
Reload(id int) error
|
||||
GetLength() int
|
||||
Length() int
|
||||
SetCapacity(capacity int)
|
||||
GetCapacity() int
|
||||
}
|
||||
@ -176,6 +181,34 @@ func (mts *MemoryTopicStore) Exists(id int) bool {
|
||||
return mts.exists.QueryRow(id).Scan(&id) == nil
|
||||
}
|
||||
|
||||
func (mts *MemoryTopicStore) Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) {
|
||||
topicName = strings.TrimSpace(topicName)
|
||||
if topicName == "" {
|
||||
return 0, ErrNoBody
|
||||
}
|
||||
|
||||
content = strings.TrimSpace(content)
|
||||
parsedContent := parseMessage(content, fid, "forums")
|
||||
if strings.TrimSpace(parsedContent) == "" {
|
||||
return 0, ErrNoBody
|
||||
}
|
||||
|
||||
wcount := wordCount(content)
|
||||
// TODO: Move this statement into the topic store
|
||||
res, err := createTopicStmt.Exec(fid, topicName, content, parsedContent, uid, ipaddress, wcount, uid)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
lastID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = fstore.AddTopic(int(lastID), uid, fid)
|
||||
return int(lastID), err
|
||||
}
|
||||
|
||||
func (mts *MemoryTopicStore) CacheSet(item *Topic) error {
|
||||
mts.Lock()
|
||||
_, ok := mts.items[item.ID]
|
||||
@ -241,7 +274,9 @@ func (mts *MemoryTopicStore) Flush() {
|
||||
mts.Unlock()
|
||||
}
|
||||
|
||||
func (mts *MemoryTopicStore) GetLength() int {
|
||||
// ! Is this concurrent?
|
||||
// Length returns the number of topics in the memory cache
|
||||
func (mts *MemoryTopicStore) Length() int {
|
||||
return int(mts.length)
|
||||
}
|
||||
|
||||
@ -253,8 +288,8 @@ func (mts *MemoryTopicStore) GetCapacity() int {
|
||||
return mts.capacity
|
||||
}
|
||||
|
||||
// Return the total number of topics on these forums
|
||||
func (mts *MemoryTopicStore) GetGlobalCount() int {
|
||||
// GlobalCount returns the total number of topics on these forums
|
||||
func (mts *MemoryTopicStore) GlobalCount() int {
|
||||
var tcount int
|
||||
err := mts.topicCount.QueryRow().Scan(&tcount)
|
||||
if err != nil {
|
||||
@ -314,6 +349,34 @@ func (sts *SQLTopicStore) Exists(id int) bool {
|
||||
return sts.exists.QueryRow(id).Scan(&id) == nil
|
||||
}
|
||||
|
||||
func (sts *SQLTopicStore) Create(fid int, topicName string, content string, uid int, ipaddress string) (tid int, err error) {
|
||||
topicName = strings.TrimSpace(topicName)
|
||||
if topicName == "" {
|
||||
return 0, ErrNoBody
|
||||
}
|
||||
|
||||
content = strings.TrimSpace(content)
|
||||
parsedContent := parseMessage(content, fid, "forums")
|
||||
if strings.TrimSpace(parsedContent) == "" {
|
||||
return 0, ErrNoBody
|
||||
}
|
||||
|
||||
wcount := wordCount(content)
|
||||
// TODO: Move this statement into the topic store
|
||||
res, err := createTopicStmt.Exec(fid, topicName, content, parsedContent, uid, ipaddress, wcount, uid)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
lastID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = fstore.AddTopic(int(lastID), uid, fid)
|
||||
return int(lastID), err
|
||||
}
|
||||
|
||||
// TODO: Use a transaction here
|
||||
func (sts *SQLTopicStore) Delete(id int) error {
|
||||
topic, err := sts.Get(id)
|
||||
@ -347,8 +410,8 @@ func (sts *SQLTopicStore) AddLastTopic(item *Topic, fid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the total number of topics on these forums
|
||||
func (sts *SQLTopicStore) GetGlobalCount() int {
|
||||
// GlobalCount returns the total number of topics on these forums
|
||||
func (sts *SQLTopicStore) GlobalCount() int {
|
||||
var tcount int
|
||||
err := sts.topicCount.QueryRow().Scan(&tcount)
|
||||
if err != nil {
|
||||
|
19
user.go
@ -121,6 +121,24 @@ func (user *User) Activate() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (user *User) ChangeName(username string) (err error) {
|
||||
_, err = setUsernameStmt.Exec(username, user.ID)
|
||||
ucache, ok := users.(UserCache)
|
||||
if ok {
|
||||
ucache.CacheRemove(user.ID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (user *User) ChangeAvatar(avatar string) (err error) {
|
||||
_, err = setAvatarStmt.Exec(avatar, user.ID)
|
||||
ucache, ok := users.(UserCache)
|
||||
if ok {
|
||||
ucache.CacheRemove(user.ID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (user *User) increasePostStats(wcount int, topic bool) error {
|
||||
var mod int
|
||||
baseScore := 1
|
||||
@ -201,6 +219,7 @@ func (user *User) decreasePostStats(wcount int, topic bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy gives you a non-pointer concurrency safe copy of the user
|
||||
func (user *User) Copy() User {
|
||||
return *user
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ type UserStore interface {
|
||||
BulkGetMap(ids []int) (map[int]*User, error)
|
||||
BypassGet(id int) (*User, error)
|
||||
Create(username string, password string, email string, group int, active int) (int, error)
|
||||
GetGlobalCount() int
|
||||
GlobalCount() int
|
||||
}
|
||||
|
||||
type UserCache interface {
|
||||
@ -38,7 +38,7 @@ type UserCache interface {
|
||||
CacheRemoveUnsafe(id int) error
|
||||
Flush()
|
||||
Reload(id int) error
|
||||
GetLength() int
|
||||
Length() int
|
||||
SetCapacity(capacity int)
|
||||
GetCapacity() int
|
||||
}
|
||||
@ -376,7 +376,9 @@ func (mus *MemoryUserStore) Flush() {
|
||||
mus.Unlock()
|
||||
}
|
||||
|
||||
func (mus *MemoryUserStore) GetLength() int {
|
||||
// ! Is this concurrent?
|
||||
// Length returns the number of users in the memory cache
|
||||
func (mus *MemoryUserStore) Length() int {
|
||||
return int(mus.length)
|
||||
}
|
||||
|
||||
@ -388,8 +390,8 @@ func (mus *MemoryUserStore) GetCapacity() int {
|
||||
return mus.capacity
|
||||
}
|
||||
|
||||
// Return the total number of users registered on the forums
|
||||
func (mus *MemoryUserStore) GetGlobalCount() int {
|
||||
// GlobalCount returns the total number of users registered on the forums
|
||||
func (mus *MemoryUserStore) GlobalCount() int {
|
||||
var ucount int
|
||||
err := mus.userCount.QueryRow().Scan(&ucount)
|
||||
if err != nil {
|
||||
@ -554,8 +556,8 @@ func (mus *SQLUserStore) Create(username string, password string, email string,
|
||||
return int(lastID), err
|
||||
}
|
||||
|
||||
// Return the total number of users registered on the forums
|
||||
func (mus *SQLUserStore) GetGlobalCount() int {
|
||||
// GlobalCount returns the total number of users registered on the forums
|
||||
func (mus *SQLUserStore) GlobalCount() int {
|
||||
var ucount int
|
||||
err := mus.userCount.QueryRow().Scan(&ucount)
|
||||
if err != nil {
|
||||
|
19
utils.go
@ -57,9 +57,8 @@ func relativeTime(in string) (string, error) {
|
||||
if in == "" {
|
||||
return "", nil
|
||||
}
|
||||
layout := "2006-01-02 15:04:05"
|
||||
t, err := time.Parse(layout, in)
|
||||
//t, err := time.ParseInLocation(layout, in, timeLocation)
|
||||
|
||||
t, err := time.Parse("2006-01-02 15:04:05", in)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -103,6 +102,8 @@ func relativeTime(in string) (string, error) {
|
||||
// TODO: Write a test for this
|
||||
func convertByteUnit(bytes float64) (float64, string) {
|
||||
switch {
|
||||
case bytes >= float64(petabyte):
|
||||
return bytes / float64(petabyte), "PB"
|
||||
case bytes >= float64(terabyte):
|
||||
return bytes / float64(terabyte), "TB"
|
||||
case bytes >= float64(gigabyte):
|
||||
@ -119,6 +120,8 @@ func convertByteUnit(bytes float64) (float64, string) {
|
||||
// TODO: Write a test for this
|
||||
func convertByteInUnit(bytes float64, unit string) (count float64) {
|
||||
switch unit {
|
||||
case "PB":
|
||||
count = bytes / float64(petabyte)
|
||||
case "TB":
|
||||
count = bytes / float64(terabyte)
|
||||
case "GB":
|
||||
@ -141,7 +144,7 @@ func convertByteInUnit(bytes float64, unit string) (count float64) {
|
||||
func convertUnit(num int) (int, string) {
|
||||
switch {
|
||||
case num >= 1000000000000:
|
||||
return 0, "∞"
|
||||
return num / 1000000000000, "T"
|
||||
case num >= 1000000000:
|
||||
return num / 1000000000, "B"
|
||||
case num >= 1000000:
|
||||
@ -156,8 +159,10 @@ func convertUnit(num int) (int, string) {
|
||||
// TODO: Write a test for this
|
||||
func convertFriendlyUnit(num int) (int, string) {
|
||||
switch {
|
||||
case num >= 1000000000000000:
|
||||
return 0, " quadrillion"
|
||||
case num >= 1000000000000:
|
||||
return 0, " zillion"
|
||||
return 0, " trillion"
|
||||
case num >= 1000000000:
|
||||
return num / 1000000000, " billion"
|
||||
case num >= 1000000:
|
||||
@ -347,7 +352,7 @@ func wordCount(input string) (count int) {
|
||||
func getLevel(score int) (level int) {
|
||||
var base float64 = 25
|
||||
var current, prev float64
|
||||
expFactor := 2.8
|
||||
var expFactor = 2.8
|
||||
|
||||
for i := 1; ; i++ {
|
||||
_, bit := math.Modf(float64(i) / 10)
|
||||
@ -390,7 +395,7 @@ func getLevelScore(getLevel int) (score int) {
|
||||
func getLevels(maxLevel int) []float64 {
|
||||
var base float64 = 25
|
||||
var current, prev float64 // = 0
|
||||
expFactor := 2.8
|
||||
var expFactor = 2.8
|
||||
var out []float64
|
||||
out = append(out, 0)
|
||||
|
||||
|