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)
|
![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.
|
More images in the /images/ folder. Beware though, some of them are *really* outdated.
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
* Go 1.8
|
* Go 1.9
|
||||||
|
|
||||||
* MariaDB (or any other MySQL compatible database engine). We'll allow other database engines in the future.
|
* 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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -15,6 +20,7 @@ var forumCreateMutex sync.Mutex
|
||||||
var forumPerms map[int]map[int]ForumPerms // [gid][fid]Perms
|
var forumPerms map[int]map[int]ForumPerms // [gid][fid]Perms
|
||||||
var fstore ForumStore
|
var fstore ForumStore
|
||||||
|
|
||||||
|
// ForumStore is an interface for accessing the forums and the metadata stored on them
|
||||||
type ForumStore interface {
|
type ForumStore interface {
|
||||||
LoadForums() error
|
LoadForums() error
|
||||||
DirtyGet(id int) *Forum
|
DirtyGet(id int) *Forum
|
||||||
|
@ -43,9 +49,9 @@ type ForumStore interface {
|
||||||
GetGlobalCount() int
|
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 {
|
type MemoryForumStore struct {
|
||||||
//forums map[int]*Forum
|
forums sync.Map // map[int]*Forum
|
||||||
forums sync.Map
|
|
||||||
forumView atomic.Value // []*Forum
|
forumView atomic.Value // []*Forum
|
||||||
//fids []int
|
//fids []int
|
||||||
forumCount int
|
forumCount int
|
||||||
|
@ -56,6 +62,7 @@ type MemoryForumStore struct {
|
||||||
getForumCount *sql.Stmt
|
getForumCount *sql.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMemoryForumStore gives you a new instance of MemoryForumStore
|
||||||
func NewMemoryForumStore() *MemoryForumStore {
|
func NewMemoryForumStore() *MemoryForumStore {
|
||||||
getStmt, err := qgen.Builder.SimpleSelect("forums", "name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "fid = ?", "", "")
|
getStmt, err := qgen.Builder.SimpleSelect("forums", "name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "fid = ?", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -81,6 +88,7 @@ func NewMemoryForumStore() *MemoryForumStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add support for subforums
|
||||||
func (mfs *MemoryForumStore) LoadForums() error {
|
func (mfs *MemoryForumStore) LoadForums() error {
|
||||||
log.Print("Adding the uncategorised forum")
|
log.Print("Adding the uncategorised forum")
|
||||||
forumUpdateMutex.Lock()
|
forumUpdateMutex.Lock()
|
||||||
|
@ -89,7 +97,7 @@ func (mfs *MemoryForumStore) LoadForums() error {
|
||||||
var forumView []*Forum
|
var forumView []*Forum
|
||||||
addForum := func(forum *Forum) {
|
addForum := func(forum *Forum) {
|
||||||
mfs.forums.Store(forum.ID, forum)
|
mfs.forums.Store(forum.ID, forum)
|
||||||
if forum.Active && forum.Name != "" {
|
if forum.Active && forum.Name != "" && forum.ParentType == "" {
|
||||||
forumView = append(forumView, forum)
|
forumView = append(forumView, forum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +140,8 @@ func (mfs *MemoryForumStore) rebuildView() {
|
||||||
var forumView []*Forum
|
var forumView []*Forum
|
||||||
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
|
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
|
||||||
forum := value.(*Forum)
|
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)
|
forumView = append(forumView, forum)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -124,7 +124,7 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
|
||||||
|
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
topicW.Body.Reset()
|
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))
|
topicReq := httptest.NewRequest("get", "/topic/1", bytes.NewReader(nil))
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
topicW.Body.Reset()
|
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() {
|
func init() {
|
||||||
// Site Info
|
// Site Info
|
||||||
site.Name = "` + siteName + `" // Should be a setting in the database
|
site.Name = "` + siteName + `"
|
||||||
site.Email = "" // Should be a setting in the database
|
site.Email = ""
|
||||||
site.Url = "` + siteURL + `"
|
site.URL = "` + siteURL + `"
|
||||||
site.Port = "` + serverPort + `"
|
site.Port = "` + serverPort + `"
|
||||||
site.EnableSsl = false
|
site.EnableSsl = false
|
||||||
site.EnableEmails = false
|
site.EnableEmails = false
|
||||||
site.HasProxy = false // Cloudflare counts as this, if it's sitting in the middle
|
site.HasProxy = false // Cloudflare counts as this, if it's sitting in the middle
|
||||||
config.SslPrivkey = ""
|
config.SslPrivkey = ""
|
||||||
config.SslFullchain = ""
|
config.SslFullchain = ""
|
||||||
|
site.Language = "english"
|
||||||
|
|
||||||
// Database details
|
// Database details
|
||||||
db_config.Host = "` + dbHost + `"
|
dbConfig.Host = "` + dbHost + `"
|
||||||
db_config.Username = "` + dbUsername + `"
|
dbConfig.Username = "` + dbUsername + `"
|
||||||
db_config.Password = "` + dbPassword + `"
|
dbConfig.Password = "` + dbPassword + `"
|
||||||
db_config.Dbname = "` + dbName + `"
|
dbConfig.Dbname = "` + dbName + `"
|
||||||
db_config.Port = "` + dbPort + `" // You probably won't need to change this
|
dbConfig.Port = "` + dbPort + `" // You probably won't need to change this
|
||||||
|
|
||||||
// Limiters
|
// Limiters
|
||||||
config.MaxRequestSize = 5 * megabyte
|
config.MaxRequestSize = 5 * megabyte
|
||||||
|
|
|
@ -53,7 +53,7 @@ func _initMysql() (err error) {
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
fmt.Println("Unable to find the database. Attempting to create it")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,5 +39,9 @@
|
||||||
},
|
},
|
||||||
"SettingLabels": {
|
"SettingLabels": {
|
||||||
"activation_type": "Activate All,Email Activation,Admin Approval"
|
"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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -8,7 +13,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
//"runtime/pprof"
|
//"runtime/pprof"
|
||||||
)
|
)
|
||||||
|
@ -27,8 +31,6 @@ const terabyte int = gigabyte * 1024
|
||||||
const saltLength int = 32
|
const saltLength int = 32
|
||||||
const sessionLength int = 80
|
const sessionLength int = 80
|
||||||
|
|
||||||
var enableWebsockets = false // Don't change this, the value is overwritten by an initialiser
|
|
||||||
|
|
||||||
var router *GenRouter
|
var router *GenRouter
|
||||||
var startTime time.Time
|
var startTime time.Time
|
||||||
|
|
||||||
|
@ -41,55 +43,13 @@ var groupCapCount int
|
||||||
var staticFiles = make(map[string]SFile)
|
var staticFiles = make(map[string]SFile)
|
||||||
var logWriter = io.MultiWriter(os.Stderr)
|
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() {
|
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" {
|
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 = strings.TrimSuffix(site.URL, "\\")
|
||||||
site.Url = strings.TrimSuffix(site.Url, ":")
|
site.URL = strings.TrimSuffix(site.URL, ":")
|
||||||
site.Url = site.Url + ":" + site.Port
|
site.URL = site.URL + ":" + site.Port
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,7 @@ func TestForumStore(t *testing.T) {
|
||||||
|
|
||||||
func TestSlugs(t *testing.T) {
|
func TestSlugs(t *testing.T) {
|
||||||
var res string
|
var res string
|
||||||
var msgList []ME_Pair
|
var msgList []MEPair
|
||||||
|
|
||||||
msgList = addMEPair(msgList, "Unknown", "unknown")
|
msgList = addMEPair(msgList, "Unknown", "unknown")
|
||||||
msgList = addMEPair(msgList, "Unknown2", "unknown2")
|
msgList = addMEPair(msgList, "Unknown2", "unknown2")
|
||||||
|
|
6
mysql.go
6
mysql.go
|
@ -27,12 +27,12 @@ func init() {
|
||||||
|
|
||||||
func _initDatabase() (err error) {
|
func _initDatabase() (err error) {
|
||||||
var _dbpassword string
|
var _dbpassword string
|
||||||
if db_config.Password != "" {
|
if dbConfig.Password != "" {
|
||||||
_dbpassword = ":" + db_config.Password
|
_dbpassword = ":" + dbConfig.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the database connection
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,33 +5,36 @@ package main
|
||||||
import "errors"
|
import "errors"
|
||||||
import "net/http"
|
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")
|
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
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ *WS_Hub) userCount() int {
|
func (_ *WSHub) userCount() int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hub *WS_Hub) broadcastMessage(_ string) error {
|
func (hub *WSHub) broadcastMessage(_ string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hub *WS_Hub) pushMessage(_ int, _ string) error {
|
func (hub *WSHub) pushMessage(_ int, _ string) error {
|
||||||
return errWsNouser
|
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
|
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
|
return errWsNouser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
pages.go
4
pages.go
|
@ -207,10 +207,10 @@ type PanelEditForumPage struct {
|
||||||
Groups []GroupForumPermPreset
|
Groups []GroupForumPermPreset
|
||||||
}
|
}
|
||||||
|
|
||||||
type NameLangPair struct {
|
/*type NameLangPair struct {
|
||||||
Name string
|
Name string
|
||||||
LangStr string
|
LangStr string
|
||||||
}
|
}*/
|
||||||
|
|
||||||
type NameLangToggle struct {
|
type NameLangToggle struct {
|
||||||
Name string
|
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?
|
// 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
|
// nolint Be quiet megacheck, this *is* used
|
||||||
var currentLanguage = "english"
|
|
||||||
var currentLangPack atomic.Value
|
var currentLangPack atomic.Value
|
||||||
var langpackCount int // TODO: Use atomics for this
|
var langpackCount int // TODO: Use atomics for this
|
||||||
|
|
||||||
|
@ -40,6 +39,7 @@ type LanguagePack struct {
|
||||||
GlobalPerms map[string]string
|
GlobalPerms map[string]string
|
||||||
LocalPerms map[string]string
|
LocalPerms map[string]string
|
||||||
SettingLabels 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
|
// 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")
|
return errors.New("You don't have any language packs")
|
||||||
}
|
}
|
||||||
|
|
||||||
langPack, ok := langpacks.Load(currentLanguage)
|
langPack, ok := langpacks.Load(site.Language)
|
||||||
if !ok {
|
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)
|
currentLangPack.Store(langPack)
|
||||||
return nil
|
return nil
|
||||||
|
@ -139,6 +139,14 @@ func GetAllSettingLabels() map[string]string {
|
||||||
return currentLangPack.Load().(*LanguagePack).SettingLabels
|
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?
|
// ? - Use runtime reflection for updating phrases?
|
||||||
// TODO: Implement these
|
// TODO: Implement these
|
||||||
func AddPhrase() {
|
func AddPhrase() {
|
||||||
|
|
|
@ -3,22 +3,21 @@ package main
|
||||||
import "strconv"
|
import "strconv"
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
// TODO: Replace the soft tabs with hard ones
|
|
||||||
// go test -v
|
// go test -v
|
||||||
|
|
||||||
type ME_Pair struct {
|
type MEPair struct {
|
||||||
Msg string
|
Msg string
|
||||||
Expects string
|
Expects string
|
||||||
}
|
}
|
||||||
|
|
||||||
func addMEPair(msgList []ME_Pair, msg string, expects string) []ME_Pair {
|
func addMEPair(msgList []MEPair, msg string, expects string) []MEPair {
|
||||||
return append(msgList, ME_Pair{msg, expects})
|
return append(msgList, MEPair{msg, expects})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBBCodeRender(t *testing.T) {
|
func TestBBCodeRender(t *testing.T) {
|
||||||
//t.Skip()
|
//t.Skip()
|
||||||
var res string
|
var res string
|
||||||
var msgList []ME_Pair
|
var msgList []MEPair
|
||||||
msgList = addMEPair(msgList, "hi", "hi")
|
msgList = addMEPair(msgList, "hi", "hi")
|
||||||
msgList = addMEPair(msgList, "😀", "😀")
|
msgList = addMEPair(msgList, "😀", "😀")
|
||||||
msgList = addMEPair(msgList, "[b]😀[/b]", "<b>😀</b>")
|
msgList = addMEPair(msgList, "[b]😀[/b]", "<b>😀</b>")
|
||||||
|
@ -190,7 +189,7 @@ func TestBBCodeRender(t *testing.T) {
|
||||||
func TestMarkdownRender(t *testing.T) {
|
func TestMarkdownRender(t *testing.T) {
|
||||||
//t.Skip()
|
//t.Skip()
|
||||||
var res string
|
var res string
|
||||||
var msgList []ME_Pair
|
var msgList []MEPair
|
||||||
msgList = addMEPair(msgList, "hi", "hi")
|
msgList = addMEPair(msgList, "hi", "hi")
|
||||||
msgList = addMEPair(msgList, "**hi**", "<b>hi</b>")
|
msgList = addMEPair(msgList, "**hi**", "<b>hi</b>")
|
||||||
msgList = addMEPair(msgList, "_hi_", "<u>hi</u>")
|
msgList = addMEPair(msgList, "_hi_", "<u>hi</u>")
|
||||||
|
|
21
site.go
21
site.go
|
@ -2,24 +2,23 @@ package main
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
// TODO: Add a langPack configuration item or setting
|
var site = &Site{Name: "Magical Fairy Land", Language: "english"}
|
||||||
|
var dbConfig = DBConfig{Host: "localhost"}
|
||||||
var site = &Site{Name: "Magical Fairy Land"}
|
|
||||||
var db_config = DB_Config{Host: "localhost"}
|
|
||||||
var config Config
|
var config Config
|
||||||
var dev DevConfig
|
var dev DevConfig
|
||||||
|
|
||||||
type Site struct {
|
type Site struct {
|
||||||
Name string
|
Name string // ? - Move this into the settings table?
|
||||||
Email string
|
Email string // ? - Move this into the settings table?
|
||||||
Url string
|
URL string
|
||||||
Port string
|
Port string
|
||||||
EnableSsl bool
|
EnableSsl bool
|
||||||
EnableEmails bool
|
EnableEmails bool
|
||||||
HasProxy bool
|
HasProxy bool
|
||||||
|
Language string // ? - Move this into the settings table?
|
||||||
}
|
}
|
||||||
|
|
||||||
type DB_Config struct {
|
type DBConfig struct {
|
||||||
Host string
|
Host string
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
|
@ -44,13 +43,13 @@ type Config struct {
|
||||||
DefaultRoute func(http.ResponseWriter, *http.Request, User)
|
DefaultRoute func(http.ResponseWriter, *http.Request, User)
|
||||||
DefaultGroup int
|
DefaultGroup int
|
||||||
ActivationGroup int
|
ActivationGroup int
|
||||||
StaffCss string
|
StaffCss string // ? - Move this into the settings table? Might be better to implement this as Group CSS
|
||||||
UncategorisedForumVisible bool
|
UncategorisedForumVisible bool
|
||||||
MinifyTemplates bool
|
MinifyTemplates bool
|
||||||
MultiServer bool
|
MultiServer bool
|
||||||
|
|
||||||
Noavatar string
|
Noavatar string // ? - Move this into the settings table?
|
||||||
ItemsPerPage int
|
ItemsPerPage int // ? - Move this into the settings table?
|
||||||
}
|
}
|
||||||
|
|
||||||
type DevConfig struct {
|
type DevConfig struct {
|
||||||
|
|
6
tasks.go
6
tasks.go
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Gosora Task System
|
||||||
|
* Copyright Azareal 2017 - 2018
|
||||||
|
*
|
||||||
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
|
@ -246,7 +246,7 @@ var topic_91 = []byte(`
|
||||||
|
|
||||||
`)
|
`)
|
||||||
var footer_0 = []byte(`<div class="footer">
|
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">
|
<form action="/theme/" method="post">
|
||||||
<div id="themeSelector" style="float: right;">
|
<div id="themeSelector" style="float: right;">
|
||||||
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">
|
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">
|
||||||
|
@ -312,10 +312,8 @@ var topic_alt_19 = []byte(`
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stop inling this x.x -->
|
<div class="rowblock post_container">
|
||||||
<style type="text/css">.rowitem:last-child .content_container { margin-bottom: 5px !important; }</style>
|
<article class="rowitem passive deletable_block editable_parent post_item top_post">
|
||||||
<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="userinfo">
|
<div class="userinfo">
|
||||||
<div class="avatar_item" style="background-image: url(`)
|
<div class="avatar_item" style="background-image: url(`)
|
||||||
var topic_alt_20 = []byte(`), url(/static/white-dot.jpg);background-position: 0px -10px;"> </div>
|
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_85 = []byte(`</div>
|
||||||
`)
|
`)
|
||||||
var topic_alt_86 = []byte(`
|
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">
|
<form action="/reply/create/" method="post">
|
||||||
<input name="tid" value='`)
|
<input name="tid" value='`)
|
||||||
var topic_alt_87 = []byte(`' type="hidden" />
|
var topic_alt_87 = []byte(`' type="hidden" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="footer">
|
<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">
|
<form action="/theme/" method="post">
|
||||||
<div id="themeSelector" style="float: right;">
|
<div id="themeSelector" style="float: right;">
|
||||||
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">
|
<select id="themeSelectorSelect" name="themeSelector" aria-label="Change the site's appearance">
|
||||||
|
|
|
@ -22,10 +22,8 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stop inling this x.x -->
|
<div class="rowblock post_container">
|
||||||
<style type="text/css">.rowitem:last-child .content_container { margin-bottom: 5px !important; }</style>
|
<article class="rowitem passive deletable_block editable_parent post_item top_post">
|
||||||
<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="userinfo">
|
<div class="userinfo">
|
||||||
<div class="avatar_item" style="background-image: url({{.Topic.Avatar}}), url(/static/white-dot.jpg);background-position: 0px -10px;"> </div>
|
<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>
|
<a href="{{.Topic.UserLink}}" class="the_name">{{.Topic.CreatedByName}}</a>
|
||||||
|
@ -79,7 +77,7 @@
|
||||||
</article>
|
</article>
|
||||||
{{end}}</div>
|
{{end}}</div>
|
||||||
{{if .CurrentUser.Perms.CreateReply}}
|
{{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">
|
<form action="/reply/create/" method="post">
|
||||||
<input name="tid" value='{{.Topic.ID}}' type="hidden" />
|
<input name="tid" value='{{.Topic.ID}}' type="hidden" />
|
||||||
<div class="formrow">
|
<div class="formrow">
|
||||||
|
|
|
@ -460,6 +460,13 @@ input, select, textarea {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
#poweredBy {
|
||||||
|
float: left;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
#poweredBy span {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Forum View */
|
/* Forum View */
|
||||||
.rowhead {
|
.rowhead {
|
||||||
|
|
|
@ -7,21 +7,29 @@
|
||||||
body {
|
body {
|
||||||
font-family: arial;
|
font-family: arial;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
|
/* TODO: Redo the background */
|
||||||
background-image: url('/static/test_bg2.svg');
|
background-image: url('/static/test_bg2.svg');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Patch for Edge, until they fix emojis in arial x.x */
|
/* 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 {
|
ul {
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid hsl(0,0%,80%);
|
||||||
background-color: white;
|
background: hsl(0, 0%, 97%);
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
margin-top: 0px;
|
||||||
|
border-bottom: 1.5px inset hsl(0,0%,80%);
|
||||||
|
margin-left: -8px;
|
||||||
|
margin-right: -8px;
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
height: 35px;
|
height: 35px;
|
||||||
|
@ -29,7 +37,9 @@ li {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
li:hover { background: rgb(250,250,250); }
|
li:hover {
|
||||||
|
background: rgb(250,250,250);
|
||||||
|
}
|
||||||
li a {
|
li a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
/*color: #515151;*/
|
/*color: #515151;*/
|
||||||
|
@ -38,12 +48,14 @@ li a {
|
||||||
}
|
}
|
||||||
.menu_left {
|
.menu_left {
|
||||||
float: 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;
|
padding-right: 10px;
|
||||||
|
background: hsl(0, 0%, 98%);
|
||||||
}
|
}
|
||||||
.menu_right {
|
.menu_right {
|
||||||
float: right;
|
float: right;
|
||||||
border-left: 1px solid #ccc;
|
border-left: 1px solid hsl(0,0%,80%);
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,8 +97,9 @@ li a {
|
||||||
color: black;
|
color: black;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.selectedAlert .alert_counter { display: none; }
|
.selectedAlert .alert_counter, .menu_alerts .alertList {
|
||||||
.menu_alerts .alertList { display: none; }
|
display: none;
|
||||||
|
}
|
||||||
.selectedAlert .alertList {
|
.selectedAlert .alertList {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 51px;
|
top: 51px;
|
||||||
|
@ -96,10 +109,10 @@ li a {
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
right: calc(5% + 7px);
|
right: calc(5% + 7px);
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid hsl(0,0%,80%);
|
||||||
border-left: 1px solid #ccc;
|
border-left: 1px solid hsl(0,0%,80%);
|
||||||
border-right: 1px solid #ccc;
|
border-right: 1px solid hsl(0,0%,80%);
|
||||||
border-bottom: 1px solid #ccc;
|
border-bottom: 1px solid hsl(0,0%,80%);
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
.alertItem {
|
.alertItem {
|
||||||
|
@ -136,19 +149,47 @@ li a {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: 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 {
|
.rowblock {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid hsl(0,0%,80%);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
padding-top: 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 {
|
.rowsmall {
|
||||||
font-size:12px;
|
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 {
|
.rowlist {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
@ -162,25 +203,6 @@ li a {
|
||||||
padding-left: 48px;
|
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 {
|
.colstack_left {
|
||||||
float: left;
|
float: left;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
|
@ -192,7 +214,7 @@ li a {
|
||||||
width: calc(70% - 15px);
|
width: calc(70% - 15px);
|
||||||
}
|
}
|
||||||
.colstack_item {
|
.colstack_item {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid hsl(0,0%,80%);
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -211,7 +233,7 @@ li a {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
.grid_item {
|
.grid_item {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid hsl(0,0%,80%);
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -223,21 +245,33 @@ li a {
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
.grid_istat { margin-bottom: 5px; }
|
.grid_istat {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.stat_green { background-color: lightgreen; border-color: green; }
|
.stat_green {
|
||||||
.stat_orange { background-color: #ffe4b3; border-color: orange; }
|
background-color: lightgreen;
|
||||||
.stat_red { background-color: #ffb2b2; border-color: red; }
|
border-color: green;
|
||||||
.stat_disabled { background-color: lightgray; border-color: gray; }
|
}
|
||||||
|
.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 {
|
.rowitem {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
/*padding-left: 8px;
|
/*padding-left: 8px;
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
padding-top: 17px;
|
padding-top: 17px;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;*/
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;*/
|
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-top: 14px;
|
padding-top: 14px;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
|
@ -249,13 +283,16 @@ li a {
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
}
|
}
|
||||||
.rowitem:not(:last-child) {
|
.rowitem:not(:last-child) {
|
||||||
border-bottom: 1px dotted #ccc;
|
border-bottom: 1px solid hsl(0,0%,85%);
|
||||||
}
|
}
|
||||||
.rowitem a {
|
.rowitem a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
.rowitem a:hover { color: silver; }
|
.rowitem a:hover {
|
||||||
|
color: hsl(0,0%,40%);
|
||||||
|
}
|
||||||
|
|
||||||
.opthead { display: none; }
|
.opthead { display: none; }
|
||||||
.datarow {
|
.datarow {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
|
@ -272,8 +309,12 @@ li a {
|
||||||
content: " ";
|
content: " ";
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
.formrow:after { clear: both; }
|
.formrow:after {
|
||||||
.formrow:not(:last-child) { border-bottom: 1px dotted #ccc; }
|
clear: both;
|
||||||
|
}
|
||||||
|
.formrow:not(:last-child) {
|
||||||
|
border-bottom: 1px dotted hsl(0,0%,80%);
|
||||||
|
}
|
||||||
|
|
||||||
.formitem {
|
.formitem {
|
||||||
float: left;
|
float: left;
|
||||||
|
@ -282,8 +323,12 @@ li a {
|
||||||
/*font-size: 17px;*/
|
/*font-size: 17px;*/
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
.formitem:not(:last-child) { border-right: 1px dotted #ccc; }
|
.formitem:not(:last-child) {
|
||||||
.formitem.invisible_border { border: none; }
|
border-right: 1px dotted hsl(0,0%,80%);
|
||||||
|
}
|
||||||
|
.formitem.invisible_border {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mostly for textareas */
|
/* Mostly for textareas */
|
||||||
.formitem:only-child { width: 100%; }
|
.formitem:only-child { width: 100%; }
|
||||||
|
@ -296,7 +341,9 @@ li a {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
float: none;
|
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 {
|
.formitem:not(:only-child).formlabel {
|
||||||
padding-top: 15px;/*18px;*/
|
padding-top: 15px;/*18px;*/
|
||||||
padding-bottom: 12px;/*16px;*/
|
padding-bottom: 12px;/*16px;*/
|
||||||
|
@ -308,7 +355,7 @@ li a {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border-color: #ccc;
|
border-color: hsl(0,0%,80%);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
@ -318,6 +365,33 @@ button {
|
||||||
|
|
||||||
/* Topics */
|
/* 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 {
|
.topic_sticky {
|
||||||
background-color: rgb(255,255,234);
|
background-color: rgb(255,255,234);
|
||||||
}
|
}
|
||||||
|
@ -338,8 +412,22 @@ button {
|
||||||
}
|
}
|
||||||
.topic_status:empty { display: none; }
|
.topic_status:empty { display: none; }
|
||||||
|
|
||||||
|
.rowhead, .colstack_head {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
.rowhead .rowitem, .opthead .rowitem, .colstack_head .rowitem {
|
.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 {
|
.topic_sticky_head {
|
||||||
background-color: #FFFFEA;
|
background-color: #FFFFEA;
|
||||||
|
@ -360,7 +448,7 @@ button {
|
||||||
color: #505050; /* 80,80,80 */
|
color: #505050; /* 80,80,80 */
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: #ccc;
|
border-color: hsl(0,0%,80%);
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
@ -396,7 +484,7 @@ button.username {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid hsl(0,0%,80%);
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
.alert_success {
|
.alert_success {
|
||||||
|
@ -415,16 +503,19 @@ button.username {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tempra Conflux */
|
/* Tempra Conflux */
|
||||||
|
|
||||||
.user_content {
|
.user_content {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
margin-top: 3px;
|
margin-top: 2px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
background: white;
|
background: white;
|
||||||
min-height: 145px;
|
min-height: 145px;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.user_content.nobuttons { min-height: 168px; }
|
.user_content.nobuttons {
|
||||||
|
min-height: 168px;
|
||||||
|
}
|
||||||
|
|
||||||
.button_container {
|
.button_container {
|
||||||
border-top: solid 1px #eaeaea;
|
border-top: solid 1px #eaeaea;
|
||||||
|
@ -515,7 +606,8 @@ button.username {
|
||||||
float: left;
|
float: left;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 4px;
|
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 {
|
.userinfo .avatar_item {
|
||||||
background-repeat: no-repeat, repeat-y;
|
background-repeat: no-repeat, repeat-y;
|
||||||
|
@ -534,7 +626,8 @@ button.username {
|
||||||
min-height: 128px;
|
min-height: 128px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
margin-right: 3px;
|
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; }
|
.action_item .userinfo { display: none; }
|
||||||
|
@ -551,7 +644,7 @@ button.username {
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: #ccc;
|
border-color: hsl(0,0%,80%);
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
|
@ -574,4 +667,62 @@ button.username {
|
||||||
top: -2px;
|
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" }}
|
{{template "media.partial.css" }}
|
||||||
|
|
|
@ -373,9 +373,6 @@ button {
|
||||||
.topic_list .topic_inner_right {
|
.topic_list .topic_inner_right {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.topic_list .rowitem:last-child {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.topic_list .lastReplyAt {
|
.topic_list .lastReplyAt {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
@ -598,6 +595,27 @@ button.username {
|
||||||
top: -2px;
|
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 {
|
#profile_comments .rowitem {
|
||||||
background-repeat: no-repeat, repeat-y;
|
background-repeat: no-repeat, repeat-y;
|
||||||
background-size: 128px;
|
background-size: 128px;
|
||||||
|
|
6
topic.go
6
topic.go
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Gosora Topic File
|
||||||
|
* Copyright Azareal 2017 - 2018
|
||||||
|
*
|
||||||
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//import "fmt"
|
//import "fmt"
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Gosora Topic Store
|
||||||
|
* Copyright Azareal 2017 - 2018
|
||||||
|
*
|
||||||
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "log"
|
import "log"
|
||||||
|
@ -32,6 +38,7 @@ type MemoryTopicStore struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMemoryTopicStore gives you a new instance of MemoryTopicStore
|
||||||
func NewMemoryTopicStore(capacity int) *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 = ?", "", "")
|
stmt, err := qgen.Builder.SimpleSelect("topics", "title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount, data", "tid = ?", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
8
user.go
8
user.go
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Gosora User File
|
||||||
|
* Copyright Azareal 2017 - 2018
|
||||||
|
*
|
||||||
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -141,7 +147,7 @@ func SendValidationEmail(username string, email string, token string) bool {
|
||||||
|
|
||||||
// TODO: Move these to the phrase system
|
// TODO: Move these to the phrase system
|
||||||
subject := "Validate Your Email @ " + site.Name
|
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)
|
return SendEmail(email, subject, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ type MemoryUserStore struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMemoryUserStore gives you a new instance of MemoryUserStore
|
||||||
func NewMemoryUserStore(capacity int) *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 = ?", "", "")
|
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 {
|
if err != nil {
|
||||||
|
|
6
utils.go
6
utils.go
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Utility Functions And Stuff
|
||||||
|
* Copyright Azareal 2017 - 2018
|
||||||
|
*
|
||||||
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
// +build !no_ws
|
// +build !no_ws
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Gosora WebSocket Subsystem
|
||||||
|
* Copyright Azareal 2017 - 2018
|
||||||
|
*
|
||||||
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -17,44 +23,46 @@ import (
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WS_User struct {
|
type WSUser struct {
|
||||||
conn *websocket.Conn
|
conn *websocket.Conn
|
||||||
User *User
|
User *User
|
||||||
}
|
}
|
||||||
|
|
||||||
type WS_Hub struct {
|
type WSHub struct {
|
||||||
onlineUsers map[int]*WS_User
|
onlineUsers map[int]*WSUser
|
||||||
onlineGuests map[*WS_User]bool
|
onlineGuests map[*WSUser]bool
|
||||||
guests sync.RWMutex
|
guests sync.RWMutex
|
||||||
users 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 wsUpgrader = websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}
|
||||||
var errWsNouser = errors.New("This user isn't connected via WebSockets")
|
var errWsNouser = errors.New("This user isn't connected via WebSockets")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
enableWebsockets = true
|
adminStatsWatchers = make(map[*WSUser]bool)
|
||||||
adminStatsWatchers = make(map[*WS_User]bool)
|
wsHub = WSHub{
|
||||||
wsHub = WS_Hub{
|
onlineUsers: make(map[int]*WSUser),
|
||||||
onlineUsers: make(map[int]*WS_User),
|
onlineGuests: make(map[*WSUser]bool),
|
||||||
onlineGuests: make(map[*WS_User]bool),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hub *WS_Hub) guestCount() int {
|
func (hub *WSHub) guestCount() int {
|
||||||
defer hub.guests.RUnlock()
|
defer hub.guests.RUnlock()
|
||||||
hub.guests.RLock()
|
hub.guests.RLock()
|
||||||
return len(hub.onlineGuests)
|
return len(hub.onlineGuests)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hub *WS_Hub) userCount() int {
|
func (hub *WSHub) userCount() int {
|
||||||
defer hub.users.RUnlock()
|
defer hub.users.RUnlock()
|
||||||
hub.users.RLock()
|
hub.users.RLock()
|
||||||
return len(hub.onlineUsers)
|
return len(hub.onlineUsers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hub *WS_Hub) broadcastMessage(msg string) error {
|
func (hub *WSHub) broadcastMessage(msg string) error {
|
||||||
hub.users.RLock()
|
hub.users.RLock()
|
||||||
for _, wsUser := range hub.onlineUsers {
|
for _, wsUser := range hub.onlineUsers {
|
||||||
w, err := wsUser.conn.NextWriter(websocket.TextMessage)
|
w, err := wsUser.conn.NextWriter(websocket.TextMessage)
|
||||||
|
@ -67,7 +75,7 @@ func (hub *WS_Hub) broadcastMessage(msg string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hub *WS_Hub) pushMessage(targetUser int, msg string) error {
|
func (hub *WSHub) pushMessage(targetUser int, msg string) error {
|
||||||
hub.users.RLock()
|
hub.users.RLock()
|
||||||
wsUser, ok := hub.onlineUsers[targetUser]
|
wsUser, ok := hub.onlineUsers[targetUser]
|
||||||
hub.users.RUnlock()
|
hub.users.RUnlock()
|
||||||
|
@ -85,7 +93,7 @@ func (hub *WS_Hub) pushMessage(targetUser int, msg string) error {
|
||||||
return nil
|
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")
|
//log.Print("In pushAlert")
|
||||||
hub.users.RLock()
|
hub.users.RLock()
|
||||||
wsUser, ok := hub.onlineUsers[targetUser]
|
wsUser, ok := hub.onlineUsers[targetUser]
|
||||||
|
@ -111,9 +119,9 @@ func (hub *WS_Hub) pushAlert(targetUser int, asid int, event string, elementType
|
||||||
return nil
|
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")
|
//log.Print("In pushAlerts")
|
||||||
var wsUsers []*WS_User
|
var wsUsers []*WSUser
|
||||||
hub.users.RLock()
|
hub.users.RLock()
|
||||||
// We don't want to keep a lock on this for too long, so we'll accept some nil pointers
|
// We don't want to keep a lock on this for too long, so we'll accept some nil pointers
|
||||||
for _, uid := range users {
|
for _, uid := range users {
|
||||||
|
@ -165,7 +173,7 @@ func routeWebsockets(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wsUser := &WS_User{conn, userptr}
|
wsUser := &WSUser{conn, userptr}
|
||||||
if user.ID == 0 {
|
if user.ID == 0 {
|
||||||
wsHub.guests.Lock()
|
wsHub.guests.Lock()
|
||||||
wsHub.onlineGuests[wsUser] = true
|
wsHub.onlineGuests[wsUser] = true
|
||||||
|
@ -224,7 +232,7 @@ func routeWebsockets(w http.ResponseWriter, r *http.Request, user User) {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func wsPageResponses(wsUser *WS_User, page []byte) {
|
func wsPageResponses(wsUser *WSUser, page []byte) {
|
||||||
switch string(page) {
|
switch string(page) {
|
||||||
case "/panel/":
|
case "/panel/":
|
||||||
//log.Print("/panel/ WS Route")
|
//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) {
|
switch string(page) {
|
||||||
case "/panel/":
|
case "/panel/":
|
||||||
adminStatsMutex.Lock()
|
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
|
var adminStatsMutex sync.RWMutex
|
||||||
|
|
||||||
func adminStatsTicker() {
|
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