Revamped Tempra Conflux.
Updated the readme. Added more comments. The forumView cache for MemoryForumStore now excludes Social Groups. Moved the word filter logic into it's own file. You can now change the language via the configuration file. Moved the inline CSS into the CSS files.
This commit is contained in:
parent
01bef7a320
commit
ce9195c841
|
@ -152,15 +152,11 @@ We're looking for ways to clean-up the plugin system so that all of them (except
|
|||
|
||||
![Tempra Conflux Mobile](https://github.com/Azareal/Gosora/blob/master/images/tempra-conflux-control-panel.png)
|
||||
|
||||
![Cosmo Conflux Theme](https://github.com/Azareal/Gosora/blob/master/images/cosmo-conflux.png)
|
||||
|
||||
![Cosmo Theme](https://github.com/Azareal/Gosora/blob/master/images/cosmo.png)
|
||||
|
||||
More images in the /images/ folder. Beware though, some of them are *really* outdated.
|
||||
|
||||
# Dependencies
|
||||
|
||||
* Go 1.8
|
||||
* Go 1.9
|
||||
|
||||
* MariaDB (or any other MySQL compatible database engine). We'll allow other database engines in the future.
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
/* Work in progress. Check back later! */
|
||||
/*
|
||||
*
|
||||
* Gosora Forum Store
|
||||
* Copyright Azareal 2017 - 2018
|
||||
*
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -15,6 +20,7 @@ var forumCreateMutex sync.Mutex
|
|||
var forumPerms map[int]map[int]ForumPerms // [gid][fid]Perms
|
||||
var fstore ForumStore
|
||||
|
||||
// ForumStore is an interface for accessing the forums and the metadata stored on them
|
||||
type ForumStore interface {
|
||||
LoadForums() error
|
||||
DirtyGet(id int) *Forum
|
||||
|
@ -43,9 +49,9 @@ type ForumStore interface {
|
|||
GetGlobalCount() 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
|
||||
type MemoryForumStore struct {
|
||||
//forums map[int]*Forum
|
||||
forums sync.Map
|
||||
forums sync.Map // map[int]*Forum
|
||||
forumView atomic.Value // []*Forum
|
||||
//fids []int
|
||||
forumCount int
|
||||
|
@ -56,6 +62,7 @@ type MemoryForumStore struct {
|
|||
getForumCount *sql.Stmt
|
||||
}
|
||||
|
||||
// NewMemoryForumStore gives you a new instance of MemoryForumStore
|
||||
func NewMemoryForumStore() *MemoryForumStore {
|
||||
getStmt, err := qgen.Builder.SimpleSelect("forums", "name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "fid = ?", "", "")
|
||||
if err != nil {
|
||||
|
@ -81,6 +88,7 @@ func NewMemoryForumStore() *MemoryForumStore {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Add support for subforums
|
||||
func (mfs *MemoryForumStore) LoadForums() error {
|
||||
log.Print("Adding the uncategorised forum")
|
||||
forumUpdateMutex.Lock()
|
||||
|
@ -89,7 +97,7 @@ func (mfs *MemoryForumStore) LoadForums() error {
|
|||
var forumView []*Forum
|
||||
addForum := func(forum *Forum) {
|
||||
mfs.forums.Store(forum.ID, forum)
|
||||
if forum.Active && forum.Name != "" {
|
||||
if forum.Active && forum.Name != "" && forum.ParentType == "" {
|
||||
forumView = append(forumView, forum)
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +140,8 @@ func (mfs *MemoryForumStore) rebuildView() {
|
|||
var forumView []*Forum
|
||||
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
|
||||
forum := value.(*Forum)
|
||||
if forum.Active && forum.Name != "" {
|
||||
// ? - ParentType blank means that it doesn't have a parent
|
||||
if forum.Active && forum.Name != "" && forum.ParentType == "" {
|
||||
forumView = append(forumView, forum)
|
||||
}
|
||||
return true
|
||||
|
|
|
@ -124,7 +124,7 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
|
|||
|
||||
for pb.Next() {
|
||||
topicW.Body.Reset()
|
||||
route_topic_id(topicW, topicReqAdmin, user)
|
||||
routeTopicID(topicW, topicReqAdmin, user)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ func BenchmarkTopicGuestRouteParallel(b *testing.B) {
|
|||
topicReq := httptest.NewRequest("get", "/topic/1", bytes.NewReader(nil))
|
||||
for pb.Next() {
|
||||
topicW.Body.Reset()
|
||||
route_topic_id(topicW, topicReq, guestUser)
|
||||
routeTopicID(topicW, topicReq, guestUser)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package main
|
||||
|
||||
// TODO: Coming Soon. Probably in the next commit or two
|
|
@ -146,22 +146,23 @@ func main() {
|
|||
|
||||
func init() {
|
||||
// Site Info
|
||||
site.Name = "` + siteName + `" // Should be a setting in the database
|
||||
site.Email = "" // Should be a setting in the database
|
||||
site.Url = "` + siteURL + `"
|
||||
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
|
||||
db_config.Host = "` + dbHost + `"
|
||||
db_config.Username = "` + dbUsername + `"
|
||||
db_config.Password = "` + dbPassword + `"
|
||||
db_config.Dbname = "` + dbName + `"
|
||||
db_config.Port = "` + dbPort + `" // You probably won't need to change this
|
||||
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
|
||||
|
|
|
@ -53,7 +53,7 @@ func _initMysql() (err error) {
|
|||
|
||||
if err == sql.ErrNoRows {
|
||||
fmt.Println("Unable to find the database. Attempting to create it")
|
||||
_, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + dbName + "")
|
||||
_, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + dbName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -39,5 +39,9 @@
|
|||
},
|
||||
"SettingLabels": {
|
||||
"activation_type": "Activate All,Email Activation,Admin Approval"
|
||||
},
|
||||
"Accounts": {
|
||||
"VerifyEmailSubject": "Validate Your Email @ {{name}}",
|
||||
"VerifyEmailBody": "Dear {{username}}, following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. {{schema}}://{{url}}/user/edit/token/{{token}}\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused."
|
||||
}
|
||||
}
|
62
main.go
62
main.go
|
@ -1,4 +1,9 @@
|
|||
/* Copyright Azareal 2016 - 2018 */
|
||||
/*
|
||||
*
|
||||
* Gosora Main File
|
||||
* Copyright Azareal 2016 - 2018
|
||||
*
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -8,7 +13,6 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
//"runtime/pprof"
|
||||
)
|
||||
|
@ -27,8 +31,6 @@ const terabyte int = gigabyte * 1024
|
|||
const saltLength int = 32
|
||||
const sessionLength int = 80
|
||||
|
||||
var enableWebsockets = false // Don't change this, the value is overwritten by an initialiser
|
||||
|
||||
var router *GenRouter
|
||||
var startTime time.Time
|
||||
|
||||
|
@ -41,55 +43,13 @@ var groupCapCount int
|
|||
var staticFiles = make(map[string]SFile)
|
||||
var logWriter = io.MultiWriter(os.Stderr)
|
||||
|
||||
type WordFilter struct {
|
||||
ID int
|
||||
Find string
|
||||
Replacement string
|
||||
}
|
||||
type WordFilterBox map[int]WordFilter
|
||||
|
||||
var wordFilterBox atomic.Value // An atomic value holding a WordFilterBox
|
||||
|
||||
func init() {
|
||||
wordFilterBox.Store(WordFilterBox(make(map[int]WordFilter)))
|
||||
}
|
||||
|
||||
func LoadWordFilters() error {
|
||||
rows, err := get_word_filters_stmt.Query()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var wordFilters = WordFilterBox(make(map[int]WordFilter))
|
||||
var wfid int
|
||||
var find string
|
||||
var replacement string
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&wfid, &find, &replacement)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wordFilters[wfid] = WordFilter{ID: wfid, Find: find, Replacement: replacement}
|
||||
}
|
||||
wordFilterBox.Store(wordFilters)
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
func addWordFilter(id int, find string, replacement string) {
|
||||
wordFilters := wordFilterBox.Load().(WordFilterBox)
|
||||
wordFilters[id] = WordFilter{ID: id, Find: find, Replacement: replacement}
|
||||
wordFilterBox.Store(wordFilters)
|
||||
}
|
||||
|
||||
func processConfig() {
|
||||
config.Noavatar = strings.Replace(config.Noavatar, "{site_url}", site.Url, -1)
|
||||
config.Noavatar = strings.Replace(config.Noavatar, "{site_url}", site.URL, -1)
|
||||
if site.Port != "80" && site.Port != "443" {
|
||||
site.Url = strings.TrimSuffix(site.Url, "/")
|
||||
site.Url = strings.TrimSuffix(site.Url, "\\")
|
||||
site.Url = strings.TrimSuffix(site.Url, ":")
|
||||
site.Url = site.Url + ":" + site.Port
|
||||
site.URL = strings.TrimSuffix(site.URL, "/")
|
||||
site.URL = strings.TrimSuffix(site.URL, "\\")
|
||||
site.URL = strings.TrimSuffix(site.URL, ":")
|
||||
site.URL = site.URL + ":" + site.Port
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -137,7 +137,7 @@ func TestForumStore(t *testing.T) {
|
|||
|
||||
func TestSlugs(t *testing.T) {
|
||||
var res string
|
||||
var msgList []ME_Pair
|
||||
var msgList []MEPair
|
||||
|
||||
msgList = addMEPair(msgList, "Unknown", "unknown")
|
||||
msgList = addMEPair(msgList, "Unknown2", "unknown2")
|
||||
|
|
6
mysql.go
6
mysql.go
|
@ -27,12 +27,12 @@ func init() {
|
|||
|
||||
func _initDatabase() (err error) {
|
||||
var _dbpassword string
|
||||
if db_config.Password != "" {
|
||||
_dbpassword = ":" + db_config.Password
|
||||
if dbConfig.Password != "" {
|
||||
_dbpassword = ":" + dbConfig.Password
|
||||
}
|
||||
|
||||
// Open the database connection
|
||||
db, err = sql.Open("mysql", db_config.Username+_dbpassword+"@tcp("+db_config.Host+":"+db_config.Port+")/"+db_config.Dbname+"?collation="+dbCollation)
|
||||
db, err = sql.Open("mysql", dbConfig.Username+_dbpassword+"@tcp("+dbConfig.Host+":"+dbConfig.Port+")/"+dbConfig.Dbname+"?collation="+dbCollation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -5,33 +5,36 @@ package main
|
|||
import "errors"
|
||||
import "net/http"
|
||||
|
||||
var wsHub WS_Hub
|
||||
// TODO: Disable WebSockets on high load? Add a Control Panel interface for disabling it?
|
||||
var enableWebsockets = false // Put this in caps for consistency with the other constants?
|
||||
|
||||
var wsHub WSHub
|
||||
var errWsNouser = errors.New("This user isn't connected via WebSockets")
|
||||
|
||||
type WS_Hub struct {
|
||||
type WSHub struct {
|
||||
}
|
||||
|
||||
func (_ *WS_Hub) guestCount() int {
|
||||
func (_ *WSHub) guestCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (_ *WS_Hub) userCount() int {
|
||||
func (_ *WSHub) userCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) broadcastMessage(_ string) error {
|
||||
func (hub *WSHub) broadcastMessage(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) pushMessage(_ int, _ string) error {
|
||||
func (hub *WSHub) pushMessage(_ int, _ string) error {
|
||||
return errWsNouser
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) pushAlert(_ int, _ int, _ string, _ string, _ int, _ int, _ int) error {
|
||||
func (hub *WSHub) pushAlert(_ int, _ int, _ string, _ string, _ int, _ int, _ int) error {
|
||||
return errWsNouser
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) pushAlerts(_ []int, _ int, _ string, _ string, _ int, _ int, _ int) error {
|
||||
func (hub *WSHub) pushAlerts(_ []int, _ int, _ string, _ string, _ int, _ int, _ int) error {
|
||||
return errWsNouser
|
||||
}
|
||||
|
||||
|
|
4
pages.go
4
pages.go
|
@ -207,10 +207,10 @@ type PanelEditForumPage struct {
|
|||
Groups []GroupForumPermPreset
|
||||
}
|
||||
|
||||
type NameLangPair struct {
|
||||
/*type NameLangPair struct {
|
||||
Name string
|
||||
LangStr string
|
||||
}
|
||||
}*/
|
||||
|
||||
type NameLangToggle struct {
|
||||
Name string
|
||||
|
|
14
phrases.go
14
phrases.go
|
@ -19,7 +19,6 @@ import (
|
|||
|
||||
// TODO: Let the admin edit phrases from inside the Control Panel? How should we persist these? Should we create a copy of the langpack or edit the primaries? Use the changeLangpack mutex for this?
|
||||
// nolint Be quiet megacheck, this *is* used
|
||||
var currentLanguage = "english"
|
||||
var currentLangPack atomic.Value
|
||||
var langpackCount int // TODO: Use atomics for this
|
||||
|
||||
|
@ -40,6 +39,7 @@ type LanguagePack struct {
|
|||
GlobalPerms map[string]string
|
||||
LocalPerms map[string]string
|
||||
SettingLabels map[string]string
|
||||
Accounts map[string]string // TODO: Apply these phrases in the software proper
|
||||
}
|
||||
|
||||
// TODO: Add the ability to edit language JSON files from the Control Panel and automatically scan the files for changes
|
||||
|
@ -86,9 +86,9 @@ func initPhrases() error {
|
|||
return errors.New("You don't have any language packs")
|
||||
}
|
||||
|
||||
langPack, ok := langpacks.Load(currentLanguage)
|
||||
langPack, ok := langpacks.Load(site.Language)
|
||||
if !ok {
|
||||
return errors.New("Couldn't find the " + currentLanguage + " language pack")
|
||||
return errors.New("Couldn't find the " + site.Language + " language pack")
|
||||
}
|
||||
currentLangPack.Store(langPack)
|
||||
return nil
|
||||
|
@ -139,6 +139,14 @@ func GetAllSettingLabels() map[string]string {
|
|||
return currentLangPack.Load().(*LanguagePack).SettingLabels
|
||||
}
|
||||
|
||||
func GetAccountPhrase(name string) string {
|
||||
res, ok := currentLangPack.Load().(*LanguagePack).Accounts[name]
|
||||
if !ok {
|
||||
return "{name}"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// ? - Use runtime reflection for updating phrases?
|
||||
// TODO: Implement these
|
||||
func AddPhrase() {
|
||||
|
|
|
@ -3,22 +3,21 @@ package main
|
|||
import "strconv"
|
||||
import "testing"
|
||||
|
||||
// TODO: Replace the soft tabs with hard ones
|
||||
// go test -v
|
||||
|
||||
type ME_Pair struct {
|
||||
type MEPair struct {
|
||||
Msg string
|
||||
Expects string
|
||||
}
|
||||
|
||||
func addMEPair(msgList []ME_Pair, msg string, expects string) []ME_Pair {
|
||||
return append(msgList, ME_Pair{msg, expects})
|
||||
func addMEPair(msgList []MEPair, msg string, expects string) []MEPair {
|
||||
return append(msgList, MEPair{msg, expects})
|
||||
}
|
||||
|
||||
func TestBBCodeRender(t *testing.T) {
|
||||
//t.Skip()
|
||||
var res string
|
||||
var msgList []ME_Pair
|
||||
var msgList []MEPair
|
||||
msgList = addMEPair(msgList, "hi", "hi")
|
||||
msgList = addMEPair(msgList, "😀", "😀")
|
||||
msgList = addMEPair(msgList, "[b]😀[/b]", "<b>😀</b>")
|
||||
|
@ -190,7 +189,7 @@ func TestBBCodeRender(t *testing.T) {
|
|||
func TestMarkdownRender(t *testing.T) {
|
||||
//t.Skip()
|
||||
var res string
|
||||
var msgList []ME_Pair
|
||||
var msgList []MEPair
|
||||
msgList = addMEPair(msgList, "hi", "hi")
|
||||
msgList = addMEPair(msgList, "**hi**", "<b>hi</b>")
|
||||
msgList = addMEPair(msgList, "_hi_", "<u>hi</u>")
|
||||
|
|
21
site.go
21
site.go
|
@ -2,24 +2,23 @@ package main
|
|||
|
||||
import "net/http"
|
||||
|
||||
// TODO: Add a langPack configuration item or setting
|
||||
|
||||
var site = &Site{Name: "Magical Fairy Land"}
|
||||
var db_config = DB_Config{Host: "localhost"}
|
||||
var site = &Site{Name: "Magical Fairy Land", Language: "english"}
|
||||
var dbConfig = DBConfig{Host: "localhost"}
|
||||
var config Config
|
||||
var dev DevConfig
|
||||
|
||||
type Site struct {
|
||||
Name string
|
||||
Email string
|
||||
Url string
|
||||
Name string // ? - Move this into the settings table?
|
||||
Email string // ? - Move this into the settings table?
|
||||
URL string
|
||||
Port string
|
||||
EnableSsl bool
|
||||
EnableEmails bool
|
||||
HasProxy bool
|
||||
Language string // ? - Move this into the settings table?
|
||||
}
|
||||
|
||||
type DB_Config struct {
|
||||
type DBConfig struct {
|
||||
Host string
|
||||
Username string
|
||||
Password string
|
||||
|
@ -44,13 +43,13 @@ type Config struct {
|
|||
DefaultRoute func(http.ResponseWriter, *http.Request, User)
|
||||
DefaultGroup int
|
||||
ActivationGroup int
|
||||
StaffCss string
|
||||
StaffCss string // ? - Move this into the settings table? Might be better to implement this as Group CSS
|
||||
UncategorisedForumVisible bool
|
||||
MinifyTemplates bool
|
||||
MultiServer bool
|
||||
|
||||
Noavatar string
|
||||
ItemsPerPage int
|
||||
Noavatar string // ? - Move this into the settings table?
|
||||
ItemsPerPage int // ? - Move this into the settings table?
|
||||
}
|
||||
|
||||
type DevConfig struct {
|
||||
|
|
6
tasks.go
6
tasks.go
|
@ -1,3 +1,9 @@
|
|||
/*
|
||||
*
|
||||
* Gosora Task System
|
||||
* Copyright Azareal 2017 - 2018
|
||||
*
|
||||
*/
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
|
|
@ -246,7 +246,7 @@ var topic_91 = []byte(`
|
|||
|
||||
`)
|
||||
var footer_0 = []byte(`<div class="footer">
|
||||
<div id="poweredBy" style="float: left;margin-top: 4px;">Powered by Gosora - <span style="font-size: 12px;">Made with love by Azareal</span></div>
|
||||
<div id="poweredBy">Powered by Gosora - <span>Made with love by Azareal</span></div>
|
||||
<form action="/theme/" method="post">
|
||||
<div id="themeSelector" style="float: right;">
|
||||
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">
|
||||
|
@ -312,10 +312,8 @@ var topic_alt_19 = []byte(`
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Stop inling this x.x -->
|
||||
<style type="text/css">.rowitem:last-child .content_container { margin-bottom: 5px !important; }</style>
|
||||
<div class="rowblock post_container" style="border-top: none;">
|
||||
<article class="rowitem passive deletable_block editable_parent post_item top_post" style="background-color: #eaeaea;padding-top: 4px;padding-left: 5px;clear: both;border-bottom: none;padding-right: 4px;padding-bottom: 2px;">
|
||||
<div class="rowblock post_container">
|
||||
<article class="rowitem passive deletable_block editable_parent post_item top_post">
|
||||
<div class="userinfo">
|
||||
<div class="avatar_item" style="background-image: url(`)
|
||||
var topic_alt_20 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;"> </div>
|
||||
|
@ -426,7 +424,7 @@ var topic_alt_84 = []byte(`
|
|||
var topic_alt_85 = []byte(`</div>
|
||||
`)
|
||||
var topic_alt_86 = []byte(`
|
||||
<div class="rowblock topic_reply_form" style="border-top: none;">
|
||||
<div class="rowblock topic_reply_form">
|
||||
<form action="/reply/create/" method="post">
|
||||
<input name="tid" value='`)
|
||||
var topic_alt_87 = []byte(`' type="hidden" />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="footer">
|
||||
<div id="poweredBy" style="float: left;margin-top: 4px;">Powered by Gosora - <span style="font-size: 12px;">Made with love by Azareal</span></div>
|
||||
<div id="poweredBy">Powered by Gosora - <span>Made with love by Azareal</span></div>
|
||||
<form action="/theme/" method="post">
|
||||
<div id="themeSelector" style="float: right;">
|
||||
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">
|
||||
|
|
|
@ -22,10 +22,8 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Stop inling this x.x -->
|
||||
<style type="text/css">.rowitem:last-child .content_container { margin-bottom: 5px !important; }</style>
|
||||
<div class="rowblock post_container" style="border-top: none;">
|
||||
<article class="rowitem passive deletable_block editable_parent post_item top_post" style="background-color: #eaeaea;padding-top: 4px;padding-left: 5px;clear: both;border-bottom: none;padding-right: 4px;padding-bottom: 2px;">
|
||||
<div class="rowblock post_container">
|
||||
<article class="rowitem passive deletable_block editable_parent post_item top_post">
|
||||
<div class="userinfo">
|
||||
<div class="avatar_item" style="background-image: url({{.Topic.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;"> </div>
|
||||
<a href="{{.Topic.UserLink}}" class="the_name">{{.Topic.CreatedByName}}</a>
|
||||
|
@ -79,7 +77,7 @@
|
|||
</article>
|
||||
{{end}}</div>
|
||||
{{if .CurrentUser.Perms.CreateReply}}
|
||||
<div class="rowblock topic_reply_form" style="border-top: none;">
|
||||
<div class="rowblock topic_reply_form">
|
||||
<form action="/reply/create/" method="post">
|
||||
<input name="tid" value='{{.Topic.ID}}' type="hidden" />
|
||||
<div class="formrow">
|
||||
|
|
|
@ -460,6 +460,13 @@ input, select, textarea {
|
|||
font-size: 13px;
|
||||
padding: 4px;
|
||||
}
|
||||
#poweredBy {
|
||||
float: left;
|
||||
margin-top: 4px;
|
||||
}
|
||||
#poweredBy span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Forum View */
|
||||
.rowhead {
|
||||
|
|
|
@ -7,21 +7,29 @@
|
|||
body {
|
||||
font-family: arial;
|
||||
padding-bottom: 8px;
|
||||
/* TODO: Redo the background */
|
||||
background-image: url('/static/test_bg2.svg');
|
||||
background-size: cover;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Patch for Edge, until they fix emojis in arial x.x */
|
||||
@supports (-ms-ime-align:auto) { .user_content { font-family: Segoe UI Emoji, arial; } }
|
||||
@supports (-ms-ime-align:auto) {
|
||||
.user_content { font-family: Segoe UI Emoji, arial; }
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
height: 36px;
|
||||
list-style-type: none;
|
||||
border: 1px solid #ccc;
|
||||
background-color: white;
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
background: hsl(0, 0%, 97%);
|
||||
margin-bottom: 12px;
|
||||
margin-top: 0px;
|
||||
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||
margin-left: -8px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
li {
|
||||
height: 35px;
|
||||
|
@ -29,7 +37,9 @@ li {
|
|||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
li:hover { background: rgb(250,250,250); }
|
||||
li:hover {
|
||||
background: rgb(250,250,250);
|
||||
}
|
||||
li a {
|
||||
text-decoration: none;
|
||||
/*color: #515151;*/
|
||||
|
@ -38,12 +48,14 @@ li a {
|
|||
}
|
||||
.menu_left {
|
||||
float: left;
|
||||
border-right: 1px solid #ccc;
|
||||
border-right: 1px solid hsl(0,0%,80%);
|
||||
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||
padding-right: 10px;
|
||||
background: hsl(0, 0%, 98%);
|
||||
}
|
||||
.menu_right {
|
||||
float: right;
|
||||
border-left: 1px solid #ccc;
|
||||
border-left: 1px solid hsl(0,0%,80%);
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
|
@ -85,8 +97,9 @@ li a {
|
|||
color: black;
|
||||
font-weight: bold;
|
||||
}
|
||||
.selectedAlert .alert_counter { display: none; }
|
||||
.menu_alerts .alertList { display: none; }
|
||||
.selectedAlert .alert_counter, .menu_alerts .alertList {
|
||||
display: none;
|
||||
}
|
||||
.selectedAlert .alertList {
|
||||
position: absolute;
|
||||
top: 51px;
|
||||
|
@ -96,10 +109,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 {
|
||||
|
@ -136,19 +149,47 @@ li a {
|
|||
padding: 0px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background: hsl(0, 0%, 98%);
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
#back {
|
||||
padding: 12px;
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
/* 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 #ccc;
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
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%);
|
||||
}
|
||||
.rowblock:empty {
|
||||
display: none;
|
||||
}
|
||||
.rowblock:empty { display: none; }
|
||||
.rowsmall {
|
||||
font-size:12px;
|
||||
}
|
||||
|
||||
/* Firefox specific CSS */
|
||||
@supports (-moz-appearance: none) {
|
||||
ul, .menu_left, .rowblock {
|
||||
border-bottom: 2px inset hsl(0,0%,40%);
|
||||
}
|
||||
}
|
||||
/* Edge... We can't get the exact shade here, because of how they implemented it x.x */
|
||||
@supports (-ms-ime-align:auto) {
|
||||
ul, .menu_left, .rowblock {
|
||||
border-bottom: 1.5px inset hsl(0,0%,100%);
|
||||
}
|
||||
}
|
||||
|
||||
.rowlist {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -162,25 +203,6 @@ li a {
|
|||
padding-left: 48px;
|
||||
}
|
||||
|
||||
.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, .colblock_right:empty { display: none; }
|
||||
|
||||
/* The new method of doing columns layouts, colblock is now deprecated :( */
|
||||
.colstack_left {
|
||||
float: left;
|
||||
width: 30%;
|
||||
|
@ -192,7 +214,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%;
|
||||
|
@ -211,7 +233,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%;
|
||||
|
@ -223,21 +245,33 @@ li a {
|
|||
padding-bottom: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.grid_istat { margin-bottom: 5px; }
|
||||
.grid_istat {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat_green { background-color: lightgreen; border-color: green; }
|
||||
.stat_orange { background-color: #ffe4b3; border-color: orange; }
|
||||
.stat_red { background-color: #ffb2b2; border-color: red; }
|
||||
.stat_disabled { background-color: lightgray; border-color: gray; }
|
||||
.stat_green {
|
||||
background-color: lightgreen;
|
||||
border-color: green;
|
||||
}
|
||||
.stat_orange {
|
||||
background-color: #ffe4b3;
|
||||
border-color: orange;
|
||||
}
|
||||
.stat_red {
|
||||
background-color: #ffb2b2;
|
||||
border-color: red;
|
||||
}
|
||||
.stat_disabled {
|
||||
background-color: lightgray;
|
||||
border-color: gray;
|
||||
}
|
||||
|
||||
.rowitem {
|
||||
width: 100%;
|
||||
/*padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 17px;
|
||||
padding-bottom: 12px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;*/
|
||||
padding-bottom: 12px;*/
|
||||
padding-left: 10px;
|
||||
padding-top: 14px;
|
||||
padding-bottom: 12px;
|
||||
|
@ -249,13 +283,16 @@ li a {
|
|||
text-transform: none;
|
||||
}
|
||||
.rowitem:not(:last-child) {
|
||||
border-bottom: 1px dotted #ccc;
|
||||
border-bottom: 1px solid hsl(0,0%,85%);
|
||||
}
|
||||
.rowitem a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.rowitem a:hover { color: silver; }
|
||||
.rowitem a:hover {
|
||||
color: hsl(0,0%,40%);
|
||||
}
|
||||
|
||||
.opthead { display: none; }
|
||||
.datarow {
|
||||
padding-top: 10px;
|
||||
|
@ -272,8 +309,12 @@ li a {
|
|||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
.formrow:after { clear: both; }
|
||||
.formrow:not(:last-child) { border-bottom: 1px dotted #ccc; }
|
||||
.formrow:after {
|
||||
clear: both;
|
||||
}
|
||||
.formrow:not(:last-child) {
|
||||
border-bottom: 1px dotted hsl(0,0%,80%);
|
||||
}
|
||||
|
||||
.formitem {
|
||||
float: left;
|
||||
|
@ -282,8 +323,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%; }
|
||||
|
@ -296,7 +341,9 @@ 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;*/
|
||||
|
@ -308,7 +355,7 @@ li a {
|
|||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-size: 15px;
|
||||
border-color: #ccc;
|
||||
border-color: hsl(0,0%,80%);
|
||||
}
|
||||
|
||||
button {
|
||||
|
@ -318,6 +365,33 @@ button {
|
|||
|
||||
/* Topics */
|
||||
|
||||
.topic_list {
|
||||
display: grid;
|
||||
grid-template-columns: calc(100% - 204px) 204px;
|
||||
}
|
||||
.topic_list .topic_inner_right {
|
||||
display: none;
|
||||
}
|
||||
.topic_list .lastReplyAt {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@supports not (display: grid) {
|
||||
.topic_list .rowitem {
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
.topic_list .topic_left {
|
||||
width: calc(100% - 204px);
|
||||
}
|
||||
.topic_list .topic_right {
|
||||
width: 204px;
|
||||
}
|
||||
}
|
||||
|
||||
.topic_status_sticky {
|
||||
display: none;
|
||||
}
|
||||
.topic_sticky {
|
||||
background-color: rgb(255,255,234);
|
||||
}
|
||||
|
@ -338,8 +412,22 @@ button {
|
|||
}
|
||||
.topic_status:empty { display: none; }
|
||||
|
||||
.rowhead, .colstack_head {
|
||||
border-bottom: none;
|
||||
}
|
||||
.rowhead .rowitem, .opthead .rowitem, .colstack_head .rowitem {
|
||||
background: linear-gradient(to bottom, white, hsl(0, 0%, 93%));
|
||||
/*background: linear-gradient(to bottom, white, hsl(0, 0%, 93%));*/
|
||||
background: hsl(0, 0%, 98%);
|
||||
}
|
||||
.rowhead h1, .colstack_head h1 {
|
||||
-webkit-margin-before: 0; /* Opera / Chrome Implicit padding */
|
||||
-webkit-margin-after: 0;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
margin-block-start: 0; /* Firefox Implicit padding */
|
||||
margin-block-end: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0; /* Edge Implicit padding */
|
||||
}
|
||||
.topic_sticky_head {
|
||||
background-color: #FFFFEA;
|
||||
|
@ -360,7 +448,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;
|
||||
}
|
||||
|
@ -396,7 +484,7 @@ button.username {
|
|||
display: block;
|
||||
padding: 5px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
background-color: white;
|
||||
}
|
||||
.alert_success {
|
||||
|
@ -415,16 +503,19 @@ button.username {
|
|||
}
|
||||
|
||||
/* Tempra Conflux */
|
||||
|
||||
.user_content {
|
||||
padding: 5px;
|
||||
margin-top: 3px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 0;
|
||||
background: white;
|
||||
min-height: 145px;
|
||||
padding-bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.user_content.nobuttons { min-height: 168px; }
|
||||
.user_content.nobuttons {
|
||||
min-height: 168px;
|
||||
}
|
||||
|
||||
.button_container {
|
||||
border-top: solid 1px #eaeaea;
|
||||
|
@ -515,7 +606,8 @@ button.username {
|
|||
float: left;
|
||||
position: sticky;
|
||||
top: 4px;
|
||||
box-shadow:0 1px 2px rgba(0,0,0,.1);
|
||||
/*box-shadow: 0 1px 2px rgba(0,0,0,.1);*/
|
||||
border-bottom: 1px inset hsl(0,0%,80%);
|
||||
}
|
||||
.userinfo .avatar_item {
|
||||
background-repeat: no-repeat, repeat-y;
|
||||
|
@ -534,7 +626,8 @@ button.username {
|
|||
min-height: 128px;
|
||||
margin-bottom: 0;
|
||||
margin-right: 3px;
|
||||
box-shadow:0 1px 2px rgba(0,0,0,.1);
|
||||
/*box-shadow: 0 1px 2px rgba(0,0,0,.1);*/
|
||||
border-bottom: 1px inset hsl(0,0%,80%);
|
||||
}
|
||||
|
||||
.action_item .userinfo { display: none; }
|
||||
|
@ -551,7 +644,7 @@ button.username {
|
|||
border-width: 1px;
|
||||
background-color: #FFFFFF;
|
||||
border-style: solid;
|
||||
border-color: #ccc;
|
||||
border-color: hsl(0,0%,80%);
|
||||
padding: 0px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
|
@ -574,4 +667,62 @@ button.username {
|
|||
top: -2px;
|
||||
}
|
||||
|
||||
.post_container {
|
||||
border-bottom: none;
|
||||
}
|
||||
.topic_reply_form {
|
||||
border-top: 1px solid hsl(0,0%,80%);
|
||||
}
|
||||
.post_container .post_item {
|
||||
background-color: #eaeaea;
|
||||
padding-top: 4px;
|
||||
padding-left: 5px;
|
||||
clear: both;
|
||||
border-bottom: none;
|
||||
padding-right: 4px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
.post_container .post_item:first-child {
|
||||
padding-top: 6px;
|
||||
}
|
||||
.post_container .post_item:last-child .content_container {
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border: 1px solid hsl(0,0%,80%);
|
||||
margin-top: 12px;
|
||||
clear: both;
|
||||
height: 40px;
|
||||
padding: 6px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
background-color: white;
|
||||
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||
}
|
||||
.footer select {
|
||||
padding: 2px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
#poweredBy {
|
||||
float: left;
|
||||
margin-top: 4px;
|
||||
}
|
||||
#poweredBy span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Firefox specific CSS */
|
||||
@supports (-moz-appearance: none) {
|
||||
.footer {
|
||||
border-bottom: 2px inset hsl(0,0%,40%);
|
||||
}
|
||||
}
|
||||
/* Edge... We can't get the exact shade here, because of how they implemented it x.x */
|
||||
@supports (-ms-ime-align:auto) {
|
||||
.footer {
|
||||
border-bottom: 1.5px inset hsl(0,0%,100%);
|
||||
}
|
||||
}
|
||||
|
||||
{{template "media.partial.css" }}
|
||||
|
|
|
@ -373,9 +373,6 @@ button {
|
|||
.topic_list .topic_inner_right {
|
||||
display: none;
|
||||
}
|
||||
.topic_list .rowitem:last-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.topic_list .lastReplyAt {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -598,6 +595,27 @@ button.username {
|
|||
top: -2px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
border: 1px solid #ccc;
|
||||
margin-top: 12px;
|
||||
clear: both;
|
||||
height: 40px;
|
||||
padding: 6px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.footer select {
|
||||
padding: 2px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
#poweredBy {
|
||||
float: left;
|
||||
margin-top: 4px;
|
||||
}
|
||||
#poweredBy span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#profile_comments .rowitem {
|
||||
background-repeat: no-repeat, repeat-y;
|
||||
background-size: 128px;
|
||||
|
|
6
topic.go
6
topic.go
|
@ -1,3 +1,9 @@
|
|||
/*
|
||||
*
|
||||
* Gosora Topic File
|
||||
* Copyright Azareal 2017 - 2018
|
||||
*
|
||||
*/
|
||||
package main
|
||||
|
||||
//import "fmt"
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
/*
|
||||
*
|
||||
* Gosora Topic Store
|
||||
* Copyright Azareal 2017 - 2018
|
||||
*
|
||||
*/
|
||||
package main
|
||||
|
||||
import "log"
|
||||
|
@ -32,6 +38,7 @@ type MemoryTopicStore struct {
|
|||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMemoryTopicStore gives you a new instance of MemoryTopicStore
|
||||
func NewMemoryTopicStore(capacity int) *MemoryTopicStore {
|
||||
stmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
|
||||
if err != nil {
|
||||
|
|
8
user.go
8
user.go
|
@ -1,3 +1,9 @@
|
|||
/*
|
||||
*
|
||||
* Gosora User File
|
||||
* Copyright Azareal 2017 - 2018
|
||||
*
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -141,7 +147,7 @@ func SendValidationEmail(username string, email string, token string) bool {
|
|||
|
||||
// TODO: Move these to the phrase system
|
||||
subject := "Validate Your Email @ " + site.Name
|
||||
msg := "Dear " + username + ", following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. " + schema + "://" + site.Url + "/user/edit/token/" + token + "\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused."
|
||||
msg := "Dear " + username + ", following your registration on our forums, we ask you to validate your email, so that we can confirm that this email actually belongs to you.\n\nClick on the following link to do so. " + schema + "://" + site.URL + "/user/edit/token/" + token + "\n\nIf you haven't created an account here, then please feel free to ignore this email.\nWe're sorry for the inconvenience this may have caused."
|
||||
return SendEmail(email, subject, msg)
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ type MemoryUserStore struct {
|
|||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewMemoryUserStore gives you a new instance of MemoryUserStore
|
||||
func NewMemoryUserStore(capacity int) *MemoryUserStore {
|
||||
getStmt, err := qgen.Builder.SimpleSelect("users", "name, group, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, last_ip, temp_group", "uid = ?", "", "")
|
||||
if err != nil {
|
||||
|
|
6
utils.go
6
utils.go
|
@ -1,3 +1,9 @@
|
|||
/*
|
||||
*
|
||||
* Utility Functions And Stuff
|
||||
* Copyright Azareal 2017 - 2018
|
||||
*
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
// +build !no_ws
|
||||
|
||||
/*
|
||||
*
|
||||
* Gosora WebSocket Subsystem
|
||||
* Copyright Azareal 2017 - 2018
|
||||
*
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -17,44 +23,46 @@ import (
|
|||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type WS_User struct {
|
||||
type WSUser struct {
|
||||
conn *websocket.Conn
|
||||
User *User
|
||||
}
|
||||
|
||||
type WS_Hub struct {
|
||||
onlineUsers map[int]*WS_User
|
||||
onlineGuests map[*WS_User]bool
|
||||
type WSHub struct {
|
||||
onlineUsers map[int]*WSUser
|
||||
onlineGuests map[*WSUser]bool
|
||||
guests sync.RWMutex
|
||||
users sync.RWMutex
|
||||
}
|
||||
|
||||
var wsHub WS_Hub
|
||||
// TODO: Disable WebSockets on high load? Add a Control Panel interface for disabling it?
|
||||
var enableWebsockets = true // Put this in caps for consistency with the other constants?
|
||||
|
||||
var wsHub WSHub
|
||||
var wsUpgrader = websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}
|
||||
var errWsNouser = errors.New("This user isn't connected via WebSockets")
|
||||
|
||||
func init() {
|
||||
enableWebsockets = true
|
||||
adminStatsWatchers = make(map[*WS_User]bool)
|
||||
wsHub = WS_Hub{
|
||||
onlineUsers: make(map[int]*WS_User),
|
||||
onlineGuests: make(map[*WS_User]bool),
|
||||
adminStatsWatchers = make(map[*WSUser]bool)
|
||||
wsHub = WSHub{
|
||||
onlineUsers: make(map[int]*WSUser),
|
||||
onlineGuests: make(map[*WSUser]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) guestCount() int {
|
||||
func (hub *WSHub) guestCount() int {
|
||||
defer hub.guests.RUnlock()
|
||||
hub.guests.RLock()
|
||||
return len(hub.onlineGuests)
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) userCount() int {
|
||||
func (hub *WSHub) userCount() int {
|
||||
defer hub.users.RUnlock()
|
||||
hub.users.RLock()
|
||||
return len(hub.onlineUsers)
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) broadcastMessage(msg string) error {
|
||||
func (hub *WSHub) broadcastMessage(msg string) error {
|
||||
hub.users.RLock()
|
||||
for _, wsUser := range hub.onlineUsers {
|
||||
w, err := wsUser.conn.NextWriter(websocket.TextMessage)
|
||||
|
@ -67,7 +75,7 @@ func (hub *WS_Hub) broadcastMessage(msg string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) pushMessage(targetUser int, msg string) error {
|
||||
func (hub *WSHub) pushMessage(targetUser int, msg string) error {
|
||||
hub.users.RLock()
|
||||
wsUser, ok := hub.onlineUsers[targetUser]
|
||||
hub.users.RUnlock()
|
||||
|
@ -85,7 +93,7 @@ func (hub *WS_Hub) pushMessage(targetUser int, msg string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) pushAlert(targetUser int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
|
||||
func (hub *WSHub) pushAlert(targetUser int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
|
||||
//log.Print("In pushAlert")
|
||||
hub.users.RLock()
|
||||
wsUser, ok := hub.onlineUsers[targetUser]
|
||||
|
@ -111,9 +119,9 @@ func (hub *WS_Hub) pushAlert(targetUser int, asid int, event string, elementType
|
|||
return nil
|
||||
}
|
||||
|
||||
func (hub *WS_Hub) pushAlerts(users []int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
|
||||
func (hub *WSHub) pushAlerts(users []int, asid int, event string, elementType string, actorID int, targetUserID int, elementID int) error {
|
||||
//log.Print("In pushAlerts")
|
||||
var wsUsers []*WS_User
|
||||
var wsUsers []*WSUser
|
||||
hub.users.RLock()
|
||||
// We don't want to keep a lock on this for too long, so we'll accept some nil pointers
|
||||
for _, uid := range users {
|
||||
|
@ -165,7 +173,7 @@ func routeWebsockets(w http.ResponseWriter, r *http.Request, user User) {
|
|||
return
|
||||
}
|
||||
|
||||
wsUser := &WS_User{conn, userptr}
|
||||
wsUser := &WSUser{conn, userptr}
|
||||
if user.ID == 0 {
|
||||
wsHub.guests.Lock()
|
||||
wsHub.onlineGuests[wsUser] = true
|
||||
|
@ -224,7 +232,7 @@ func routeWebsockets(w http.ResponseWriter, r *http.Request, user User) {
|
|||
conn.Close()
|
||||
}
|
||||
|
||||
func wsPageResponses(wsUser *WS_User, page []byte) {
|
||||
func wsPageResponses(wsUser *WSUser, page []byte) {
|
||||
switch string(page) {
|
||||
case "/panel/":
|
||||
//log.Print("/panel/ WS Route")
|
||||
|
@ -255,7 +263,7 @@ func wsPageResponses(wsUser *WS_User, page []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
func wsLeavePage(wsUser *WS_User, page []byte) {
|
||||
func wsLeavePage(wsUser *WSUser, page []byte) {
|
||||
switch string(page) {
|
||||
case "/panel/":
|
||||
adminStatsMutex.Lock()
|
||||
|
@ -264,7 +272,7 @@ func wsLeavePage(wsUser *WS_User, page []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
var adminStatsWatchers map[*WS_User]bool
|
||||
var adminStatsWatchers map[*WSUser]bool
|
||||
var adminStatsMutex sync.RWMutex
|
||||
|
||||
func adminStatsTicker() {
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package main
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
type WordFilter struct {
|
||||
ID int
|
||||
Find string
|
||||
Replacement string
|
||||
}
|
||||
type WordFilterBox map[int]WordFilter
|
||||
|
||||
var wordFilterBox atomic.Value // An atomic value holding a WordFilterBox
|
||||
|
||||
func init() {
|
||||
wordFilterBox.Store(WordFilterBox(make(map[int]WordFilter)))
|
||||
}
|
||||
|
||||
func LoadWordFilters() error {
|
||||
rows, err := get_word_filters_stmt.Query()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var wordFilters = WordFilterBox(make(map[int]WordFilter))
|
||||
var wfid int
|
||||
var find string
|
||||
var replacement string
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&wfid, &find, &replacement)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wordFilters[wfid] = WordFilter{ID: wfid, Find: find, Replacement: replacement}
|
||||
}
|
||||
wordFilterBox.Store(wordFilters)
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
func addWordFilter(id int, find string, replacement string) {
|
||||
wordFilters := wordFilterBox.Load().(WordFilterBox)
|
||||
wordFilters[id] = WordFilter{ID: id, Find: find, Replacement: replacement}
|
||||
wordFilterBox.Store(wordFilters)
|
||||
}
|
Loading…
Reference in New Issue