The Search and Filter Widget is now partly implemented. Just Search to go in the basic implementation.

Added AJAX Pagination for the Topic List and Forum Page.
A new log file pair is now created every-time Gosora starts up.
Added proper per-theme template overrides.

Added EasyJSON to make JSON serialisation faster.
Moved a bit of boilerplate into paginator.html
Improved paginator.html with a richer template with first, last and symbols instead of text.
Phased out direct access to Templates.ExecuteTemplate across the software.
Fixed the Live Topic List so it should work again.
Added MicroAvatar to WsJSONUser for topic list JSON requests.
An instance of the plugin is now passed to plugin handlers rather than having the plugins manipulate the globals directly.
Added the pre_render_panel_forum_edit and pre_render_panel_forum_edit_perms hooks to replace pre_render_panel_edit_forum.
Renamed the pre_render_panel_edit_user hook to pre_render_panel_user_edit
Reduced the amount of noise from fsnotify.
Added RawPrepare() to qgen.Accumulator.
Added a temporary phrase whitelist to the phrase endpoint.
Moved the location of the zone data assignments in the topic list to reduce the chances of security issues in the future.
Changed the signature of routes/panel/renderTemplate() requiring some changes across the panel routes.
Removed bits of boilerplate in some of the panel routes with renderTemplate()
Added a BenchmarkTopicsGuestJSRouteParallelWithRouter benchmark.
Removed a fair bit of boilerplate for each page struct by generating a couple of interface casts for each template file instead.
Added the profile_comments_row_alt template.
Added the topics_quick_topic template to reuse part of the quick topic logic for both the topic list and forum page.
Tweaked the CSS for the Online Users Widget.
Tweaked the CSS for Widgets in every theme with a sidebar.
Refactored the template initialisers to hopefully reduce the amount of boilerplate and make things easier to maintain and follow.
Add genIntTmpl in the template initialiser file to reduce the amount of boilerplate needed for the fallback template bindings.

Removed the topics_head phrase.
Moved the paginator_ phrases into the paginator. namespace and renamed them accordingly.
Added the paginator.first_page phrase.
Added the paginator.first_page_aria phrase.
Added the paginator.last_page phrase.
Added the paginator.last_page_aria phrase.
Added the panel_forum_delete_are_you_sure phrase.

Fixed a data race in LogWarning()
This commit is contained in:
Azareal 2019-02-10 15:52:26 +10:00
parent 6a8ad887cc
commit d9acf27c5b
92 changed files with 1287 additions and 871 deletions

View File

@ -14,6 +14,9 @@ go build -o QueryGen "./cmd/query_gen"
echo "Running the query generator"
./QueryGen
echo "Generating the JSON handlers"
easyjson -pkg common
echo "Building Gosora"
go generate
go build -o Gosora

View File

@ -20,6 +20,9 @@ cd ../..
echo "Running the query generator"
./QueryGen
echo "Generating the JSON handlers"
easyjson -pkg common
echo "Building Gosora"
go generate
go build -o Gosora -tags no_ws

View File

@ -12,6 +12,9 @@ if %errorlevel% neq 0 (
exit /b %errorlevel%
)
echo Generating the JSON handlers
easyjson -pkg common
echo Building the executable
go build -o gosora.exe -tags no_ws
if %errorlevel% neq 0 (

View File

@ -14,6 +14,9 @@ if %errorlevel% neq 0 (
exit /b %errorlevel%
)
echo Generating the JSON handlers
easyjson -pkg common
echo Building the executable
go build -o gosora.exe
if %errorlevel% neq 0 (

View File

@ -118,10 +118,10 @@ func LogWarning(err error, extra ...string) {
} else {
errmsg += err.Error()
}
stack := debug.Stack()
log.Print(errmsg+"\n", string(stack))
errorBufferMutex.Lock()
defer errorBufferMutex.Unlock()
stack := debug.Stack() // debug.Stack() can't be executed concurrently, so we'll guard this with a mutex too
log.Print(errmsg+"\n", string(stack))
errorBuffer = append(errorBuffer, ErrorItem{err, stack})
}
@ -304,7 +304,7 @@ func SecurityError(w http.ResponseWriter, r *http.Request, user User) RouteError
if RunPreRenderHook("pre_render_security_error", w, r, &user, &pi) {
return nil
}
err := Templates.ExecuteTemplate(w, "error.html", pi)
err := pi.Header.Theme.RunTmpl("error", pi, w)
if err != nil {
LogError(err)
}

View File

@ -237,10 +237,11 @@ var PreRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User
"pre_render_ban": nil,
"pre_render_ip_search": nil,
"pre_render_panel_dashboard": nil,
"pre_render_panel_forums": nil,
"pre_render_panel_delete_forum": nil,
"pre_render_panel_edit_forum": nil,
"pre_render_panel_dashboard": nil,
"pre_render_panel_forums": nil,
"pre_render_panel_delete_forum": nil,
"pre_render_panel_forum_edit": nil,
"pre_render_panel_forum_edit_perms": nil,
"pre_render_panel_analytics_views": nil,
"pre_render_panel_analytics_routes": nil,
@ -258,7 +259,7 @@ var PreRenderHooks = map[string][]func(http.ResponseWriter, *http.Request, *User
"pre_render_panel_word_filters_edit": nil,
"pre_render_panel_plugins": nil,
"pre_render_panel_users": nil,
"pre_render_panel_edit_user": nil,
"pre_render_panel_user_edit": nil,
"pre_render_panel_groups": nil,
"pre_render_panel_group_edit": nil,
"pre_render_panel_group_edit_perms": nil,
@ -284,11 +285,11 @@ type Plugin struct {
Installable bool
Installed bool
Init func() error
Activate func() error
Deactivate func() // TODO: We might want to let this return an error?
Install func() error
Uninstall func() error // TODO: I'm not sure uninstall is implemented
Init func(plugin *Plugin) error
Activate func(plugin *Plugin) error
Deactivate func(plugin *Plugin) // TODO: We might want to let this return an error?
Install func(plugin *Plugin) error
Uninstall func(plugin *Plugin) error // TODO: I'm not sure uninstall is implemented
Hooks map[string]int
Data interface{} // Usually used for hosting the VMs / reusable elements of non-native plugins
@ -511,8 +512,8 @@ func InitPlugins() {
log.Printf("Added plugin '%s'", name)
if body.Active {
log.Printf("Initialised plugin '%s'", name)
if Plugins[name].Init != nil {
err := Plugins[name].Init()
if body.Init != nil {
err := body.Init(body)
if err != nil {
log.Print(err)
}

View File

@ -40,19 +40,20 @@ type CSSData struct {
func (list SFileList) JSTmplInit() error {
DebugLog("Initialising the client side templates")
var fragMap = make(map[string][][]byte)
fragMap["alert"] = tmpl.GetFrag("alert")
fragMap["topics_topic"] = tmpl.GetFrag("topics_topic")
fragMap["topic_posts"] = tmpl.GetFrag("topic_posts")
fragMap["topic_alt_posts"] = tmpl.GetFrag("topic_alt_posts")
var parseFrags = func(name string) {
fragMap[name] = tmpl.GetFrag(name)
}
parseFrags("alert")
parseFrags("forum")
parseFrags("topics_topic")
parseFrags("topic_posts")
parseFrags("topic_alt_posts")
parseFrags("paginator")
DebugLog("fragMap: ", fragMap)
return filepath.Walk("./tmpl_client", func(path string, f os.FileInfo, err error) error {
if f.IsDir() {
if f.IsDir() || strings.HasSuffix(path, "template_list.go") || strings.HasSuffix(path, "stub.go") {
return nil
}
if strings.HasSuffix(path, "template_list.go") || strings.HasSuffix(path, "stub.go") {
return nil
}
path = strings.Replace(path, "\\", "/", -1)
DebugLog("Processing client template " + path)
data, err := ioutil.ReadFile(path)
@ -110,6 +111,10 @@ func (list SFileList) JSTmplInit() error {
}
return out + "]"
}*/
data = replace(data, `)
if !ok {
return errors.New("invalid page struct value")
}`, "*/tmpl_"+shortName+"_vars = tmpl_"+shortName+"_i")
// ? Can we just use a regex? I'm thinking of going more efficient, or just outright rolling wasm, this is a temp hack in a place where performance doesn't particularly matter
var each = func(phrase string, handle func(index int)) {
@ -156,7 +161,7 @@ func (list SFileList) JSTmplInit() error {
each("RelativeTime(", func(index int) {
braceAt, _ := skipUntilIfExistsOrLine(data, index, 10)
if data[braceAt-1] == ' ' {
data[braceAt-1] = ')' // Blank it
data[braceAt-1] = ' ' // Blank it
}
})
each("if ", func(index int) {
@ -191,8 +196,10 @@ func (list SFileList) JSTmplInit() error {
data = replace(data, shortName+"_tmpl_phrase_id = RegisterTmplPhraseNames([]string{", "[")
data = replace(data, "var plist = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "let plist = tmplPhrases[\""+tmplName+"\"];")
data = replace(data, "var cached_var_", "let cached_var_")
data = replace(data, `tmpl_`+shortName+`_vars, ok := tmpl_`+shortName+`_i.`, `/*`)
data = replace(data, "[]byte(", "")
data = replace(data, "StringToBytes(", "")
data = replace(data, "RelativeTime(tmpl_"+shortName+"_vars.", "tmpl_"+shortName+"_vars.Relative")
// TODO: Format dates properly on the client side
data = replace(data, ".Format(\"2006-01-02 15:04:05\"", "")
data = replace(data, ", 10", "")
@ -252,7 +259,6 @@ func (list SFileList) Init() error {
if err != nil {
return err
}
path = strings.TrimPrefix(path, "public/")
var ext = filepath.Ext("/public/" + path)
mimetype := mime.TypeByExtension(ext)

View File

@ -45,7 +45,7 @@ func (js *OttoPluginLang) AddPlugin(meta PluginMeta) (plugin *Plugin, err error)
return nil, err
}
var pluginInit = func() error {
var pluginInit = func(plugin *Plugin) error {
retValue, err := js.vm.Run(script)
if err != nil {
return err

View File

@ -849,6 +849,7 @@ func CoerceIntString(data string) (res int, length int) {
}
// TODO: Write tests for this
// Make sure we reflect changes to this in the JS port in /public/global.js
func Paginate(count int, perPage int, maxPages int) []int {
if count < perPage {
return []int{1}
@ -866,6 +867,7 @@ func Paginate(count int, perPage int, maxPages int) []int {
}
// TODO: Write tests for this
// Make sure we reflect changes to this in the JS port in /public/global.js
func PageOffset(count int, page int, perPage int) (int, int, int) {
var offset int
lastPage := LastPage(count, perPage)
@ -886,6 +888,7 @@ func PageOffset(count int, page int, perPage int) (int, int, int) {
}
// TODO: Write tests for this
// Make sure we reflect changes to this in the JS port in /public/global.js
func LastPage(count int, perPage int) int {
return (count / perPage) + 1
}

View File

@ -1,7 +1,7 @@
/*
*
* Gosora Phrase System
* Copyright Azareal 2017 - 2019
* Copyright Azareal 2017 - 2020
*
*/
package phrases

72
common/search.go Normal file
View File

@ -0,0 +1,72 @@
package common
import (
"database/sql"
"errors"
"github.com/Azareal/Gosora/query_gen"
)
//var RepliesSearch Searcher
type Searcher interface {
Query(q string) ([]int, error)
}
type ZoneSearcher interface {
QueryZone(q string, zoneID int) ([]int, error)
}
// TODO: Implement this
// Note: This is slow compared to something like ElasticSearch and very limited
type SQLSearcher struct {
queryReplies *sql.Stmt
queryTopics *sql.Stmt
queryZoneReplies *sql.Stmt
queryZoneTopics *sql.Stmt
}
// TODO: Support things other than MySQL
func NewSQLSearcher(acc *qgen.Accumulator) (*SQLSearcher, error) {
if acc.GetAdapter().GetName() != "mysql" {
return nil, errors.New("SQLSearcher only supports MySQL at this time")
}
return &SQLSearcher{
queryReplies: acc.RawPrepare("SELECT `rid` FROM `replies` WHERE MATCH(content) AGAINST (? IN NATURAL LANGUAGE MODE);"),
queryTopics: acc.RawPrepare("SELECT `tid` FROM `topics` WHERE MATCH(title,content) AGAINST (? IN NATURAL LANGUAGE MODE);"),
queryZoneReplies: acc.RawPrepare("SELECT `rid` FROM `replies` WHERE MATCH(content) AGAINST (? IN NATURAL LANGUAGE MODE) AND `parentID` = ?;"),
queryZoneTopics: acc.RawPrepare("SELECT `tid` FROM `topics` WHERE MATCH(title,content) AGAINST (? IN NATURAL LANGUAGE MODE) AND `parentID` = ?;"),
}, acc.FirstError()
}
func (searcher *SQLSearcher) Query(q string) ([]int, error) {
return nil, nil
/*
rows, err := stmt.Query(q)
if err != nil {
return nil, err
}
defer rows.Close()
*/
}
func (searcher *SQLSearcher) QueryZone(q string, zoneID int) ([]int, error) {
return nil, nil
}
// TODO: Implement this
type ElasticSearchSearcher struct {
}
func NewElasticSearchSearcher() *ElasticSearchSearcher {
return &ElasticSearchSearcher{}
}
func (searcher *ElasticSearchSearcher) Query(q string) ([]int, error) {
return nil, nil
}
func (searcher *ElasticSearchSearcher) QueryZone(q string, zoneID int) ([]int, error) {
return nil, nil
}

View File

@ -16,7 +16,10 @@ import (
)
var Ctemplates []string // TODO: Use this to filter out top level templates we don't need
var Templates = template.New("")
var DefaultTemplates = template.New("")
var DefaultTemplateFuncMap map[string]interface{}
//var Templates = template.New("")
var PrebuildTmplList []func(User, *Header) CTmpl
func skipCTmpl(key string) bool {
@ -37,120 +40,52 @@ type CTmpl struct {
Imports []string
}
func genIntTmpl(name string) func(pi interface{}, w io.Writer) error {
return func(pi interface{}, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap[name]
if !ok {
mapping = name
}
return DefaultTemplates.ExecuteTemplate(w, mapping+".html", pi)
}
}
// TODO: Refactor the template trees to not need these
// TODO: Stop duplicating these bits of code
// nolint
func interpretedTopicTemplate(pi TopicPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["topic"]
if !ok {
mapping = "topic"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
var Template_topic_handle = genIntTmpl("topic")
var Template_topic_guest_handle = Template_topic_handle
var Template_topic_member_handle = Template_topic_handle
var Template_topic_alt_handle = genIntTmpl("topic")
var Template_topic_alt_guest_handle = Template_topic_alt_handle
var Template_topic_alt_member_handle = Template_topic_alt_handle
// nolint
var Template_topic_handle = interpretedTopicTemplate
var Template_topic_alt_handle = interpretedTopicTemplate
var Template_topic_alt_guest_handle = interpretedTopicTemplate
var Template_topic_alt_member_handle = interpretedTopicTemplate
// nolint
var Template_topics_handle = func(pi TopicListPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["topics"]
if !ok {
mapping = "topics"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
var Template_topics_handle = genIntTmpl("topics")
var Template_topics_guest_handle = Template_topics_handle
var Template_topics_member_handle = Template_topics_handle
// nolint
var Template_forum_handle = func(pi ForumPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["forum"]
if !ok {
mapping = "forum"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
var Template_forum_handle = genIntTmpl("forum")
var Template_forum_guest_handle = Template_forum_handle
var Template_forum_member_handle = Template_forum_handle
// nolint
var Template_forums_handle = func(pi ForumsPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["forums"]
if !ok {
mapping = "forums"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
var Template_forums_handle = genIntTmpl("forums")
var Template_forums_guest_handle = Template_forums_handle
var Template_forums_member_handle = Template_forums_handle
// nolint
var Template_profile_handle = func(pi ProfilePage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["profile"]
if !ok {
mapping = "profile"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
var Template_profile_handle = genIntTmpl("profile")
var Template_profile_guest_handle = Template_profile_handle
var Template_profile_member_handle = Template_profile_handle
// nolint
var Template_create_topic_handle = func(pi CreateTopicPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["create_topic"]
if !ok {
mapping = "create_topic"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_login_handle = func(pi Page, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["login"]
if !ok {
mapping = "login"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_register_handle = func(pi Page, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["register"]
if !ok {
mapping = "register"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_error_handle = func(pi ErrorPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["error"]
if !ok {
mapping = "error"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_ip_search_handle = func(pi IPSearchPage, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["ip_search"]
if !ok {
mapping = "ip_search"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
// nolint
var Template_account_handle = func(pi Account, w io.Writer) error {
mapping, ok := Themes[DefaultThemeBox.Load().(string)].TemplatesMap["account"]
if !ok {
mapping = "account"
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
}
var Template_create_topic_handle = genIntTmpl("create_topic")
var Template_login_handle = genIntTmpl("login")
var Template_register_handle = genIntTmpl("register")
var Template_error_handle = genIntTmpl("error")
var Template_ip_search_handle = genIntTmpl("ip_search")
var Template_account_handle = genIntTmpl("account")
func tmplInitUsers() (User, User, User) {
avatar, microAvatar := BuildAvatar(62, "")
@ -198,8 +133,36 @@ type TmplLoggedin struct {
type nobreak interface{}
type TItem struct {
Expects string
ExpectsInt interface{}
LoggedIn bool
}
type TItemHold map[string]TItem
func (hold TItemHold) Add(name string, expects string, expectsInt interface{}) {
hold[name] = TItem{expects, expectsInt, true}
}
func (hold TItemHold) AddStd(name string, expects string, expectsInt interface{}) {
hold[name] = TItem{expects, expectsInt, false}
}
// ? - Add template hooks?
func CompileTemplates() error {
log.Print("Compiling the templates")
// TODO: Implement per-theme template overrides here too
var overriden = make(map[string]map[string]bool)
for _, theme := range Themes {
overriden[theme.Name] = make(map[string]bool)
log.Printf("theme.OverridenTemplates: %+v\n", theme.OverridenTemplates)
for _, override := range theme.OverridenTemplates {
overriden[theme.Name][override] = true
}
}
log.Printf("overriden: %+v\n", overriden)
var config tmpl.CTemplateConfig
config.Minify = Config.MinifyTemplates
config.Debug = Dev.DebugMode
@ -212,61 +175,48 @@ func CompileTemplates() error {
"github.com/Azareal/Gosora/common": "github.com/Azareal/Gosora/common",
})
c.SetBuildTags("!no_templategen")
c.SetOverrideTrack(overriden)
c.SetPerThemeTmpls(make(map[string]bool))
// Schemas to train the template compiler on what to expect
log.Print("Compiling the default templates")
var wg sync.WaitGroup
err := compileTemplates(&wg, c, "")
if err != nil {
return err
}
oroots := c.GetOverridenRoots()
log.Printf("oroots: %+v\n", oroots)
log.Print("Compiling the per-theme templates")
for theme, tmpls := range oroots {
c.SetThemeName(theme)
c.SetPerThemeTmpls(tmpls)
log.Print("theme: ", theme)
log.Printf("perThemeTmpls: %+v\n", tmpls)
err = compileTemplates(&wg, c, theme)
if err != nil {
return err
}
}
writeTemplateList(c, &wg, "./")
return nil
}
func compileCommons(c *tmpl.CTemplateSet, header *Header, header2 *Header, out TItemHold) error {
// TODO: Add support for interface{}s
user, user2, user3 := tmplInitUsers()
header, header2, _ := tmplInitHeaders(user, user2, user3)
_, user2, user3 := tmplInitUsers()
now := time.Now()
log.Print("Compiling the templates")
poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
PollOption{0, "Nothing"},
PollOption{1, "Something"},
}, VoteCount: 7}
avatar, microAvatar := BuildAvatar(62, "")
miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}}
topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false, miniAttach}
var replyList []ReplyUser
// TODO: Do we want the UID on this to be 0?
avatar, microAvatar = BuildAvatar(0, "")
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, 1, "", "", miniAttach})
var varList = make(map[string]tmpl.VarItem)
var compile = func(name string, expects string, expectsInt interface{}) (tmpl string, err error) {
return c.Compile(name+".html", "templates/", expects, expectsInt, varList)
// Convienience function to save a line here and there
var htitle = func(name string) *Header {
header.Title = name
return header
}
var compileByLoggedin = func(name string, expects string, expectsInt interface{}) (tmpl TmplLoggedin, err error) {
stub, guest, member, err := c.CompileByLoggedin(name+".html", "templates/", expects, expectsInt, varList)
return TmplLoggedin{stub, guest, member}, err
}
header.Title = "Topic Name"
tpage := TopicPage{header, replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, Paginator{[]int{1}, 1, 1}}
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
topicTmpl, err := compile("topic", "common.TopicPage", tpage)
if err != nil {
return err
}
/*topicAltTmpl, err := compile("topic_alt", "common.TopicPage", tpage)
if err != nil {
return err
/*var htitle2 = func(name string) *Header {
header2.Title = name
return header2
}*/
topicAltTmpl, err := compileByLoggedin("topic_alt", "common.TopicPage", tpage)
if err != nil {
return err
}
varList = make(map[string]tmpl.VarItem)
header.Title = "User 526"
ppage := ProfilePage{header, replyList, user, 0, 0} // TODO: Use the score from user to generate the currentScore and nextScore
profileTmpl, err := compileByLoggedin("profile", "common.ProfilePage", ppage)
if err != nil {
return err
}
// TODO: Use a dummy forum list to avoid o(n) problems
var forumList []Forum
forums, err := Forums.GetAll()
@ -277,95 +227,100 @@ func CompileTemplates() error {
forumList = append(forumList, *forum)
}
varList = make(map[string]tmpl.VarItem)
header.Title = "Forum List"
forumsPage := ForumsPage{header, forumList}
forumsTmpl, err := compileByLoggedin("forums", "common.ForumsPage", forumsPage)
if err != nil {
return err
}
var topicsList []*TopicsRow
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "127.0.0.1", 1, 0, 1, 1, 0, "classname", "", &user2, "", 0, &user3, "General", "/forum/general.2"})
header2.Title = "Topic List"
topicListPage := TopicListPage{header, topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
/*topicListTmpl, err := compile("topics", "common.TopicListPage", topicListPage)
if err != nil {
return err
}*/
topicListTmpl, err := compileByLoggedin("topics", "common.TopicListPage", topicListPage)
if err != nil {
return err
}
topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
out.Add("topics", "common.TopicListPage", topicListPage)
forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
header.Title = "General Forum"
forumPage := ForumPage{header, topicsList, forumItem, Paginator{[]int{1}, 1, 1}}
forumTmpl, err := compileByLoggedin("forum", "common.ForumPage", forumPage)
forumPage := ForumPage{htitle("General Forum"), topicsList, forumItem, Paginator{[]int{1}, 1, 1}}
out.Add("forum", "common.ForumPage", forumPage)
out.Add("forums", "common.ForumsPage", ForumsPage{htitle("Forum List"), forumList})
poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
PollOption{0, "Nothing"},
PollOption{1, "Something"},
}, VoteCount: 7}
avatar, microAvatar := BuildAvatar(62, "")
miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}}
topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false, miniAttach}
var replyList []ReplyUser
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, 1, "", "", miniAttach})
tpage := TopicPage{htitle("Topic Name"), replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, Paginator{[]int{1}, 1, 1}}
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
out.Add("topic", "common.TopicPage", tpage)
out.Add("topic_alt", "common.TopicPage", tpage)
return nil
}
func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string) error {
// Schemas to train the template compiler on what to expect
// TODO: Add support for interface{}s
user, user2, user3 := tmplInitUsers()
header, header2, _ := tmplInitHeaders(user, user2, user3)
now := time.Now()
/*poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
PollOption{0, "Nothing"},
PollOption{1, "Something"},
}, VoteCount: 7}*/
avatar, microAvatar := BuildAvatar(62, "")
miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}}
var replyList []ReplyUser
//topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false, miniAttach}
// TODO: Do we want the UID on this to be 0?
avatar, microAvatar = BuildAvatar(0, "")
replyList = append(replyList, ReplyUser{0, 0, "Yo!", "Yo!", 0, "alice", "Alice", Config.DefaultGroup, now, 0, 0, avatar, microAvatar, "", 0, "", "", "", "", 0, "127.0.0.1", false, 1, 1, "", "", miniAttach})
// Convienience function to save a line here and there
var htitle = func(name string) *Header {
header.Title = name
return header
}
tmpls := TItemHold(make(map[string]TItem))
err := compileCommons(c, header, header2, tmpls)
if err != nil {
return err
}
header.Title = "Login Page"
loginPage := Page{header, tList, nil}
loginTmpl, err := compile("login", "common.Page", loginPage)
if err != nil {
return err
}
ppage := ProfilePage{htitle("User 526"), replyList, user, 0, 0} // TODO: Use the score from user to generate the currentScore and nextScore
tmpls.Add("profile", "common.ProfilePage", ppage)
header.Title = "Registration Page"
registerPage := Page{header, tList, "nananana"}
registerTmpl, err := compile("register", "common.Page", registerPage)
if err != nil {
return err
}
tmpls.AddStd("login", "common.Page", Page{htitle("Login Page"), tList, nil})
tmpls.AddStd("register", "common.Page", Page{htitle("Registration Page"), tList, "nananana"})
tmpls.AddStd("error", "common.ErrorPage", ErrorPage{htitle("Error"), "A problem has occurred in the system."})
header.Title = "Error"
errorPage := ErrorPage{header, "A problem has occurred in the system."}
errorTmpl, err := compile("error", "common.ErrorPage", errorPage)
if err != nil {
return err
}
var ipUserList = make(map[int]*User)
ipUserList[1] = &user2
header.Title = "IP Search"
ipSearchPage := IPSearchPage{header2, ipUserList, "::1"}
ipSearchTmpl, err := compile("ip_search", "common.IPSearchPage", ipSearchPage)
if err != nil {
return err
}
ipSearchPage := IPSearchPage{htitle("IP Search"), map[int]*User{1: &user2}, "::1"}
tmpls.AddStd("ip_search", "common.IPSearchPage", ipSearchPage)
var inter nobreak
accountPage := Account{header, "dashboard", "account_own_edit", inter}
accountTmpl, err := compile("account", "common.Account", accountPage)
if err != nil {
return err
}
tmpls.AddStd("account", "common.Account", accountPage)
var wg sync.WaitGroup
var writeTemplate = func(name string, content interface{}) {
log.Print("Writing template '" + name + "'")
var writeTmpl = func(name string, content string) {
if content == "" {
log.Fatal("No content body for " + name)
return //log.Fatal("No content body for " + name)
}
err := writeFile("./template_"+name+".go", content)
if err != nil {
log.Fatal(err)
}
}
wg.Add(1)
go func() {
tname := themeName
if tname != "" {
tname = "_" + tname
}
switch content := content.(type) {
case string:
writeTmpl(name, content)
writeTmpl(name+tname, content)
case TmplLoggedin:
writeTmpl(name, content.Stub)
writeTmpl(name+"_guest", content.Guest)
writeTmpl(name+"_member", content.Member)
writeTmpl(name+tname, content.Stub)
writeTmpl(name+tname+"_guest", content.Guest)
writeTmpl(name+tname+"_member", content.Member)
}
wg.Done()
}()
@ -373,12 +328,12 @@ func CompileTemplates() error {
// Let plugins register their own templates
DebugLog("Registering the templates for the plugins")
config = c.GetConfig()
config := c.GetConfig()
config.SkipHandles = true
c.SetConfig(config)
for _, tmplfunc := range PrebuildTmplList {
tmplItem := tmplfunc(user, header)
varList = make(map[string]tmpl.VarItem)
varList := make(map[string]tmpl.VarItem)
compiledTmpl, err := c.Compile(tmplItem.Filename, tmplItem.Path, tmplItem.StructName, tmplItem.Data, varList, tmplItem.Imports...)
if err != nil {
return err
@ -387,18 +342,30 @@ func CompileTemplates() error {
}
log.Print("Writing the templates")
writeTemplate("topic", topicTmpl)
writeTemplate("topic_alt", topicAltTmpl)
writeTemplate("profile", profileTmpl)
for name, titem := range tmpls {
log.Print("Writing " + name)
varList := make(map[string]tmpl.VarItem)
if titem.LoggedIn {
stub, guest, member, err := c.CompileByLoggedin(name+".html", "templates/", titem.Expects, titem.ExpectsInt, varList)
if err != nil {
return err
}
writeTemplate(name, TmplLoggedin{stub, guest, member})
} else {
tmpl, err := c.Compile(name+".html", "templates/", titem.Expects, titem.ExpectsInt, varList)
if err != nil {
return err
}
writeTemplate(name, tmpl)
}
}
/*writeTemplate("profile", profileTmpl)
writeTemplate("forums", forumsTmpl)
writeTemplate("topics", topicListTmpl)
writeTemplate("forum", forumTmpl)
writeTemplate("login", loginTmpl)
writeTemplate("register", registerTmpl)
writeTemplate("ip_search", ipSearchTmpl)
writeTemplate("account", accountTmpl)
writeTemplate("error", errorTmpl)
writeTemplateList(c, &wg, "./")
writeTemplate("error", errorTmpl)*/
return nil
}
@ -470,6 +437,15 @@ func CompileJSTemplates() error {
if err != nil {
return err
}
itemsPerPage := 25
_, page, lastPage := PageOffset(20, 1, itemsPerPage)
pageList := Paginate(20, itemsPerPage, 5)
paginatorTmpl, err := c.Compile("paginator.html", "templates/", "common.Paginator", Paginator{pageList, page, lastPage}, varList)
if err != nil {
return err
}
/*widget := &Widget{ID: 0}
panelWidgetsWidgetTmpl, err := c.Compile("panel_themes_widgets_widget.html", "templates/", "*common.Widget", widget, varList)
if err != nil {
@ -481,9 +457,8 @@ func CompileJSTemplates() error {
var writeTemplate = func(name string, content string) {
log.Print("Writing template '" + name + "'")
if content == "" {
log.Fatal("No content body")
return //log.Fatal("No content body")
}
wg.Add(1)
go func() {
err := writeFile(dirPrefix+"template_"+name+".go", content)
@ -494,59 +469,74 @@ func CompileJSTemplates() error {
}()
}
writeTemplate("alert", alertTmpl)
//writeTemplate("forum", forumTmpl)
writeTemplate("topics_topic", topicListItemTmpl)
writeTemplate("topic_posts", topicPostsTmpl)
writeTemplate("topic_alt_posts", topicAltPostsTmpl)
writeTemplate("paginator", paginatorTmpl)
//writeTemplate("panel_themes_widgets_widget", panelWidgetsWidgetTmpl)
writeTemplateList(c, &wg, dirPrefix)
return nil
}
func getTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) string {
pout := "\n// nolint\nfunc init() {\n"
var tFragCount = make(map[string]int)
var bodyMap = make(map[string]string) //map[body]fragmentPrefix
//var tmplMap = make(map[string]map[string]string) // map[tmpl]map[body]fragmentPrefix
var tmpCount = 0
for _, frag := range c.FragOut {
front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]"
/*bodyMap, tok := tmplMap[frag.TmplName]
if !tok {
tmplMap[frag.TmplName] = make(map[string]string)
bodyMap = tmplMap[frag.TmplName]
}*/
fp, ok := bodyMap[frag.Body]
if !ok {
bodyMap[frag.Body] = front
var bits string
for _, char := range []byte(frag.Body) {
if char == '\'' {
bits += "'\\" + string(char) + "',"
} else {
bits += "'" + string(char) + "',"
}
}
tmpStr := strconv.Itoa(tmpCount)
pout += "arr_" + tmpStr + " := [...]byte{" + bits + "}\n"
pout += front + " = arr_" + tmpStr + "[:]\n"
tmpCount++
//pout += front + " = []byte(`" + frag.Body + "`)\n"
} else {
pout += front + " = " + fp + "\n"
}
_, ok = tFragCount[frag.TmplName]
if !ok {
tFragCount[frag.TmplName] = 0
}
tFragCount[frag.TmplName]++
}
out := "package " + c.GetConfig().PackageName + "\n\n"
var getterstr = "\n// nolint\nGetFrag = func(name string) [][]byte {\nswitch(name) {\n"
for templateName, count := range tFragCount {
out += "var " + templateName + "_frags = make([][]byte," + strconv.Itoa(count) + ")\n"
getterstr += "\tcase \"" + templateName + "\":\n"
getterstr += "\treturn " + templateName + "_frags\n"
}
getterstr += "}\nreturn nil\n}\n"
out += pout + "\n" + getterstr + "}\n"
return out
}
func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) {
log.Print("Writing template list")
wg.Add(1)
go func() {
out := "package " + c.GetConfig().PackageName + "\n\n"
var getterstr = "\n// nolint\nGetFrag = func(name string) [][]byte {\nswitch(name) {\n"
for templateName, count := range c.TemplateFragmentCount {
out += "var " + templateName + "_frags = make([][]byte," + strconv.Itoa(count) + ")\n"
getterstr += "\tcase \"" + templateName + "\":\n"
getterstr += "\treturn " + templateName + "_frags\n"
}
getterstr += "}\nreturn nil\n}\n"
out += "\n// nolint\nfunc init() {\n"
var bodyMap = make(map[string]string) //map[body]fragmentPrefix
//var tmplMap = make(map[string]map[string]string) // map[tmpl]map[body]fragmentPrefix
var tmpCount = 0
for _, frag := range c.FragOut {
front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]"
/*bodyMap, tok := tmplMap[frag.TmplName]
if !tok {
tmplMap[frag.TmplName] = make(map[string]string)
bodyMap = tmplMap[frag.TmplName]
}*/
fp, ok := bodyMap[frag.Body]
if !ok {
bodyMap[frag.Body] = front
var bits string
for _, char := range []byte(frag.Body) {
if char == '\'' {
bits += "'\\" + string(char) + "',"
} else {
bits += "'" + string(char) + "',"
}
}
tmpStr := strconv.Itoa(tmpCount)
out += "arr_" + tmpStr + " := [...]byte{" + bits + "}\n"
out += front + " = arr_" + tmpStr + "[:]\n"
tmpCount++
//out += front + " = []byte(`" + frag.Body + "`)\n"
} else {
out += front + " = " + fp + "\n"
}
}
out += "\n" + getterstr + "}\n"
err := writeFile(prefix+"template_list.go", out)
err := writeFile(prefix+"template_list.go", getTemplateList(c, wg, prefix))
if err != nil {
log.Fatal(err)
}
@ -579,8 +569,7 @@ func arithDuoToInt64(left interface{}, right interface{}) (leftInt int64, rightI
return arithToInt64(left), arithToInt64(right)
}
func InitTemplates() error {
DebugLog("Initialising the template system")
func initDefaultTmplFuncMap() {
// TODO: Add support for floats
fmap := make(map[string]interface{})
fmap["add"] = func(left interface{}, right interface{}) interface{} {
@ -671,9 +660,11 @@ func InitTemplates() error {
return ""
}
// The interpreted templates...
DebugLog("Loading the template files...")
Templates.Funcs(fmap)
DefaultTemplateFuncMap = fmap
}
func loadTemplates(tmpls *template.Template, themeName string) error {
tmpls.Funcs(DefaultTemplateFuncMap)
templateFiles, err := filepath.Glob("templates/*.html")
if err != nil {
return err
@ -709,8 +700,39 @@ func InitTemplates() error {
}
templateFiles[index] = path
}
template.Must(Templates.ParseFiles(templateFiles...))
template.Must(Templates.ParseGlob("pages/*"))
if themeName != "" {
overrideFiles, err := filepath.Glob("./themes/" + themeName + "/overrides/*.html")
if err != nil {
return err
}
for _, path := range overrideFiles {
path = strings.Replace(path, "\\", "/", -1)
log.Print("overrideFile: ", path)
if skipCTmpl(path) {
log.Print("skipping")
continue
}
index, ok := templateFileMap["templates/"+strings.TrimPrefix(path, "themes/"+themeName+"/overrides/")]
if !ok {
log.Print("not ok: templates/" + strings.TrimPrefix(path, "themes/"+themeName+"/overrides/"))
templateFiles = append(templateFiles, path)
continue
}
templateFiles[index] = path
}
}
template.Must(tmpls.ParseFiles(templateFiles...))
template.Must(tmpls.ParseGlob("pages/*"))
return nil
}
func InitTemplates() error {
DebugLog("Initialising the template system")
initDefaultTmplFuncMap()
// The interpreted templates...
DebugLog("Loading the template files...")
return loadTemplates(DefaultTemplates, "")
}

View File

@ -28,6 +28,7 @@ type CContext struct {
RootHolder string
VarHolder string
HoldReflect reflect.Value
RootTemplateName string
TemplateName string
LoopDepth int
OutBuf *[]OutBufferFrame

View File

@ -63,6 +63,11 @@ type CTemplateSet struct {
config CTemplateConfig
baseImportMap map[string]string
buildTags string
overridenTrack map[string]map[string]bool
overridenRoots map[string]map[string]bool
themeName string
perThemeTmpls map[string]bool
}
func NewCTemplateSet() *CTemplateSet {
@ -70,7 +75,8 @@ func NewCTemplateSet() *CTemplateSet {
config: CTemplateConfig{
PackageName: "main",
},
baseImportMap: map[string]string{},
baseImportMap: map[string]string{},
overridenRoots: map[string]map[string]bool{},
funcMap: map[string]interface{}{
"and": "&&",
"not": "!",
@ -118,6 +124,22 @@ func (c *CTemplateSet) SetBuildTags(tags string) {
c.buildTags = tags
}
func (c *CTemplateSet) SetOverrideTrack(overriden map[string]map[string]bool) {
c.overridenTrack = overriden
}
func (c *CTemplateSet) GetOverridenRoots() map[string]map[string]bool {
return c.overridenRoots
}
func (c *CTemplateSet) SetThemeName(name string) {
c.themeName = name
}
func (c *CTemplateSet) SetPerThemeTmpls(perThemeTmpls map[string]bool) {
c.perThemeTmpls = perThemeTmpls
}
type SkipBlock struct {
Frags map[int]int
LastCount int
@ -139,10 +161,8 @@ func (c *CTemplateSet) CompileByLoggedin(name string, fileDir string, expects st
for index, item := range c.baseImportMap {
c.importMap[index] = item
}
if len(imports) > 0 {
for _, importItem := range imports {
c.importMap[importItem] = importItem
}
for _, importItem := range imports {
c.importMap[importItem] = importItem
}
var importList string
for _, item := range c.importMap {
@ -150,10 +170,18 @@ func (c *CTemplateSet) CompileByLoggedin(name string, fileDir string, expects st
}
fname := strings.TrimSuffix(name, filepath.Ext(name))
if c.themeName != "" {
_, ok := c.perThemeTmpls[fname]
if !ok {
return "", "", "", nil
}
fname += "_" + c.themeName
}
c.importMap["github.com/Azareal/Gosora/common"] = "github.com/Azareal/Gosora/common"
stub = `package ` + c.config.PackageName + `
` + importList + `
import "errors"
`
if !c.config.SkipInitBlock {
@ -171,13 +199,18 @@ func (c *CTemplateSet) CompileByLoggedin(name string, fileDir string, expects st
stub += "}\n\n"
}
// TODO: Try to remove this redundant interface cast
stub += `
// nolint
func Template_` + fname + `(tmpl_` + fname + `_vars ` + expects + `, w io.Writer) error {
if tmpl_` + fname + `_vars.CurrentUser.Loggedin {
return Template_` + fname + `_member(tmpl_` + fname + `_vars, w)
func Template_` + fname + `(tmpl_` + fname + `_i interface{}, w io.Writer) error {
tmpl_` + fname + `_vars, ok := tmpl_` + fname + `_i.(` + expects + `)
if !ok {
return errors.New("invalid page struct value")
}
return Template_` + fname + `_guest(tmpl_` + fname + `_vars, w)
if tmpl_` + fname + `_vars.CurrentUser.Loggedin {
return Template_` + fname + `_member(tmpl_` + fname + `_i, w)
}
return Template_` + fname + `_guest(tmpl_` + fname + `_i, w)
}`
c.fileDir = fileDir
@ -213,15 +246,14 @@ func (c *CTemplateSet) Compile(name string, fileDir string, expects string, expe
return c.compile(name, content, expects, expectsInt, varList, imports...)
}
func (c *CTemplateSet) compile(name string, content, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) {
func (c *CTemplateSet) compile(name string, content string, expects string, expectsInt interface{}, varList map[string]VarItem, imports ...string) (out string, err error) {
c.importMap = map[string]string{}
for index, item := range c.baseImportMap {
c.importMap[index] = item
}
if len(imports) > 0 {
for _, importItem := range imports {
c.importMap[importItem] = importItem
}
c.importMap["errors"] = "errors"
for _, importItem := range imports {
c.importMap[importItem] = importItem
}
c.varList = varList
@ -238,6 +270,13 @@ func (c *CTemplateSet) compile(name string, content, expects string, expectsInt
c.detail(name)
fname := strings.TrimSuffix(name, filepath.Ext(name))
if c.themeName != "" {
_, ok := c.perThemeTmpls[fname]
if !ok {
return "", nil
}
fname += "_" + c.themeName
}
if c.guestOnly {
fname += "_guest"
} else if c.memberOnly {
@ -246,7 +285,14 @@ func (c *CTemplateSet) compile(name string, content, expects string, expectsInt
var outBuf []OutBufferFrame
var rootHold = "tmpl_" + fname + "_vars"
con := CContext{RootHolder: rootHold, VarHolder: rootHold, HoldReflect: reflect.ValueOf(expectsInt), TemplateName: fname, OutBuf: &outBuf}
con := CContext{
RootHolder: rootHold,
VarHolder: rootHold,
HoldReflect: reflect.ValueOf(expectsInt),
RootTemplateName: fname,
TemplateName: fname,
OutBuf: &outBuf,
}
c.templateList = map[string]*parse.Tree{fname: tree}
c.detail(c.templateList)
c.localVars = make(map[string]map[string]VarItemReflect)
@ -274,7 +320,6 @@ func (c *CTemplateSet) compile(name string, content, expects string, expectsInt
if !ok {
c.FragOnce[fname] = true
}
if len(c.langIndexToName) > 0 {
c.importMap[langPkg] = langPkg
}
@ -319,7 +364,12 @@ func (c *CTemplateSet) compile(name string, content, expects string, expectsInt
fout += "}\n\n"
}
fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_vars " + expects + ", w io.Writer) error {\n"
fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_i interface{}, w io.Writer) error {\n"
fout += `tmpl_` + fname + `_vars, ok := tmpl_` + fname + `_i.(` + expects + `)
if !ok {
return errors.New("invalid page struct value")
}
`
if len(c.langIndexToName) > 0 {
fout += "var plist = phrases.GetTmplPhrasesBytes(" + fname + "_tmpl_phrase_id)\n"
}
@ -427,7 +477,7 @@ func (c *CTemplateSet) rootIterate(tree *parse.Tree, con CContext) {
c.retCall("rootIterate")
}
var inSlice = func(haystack []string, expr string) bool {
func inSlice(haystack []string, expr string) bool {
for _, needle := range haystack {
if needle == expr {
return true
@ -600,11 +650,15 @@ func (c *CTemplateSet) compileRangeNode(con CContext, node *parse.RangeNode) {
}
c.detail("Range item:", item)
if !item.IsValid() {
c.critical("expr:", expr)
c.critical("con.VarHolder", con.VarHolder)
panic("item" + "^\n" + "Invalid map. Maybe, it doesn't have any entries for the template engine to analyse?")
}
startIf(item, true)
case reflect.Slice:
if outVal.Len() == 0 {
c.critical("expr:", expr)
c.critical("con.VarHolder", con.VarHolder)
panic("The sample data needs at-least one or more elements for the slices. We're looking into removing this requirement at some point!")
}
startIf(outVal.Index(0), false)
@ -1367,6 +1421,7 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNode) {
c.dumpCall("compileSubTemplate", pcon, node)
defer c.retCall("compileSubTemplate")
c.detail("Template Node: ", node.Name)
// TODO: Cascade errors back up the tree to the caller?
@ -1474,9 +1529,52 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
if !ok {
c.FragOnce[fname] = true
}
// map[string]map[string]bool
c.detail("overridenTrack loop")
c.detail("fname:", fname)
for themeName, track := range c.overridenTrack {
c.detail("themeName:", themeName)
c.detailf("track: %+v\n", track)
croot, ok := c.overridenRoots[themeName]
if !ok {
croot = make(map[string]bool)
c.overridenRoots[themeName] = croot
}
c.detailf("croot: %+v\n", croot)
for tmplName, _ := range track {
cname := tmplName
if c.guestOnly {
cname += "_guest"
} else if c.memberOnly {
cname += "_member"
}
c.detail("cname:", cname)
if fname == cname {
c.detail("match")
croot[strings.TrimSuffix(strings.TrimSuffix(con.RootTemplateName, "_guest"), "_member")] = true
} else {
c.detail("no match")
}
}
}
c.detailf("c.overridenRoots: %+v\n", c.overridenRoots)
}
func (c *CTemplateSet) loadTemplate(fileDir string, name string) (content string, err error) {
if c.themeName != "" {
c.detail("per-theme override: ", "./themes/"+c.themeName+"/overrides/"+name)
res, err := ioutil.ReadFile("./themes/" + c.themeName + "/overrides/" + name)
if err == nil {
content = string(res)
if c.config.Minify {
content = minify(content)
}
return content, nil
}
c.detail("override err: ", err)
}
res, err := ioutil.ReadFile(c.fileDir + "overrides/" + name)
if err != nil {
c.detail("override path: ", c.fileDir+"overrides/"+name)

View File

@ -5,6 +5,7 @@ import (
"bytes"
"database/sql"
"errors"
htmpl "html/template"
"io"
"io/ioutil"
"log"
@ -24,26 +25,28 @@ var ErrNoDefaultTheme = errors.New("The default theme isn't registered in the sy
type Theme struct {
Path string // Redirect this file to another folder
Name string
FriendlyName string
Version string
Creator string
FullImage string
MobileFriendly bool
Disabled bool
HideFromThemes bool
BgAvatars bool // For profiles, at the moment
GridLists bool // User Manager
ForkOf string
Tag string
URL string
Docks []string // Allowed Values: leftSidebar, rightSidebar, footer
Settings map[string]ThemeSetting
Templates []TemplateMapping
TemplatesMap map[string]string
TmplPtr map[string]interface{}
Resources []ThemeResource
ResourceTemplates *template.Template
Name string
FriendlyName string
Version string
Creator string
FullImage string
MobileFriendly bool
Disabled bool
HideFromThemes bool
BgAvatars bool // For profiles, at the moment
GridLists bool // User Manager
ForkOf string
Tag string
URL string
Docks []string // Allowed Values: leftSidebar, rightSidebar, footer
Settings map[string]ThemeSetting
IntTmplHandle *htmpl.Template
OverridenTemplates []string
Templates []TemplateMapping
TemplatesMap map[string]string
TmplPtr map[string]interface{}
Resources []ThemeResource
ResourceTemplates *template.Template
// Dock intercepters
// TODO: Implement this
@ -180,100 +183,22 @@ func (theme *Theme) MapTemplates() {
LogError(errors.New("The source template doesn't exist!"))
}
switch dTmplPtr := destTmplPtr.(type) {
case *func(CustomPagePage, io.Writer) error:
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(CustomPagePage, io.Writer) error:
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case *func(TopicPage, io.Writer) error:
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(TopicPage, io.Writer) error:
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case *func(TopicListPage, io.Writer) error:
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(TopicListPage, io.Writer) error:
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case *func(ForumPage, io.Writer) error:
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(ForumPage, io.Writer) error:
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case *func(ForumsPage, io.Writer) error:
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(ForumsPage, io.Writer) error:
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case *func(ProfilePage, io.Writer) error:
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(ProfilePage, io.Writer) error:
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case *func(CreateTopicPage, io.Writer) error:
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(CreateTopicPage, io.Writer) error:
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case *func(IPSearchPage, io.Writer) error:
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(IPSearchPage, io.Writer) error:
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case *func(AccountDashPage, io.Writer) error:
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(AccountDashPage, io.Writer) error:
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case *func(ErrorPage, io.Writer) error:
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(ErrorPage, io.Writer) error:
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case *func(Page, io.Writer) error:
switch sTmplPtr := sourceTmplPtr.(type) {
case *func(Page, io.Writer) error:
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
default:
dTmplPtr, ok := destTmplPtr.(*func(interface{}, io.Writer) error)
if !ok {
log.Print("themeTmpl.Name: ", themeTmpl.Name)
log.Print("themeTmpl.Source: ", themeTmpl.Source)
LogError(errors.New("Unknown destination template type!"))
return
}
sTmplPtr, ok := sourceTmplPtr.(*func(interface{}, io.Writer) error)
if !ok {
LogError(errors.New("The source and destination templates are incompatible"))
return
}
overridenTemplates[themeTmpl.Name] = true
*dTmplPtr = *sTmplPtr
}
}
}
@ -366,67 +291,17 @@ func (theme *Theme) RunTmpl(template string, pi interface{}, w io.Writer) error
var getTmpl = theme.GetTmpl(template)
switch tmplO := getTmpl.(type) {
case *func(CustomPagePage, io.Writer) error:
case *func(interface{}, io.Writer) error:
var tmpl = *tmplO
return tmpl(pi.(CustomPagePage), w)
case *func(TopicPage, io.Writer) error:
var tmpl = *tmplO
return tmpl(pi.(TopicPage), w)
case *func(TopicListPage, io.Writer) error:
var tmpl = *tmplO
return tmpl(pi.(TopicListPage), w)
case *func(ForumPage, io.Writer) error:
var tmpl = *tmplO
return tmpl(pi.(ForumPage), w)
case *func(ForumsPage, io.Writer) error:
var tmpl = *tmplO
return tmpl(pi.(ForumsPage), w)
case *func(ProfilePage, io.Writer) error:
var tmpl = *tmplO
return tmpl(pi.(ProfilePage), w)
case *func(CreateTopicPage, io.Writer) error:
var tmpl = *tmplO
return tmpl(pi.(CreateTopicPage), w)
case *func(IPSearchPage, io.Writer) error:
var tmpl = *tmplO
return tmpl(pi.(IPSearchPage), w)
case *func(Account, io.Writer) error:
var tmpl = *tmplO
return tmpl(pi.(Account), w)
case *func(ErrorPage, io.Writer) error:
var tmpl = *tmplO
return tmpl(pi.(ErrorPage), w)
case *func(Page, io.Writer) error:
var tmpl = *tmplO
return tmpl(pi.(Page), w)
case func(CustomPagePage, io.Writer) error:
return tmplO(pi.(CustomPagePage), w)
case func(TopicPage, io.Writer) error:
return tmplO(pi.(TopicPage), w)
case func(TopicListPage, io.Writer) error:
return tmplO(pi.(TopicListPage), w)
case func(ForumPage, io.Writer) error:
return tmplO(pi.(ForumPage), w)
case func(ForumsPage, io.Writer) error:
return tmplO(pi.(ForumsPage), w)
case func(ProfilePage, io.Writer) error:
return tmplO(pi.(ProfilePage), w)
case func(CreateTopicPage, io.Writer) error:
return tmplO(pi.(CreateTopicPage), w)
case func(IPSearchPage, io.Writer) error:
return tmplO(pi.(IPSearchPage), w)
case func(Account, io.Writer) error:
return tmplO(pi.(Account), w)
case func(ErrorPage, io.Writer) error:
return tmplO(pi.(ErrorPage), w)
case func(Page, io.Writer) error:
return tmplO(pi.(Page), w)
return tmpl(pi, w)
case func(interface{}, io.Writer) error:
return tmplO(pi, w)
case nil, string:
mapping, ok := theme.TemplatesMap[template]
if !ok {
mapping = template
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
return theme.IntTmplHandle.ExecuteTemplate(w, mapping+".html", pi)
default:
log.Print("theme ", theme)
log.Print("template ", template)

View File

@ -4,11 +4,14 @@ import (
"database/sql"
"encoding/json"
"errors"
"html/template"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"sync/atomic"
@ -136,6 +139,40 @@ func NewThemeList() (themes ThemeList, err error) {
}
}
theme.IntTmplHandle = DefaultTemplates
overrides, err := ioutil.ReadDir(theme.Path + "/overrides/")
if err != nil && !os.IsNotExist(err) {
return themes, err
}
if len(overrides) > 0 {
var overCount = 0
for _, override := range overrides {
if override.IsDir() {
continue
}
var ext = filepath.Ext(themePath + "/overrides/" + override.Name())
log.Print("attempting to add " + themePath + "/overrides/" + override.Name())
if ext != ".html" {
log.Print("not a html file")
continue
}
overCount++
theme.OverridenTemplates = append(theme.OverridenTemplates, strings.TrimSuffix(override.Name(), ext))
log.Print("succeeded")
}
localTmpls := template.New("")
err = loadTemplates(localTmpls, theme.Name)
if err != nil {
return themes, err
}
theme.IntTmplHandle = localTmpls
log.Printf("theme.OverridenTemplates: %+v\n", theme.OverridenTemplates)
log.Printf("theme.IntTmplHandle: %+v\n", theme.IntTmplHandle)
} else {
log.Print("no overrides for " + theme.Name)
}
// TODO: Bind the built template, or an interpreted one for any dock overrides this theme has
themes[theme.Name] = theme
@ -218,88 +255,20 @@ func ResetTemplateOverrides() {
}
// Not really a pointer, more of a function handle, an artifact from one of the earlier versions of themes.go
switch oPtr := originPointer.(type) {
case func(CustomPagePage, io.Writer) error:
switch dPtr := destTmplPtr.(type) {
case *func(CustomPagePage, io.Writer) error:
*dPtr = oPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case func(TopicPage, io.Writer) error:
switch dPtr := destTmplPtr.(type) {
case *func(TopicPage, io.Writer) error:
*dPtr = oPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case func(TopicListPage, io.Writer) error:
switch dPtr := destTmplPtr.(type) {
case *func(TopicListPage, io.Writer) error:
*dPtr = oPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case func(ForumPage, io.Writer) error:
switch dPtr := destTmplPtr.(type) {
case *func(ForumPage, io.Writer) error:
*dPtr = oPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case func(ForumsPage, io.Writer) error:
switch dPtr := destTmplPtr.(type) {
case *func(ForumsPage, io.Writer) error:
*dPtr = oPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case func(ProfilePage, io.Writer) error:
switch dPtr := destTmplPtr.(type) {
case *func(ProfilePage, io.Writer) error:
*dPtr = oPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case func(CreateTopicPage, io.Writer) error:
switch dPtr := destTmplPtr.(type) {
case *func(CreateTopicPage, io.Writer) error:
*dPtr = oPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case func(IPSearchPage, io.Writer) error:
switch dPtr := destTmplPtr.(type) {
case *func(IPSearchPage, io.Writer) error:
*dPtr = oPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case func(AccountDashPage, io.Writer) error:
switch dPtr := destTmplPtr.(type) {
case *func(AccountDashPage, io.Writer) error:
*dPtr = oPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case func(ErrorPage, io.Writer) error:
switch dPtr := destTmplPtr.(type) {
case *func(ErrorPage, io.Writer) error:
*dPtr = oPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
case func(Page, io.Writer) error:
switch dPtr := destTmplPtr.(type) {
case *func(Page, io.Writer) error:
*dPtr = oPtr
default:
LogError(errors.New("The source and destination templates are incompatible"))
}
default:
oPtr, ok := originPointer.(func(interface{}, io.Writer) error)
if !ok {
log.Print("name: ", name)
LogError(errors.New("Unknown destination template type!"))
return
}
dPtr, ok := destTmplPtr.(*func(interface{}, io.Writer) error)
if !ok {
LogError(errors.New("The source and destination templates are incompatible"))
return
}
*dPtr = oPtr
log.Print("The template override was reset")
}
overridenTemplates = make(map[string]bool)
@ -313,7 +282,7 @@ func CreateThemeTemplate(theme string, name string) {
if !ok {
mapping = name
}
return Templates.ExecuteTemplate(w, mapping+".html", pi)
return DefaultTemplates.ExecuteTemplate(w, mapping+".html", pi)
}
}

View File

@ -1,7 +1,7 @@
/*
*
* Gosora Topic File
* Copyright Azareal 2017 - 2019
* Copyright Azareal 2017 - 2020
*
*/
package common

View File

@ -16,9 +16,9 @@ type TopicListHolder struct {
}
type TopicListInt interface {
GetListByCanSee(canSee []int, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
GetList(page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
GetListByCanSee(canSee []int, page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
GetListByGroup(group *Group, page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
GetList(page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
}
type DefaultTopicList struct {
@ -94,7 +94,7 @@ func (tList *DefaultTopicList) Tick() error {
var canSeeHolders = make(map[string]*TopicListHolder)
for name, canSee := range permTree {
topicList, forumList, paginator, err := tList.GetListByCanSee(canSee, 1, "")
topicList, forumList, paginator, err := tList.GetListByCanSee(canSee, 1, "", nil)
if err != nil {
return err
}
@ -115,12 +115,12 @@ func (tList *DefaultTopicList) Tick() error {
return nil
}
func (tList *DefaultTopicList) GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) {
func (tList *DefaultTopicList) GetListByGroup(group *Group, page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) {
if page == 0 {
page = 1
}
// TODO: Cache the first three pages not just the first along with all the topics on this beaten track
if page == 1 && orderby == "" {
if page == 1 && orderby == "" && len(filterIDs) == 0 {
var holder *TopicListHolder
var ok bool
if group.ID%2 == 0 {
@ -139,10 +139,10 @@ func (tList *DefaultTopicList) GetListByGroup(group *Group, page int, orderby st
// TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins?
//log.Printf("deoptimising for %d on page %d\n", group.ID, page)
return tList.GetListByCanSee(group.CanSee, page, orderby)
return tList.GetListByCanSee(group.CanSee, page, orderby, filterIDs)
}
func (tList *DefaultTopicList) GetListByCanSee(canSee []int, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) {
func (tList *DefaultTopicList) GetListByCanSee(canSee []int, page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) {
// We need a list of the visible forums for Quick Topic
// ? - Would it be useful, if we could post in social groups from /topics/?
for _, fid := range canSee {
@ -154,6 +154,26 @@ func (tList *DefaultTopicList) GetListByCanSee(canSee []int, page int, orderby s
}
}
var inSlice = func(haystack []int, needle int) bool {
for _, item := range haystack {
if needle == item {
return true
}
}
return false
}
var filteredForums []Forum
if len(filterIDs) > 0 {
for _, forum := range forumList {
if inSlice(filterIDs, forum.ID) {
filteredForums = append(filteredForums, forum)
}
}
} else {
filteredForums = forumList
}
// ? - Should we be showing plugin_guilds posts on /topics/?
argList, qlist := ForumListToArgQ(forumList)
if qlist == "" {
@ -166,13 +186,33 @@ func (tList *DefaultTopicList) GetListByCanSee(canSee []int, page int, orderby s
}
// TODO: Reduce the number of returns
func (tList *DefaultTopicList) GetList(page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) {
func (tList *DefaultTopicList) GetList(page int, orderby string, filterIDs []int) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) {
// TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins?
canSee, err := Forums.GetAllVisibleIDs()
cCanSee, err := Forums.GetAllVisibleIDs()
if err != nil {
return nil, nil, Paginator{nil, 1, 1}, err
}
var inSlice = func(haystack []int, needle int) bool {
for _, item := range haystack {
if needle == item {
return true
}
}
return false
}
var canSee []int
if len(filterIDs) > 0 {
for _, fid := range cCanSee {
if inSlice(filterIDs, fid) {
canSee = append(canSee, fid)
}
}
} else {
canSee = cCanSee
}
// We need a list of the visible forums for Quick Topic
// ? - Would it be useful, if we could post in social groups from /topics/?
for _, fid := range canSee {

View File

@ -62,20 +62,21 @@ func (user *User) WebSockets() *WsJSONUser {
groupID = user.TempGroup
}
// TODO: Do we want to leak the user's permissions? Users will probably be able to see their status from the group tags, but still
return &WsJSONUser{user.ID, user.Link, user.Name, groupID, user.IsMod, user.Avatar, user.Level, user.Score, user.Liked}
return &WsJSONUser{user.ID, user.Link, user.Name, groupID, user.IsMod, user.Avatar, user.MicroAvatar, user.Level, user.Score, user.Liked}
}
// Use struct tags to avoid having to define this? It really depends on the circumstances, sometimes we want the whole thing, sometimes... not.
type WsJSONUser struct {
ID int
Link string
Name string
Group int // Be sure to mask with TempGroup
IsMod bool
Avatar string
Level int
Score int
Liked int
ID int
Link string
Name string
Group int // Be sure to mask with TempGroup
IsMod bool
Avatar string
MicroAvatar string
Level int
Score int
Liked int
}
func (user *User) Me() *MeUser {

View File

@ -37,8 +37,10 @@ func init() {
topicWatchers = make(map[int]map[*WSUser]bool)
}
//easyjson:json
type WsTopicList struct {
Topics []*WsTopicsRow
Topics []*WsTopicsRow
LastPage int // Not for WebSockets, but for the JSON endpoint for /topics/ to keep the paginator functional
}
// TODO: How should we handle errors for this?

View File

@ -3,16 +3,19 @@ package common
import "errors"
// TODO: Move this into it's own package to make neater and tidier
type filterForum struct {
*Forum
Selected bool
}
type searchAndFilter struct {
*Header
Forums []*Forum
Forums []filterForum
}
func widgetSearchAndFilter(widget *Widget, hvars interface{}) (out string, err error) {
header := hvars.(*Header)
user := header.CurrentUser
var forums []*Forum
var forums []filterForum
var canSee []int
if user.IsSuperAdmin {
canSee, err = Forums.GetAllVisibleIDs()
@ -31,11 +34,11 @@ func widgetSearchAndFilter(widget *Widget, hvars interface{}) (out string, err e
for _, fid := range canSee {
forum := Forums.DirtyGet(fid)
if forum.ParentID == 0 && forum.Name != "" && forum.Active {
forums = append(forums, forum)
forums = append(forums, filterForum{forum, (header.Zone == "view_forum" || header.Zone == "topics") && header.ZoneID == forum.ID})
}
}
saf := &searchAndFilter{header, forums}
err = saf.Header.Theme.RunTmpl("widget_search_and_filter", saf, saf.Header.Writer)
return "", err
}
}

View File

@ -48,7 +48,7 @@ type NameTextPair struct {
func preparseWidget(widget *Widget, wdata string) (err error) {
prebuildWidget := func(name string, data interface{}) (string, error) {
var b bytes.Buffer
err := Templates.ExecuteTemplate(&b, name+".html", data)
err := DefaultTemplates.ExecuteTemplate(&b, name+".html", data)
return string(b.Bytes()), err
}

View File

@ -47,9 +47,13 @@ func (hub *WsHubImpl) Start() {
// This Tick is separate from the admin one, as we want to process that in parallel with this due to the blocking calls to gopsutil
func (hub *WsHubImpl) Tick() error {
return wsTopicListTick(hub)
}
func wsTopicListTick(hub *WsHubImpl) error {
// Don't waste CPU time if nothing has happened
// TODO: Get a topic list method which strips stickies?
tList, _, _, err := TopicList.GetList(1, "")
tList, _, _, err := TopicList.GetList(1, "", nil)
if err != nil {
hub.lastTick = time.Now()
return err // TODO: Do we get ErrNoRows here?
@ -117,7 +121,7 @@ func (hub *WsHubImpl) Tick() error {
var canSeeRenders = make(map[string][]byte)
for name, canSee := range canSeeMap {
topicList, forumList, _, err := TopicList.GetListByCanSee(canSee, 1, "")
topicList, forumList, _, err := TopicList.GetListByCanSee(canSee, 1, "", nil)
if err != nil {
return err // TODO: Do we get ErrNoRows here?
}
@ -146,7 +150,7 @@ func (hub *WsHubImpl) Tick() error {
wsTopicList[i] = topicRow.WebSockets()
}
outBytes, err := json.Marshal(&WsTopicList{wsTopicList})
outBytes, err := json.Marshal(&WsTopicList{wsTopicList, 0})
if err != nil {
return err
}

View File

@ -126,6 +126,10 @@ go build "./cmd/install"
install.exe
go get github.com/mailru/easyjson/...
easyjson -pkg common
gosora.exe -build-templates
gosora.exe

View File

@ -116,7 +116,7 @@ func CommonAreaWidgets(header *common.Header) {
common.WidgetMenuItem{"Create Guild", "/guild/create/", false},
}}
err := common.Templates.ExecuteTemplate(&b, "widget_menu.html", menu)
err := header.Theme.RunTmpl("widget_menu", pi, w)
if err != nil {
common.LogError(err)
return
@ -232,7 +232,7 @@ func RouteCreateGuild(w http.ResponseWriter, r *http.Request, user common.User)
CommonAreaWidgets(header)
pi := common.Page{header, tList, nil}
err := common.Templates.ExecuteTemplate(w, "guilds_create_guild.html", pi)
err := header.Theme.RunTmpl("guilds_create_guild", pi, w)
if err != nil {
return common.InternalError(err, w, r)
}
@ -384,7 +384,7 @@ func PreRenderViewForum(w http.ResponseWriter, r *http.Request, user *common.Use
guildItem := guildData.(*Guild)
guildpi := Page{pi.Title, pi.Header, pi.ItemList, pi.Forum, guildItem, pi.Page, pi.LastPage}
err := common.Templates.ExecuteTemplate(w, "guilds_view_guild.html", guildpi)
err := header.Theme.RunTmpl("guilds_view_guild", guildpi, w)
if err != nil {
common.LogError(err)
return false

View File

@ -15,13 +15,13 @@ func init() {
common.PrebuildTmplList = append(common.PrebuildTmplList, guilds.PrebuildTmplList)
}
func initGuilds() (err error) {
common.Plugins["guilds"].AddHook("intercept_build_widgets", guilds.Widgets)
common.Plugins["guilds"].AddHook("trow_assign", guilds.TrowAssign)
common.Plugins["guilds"].AddHook("topic_create_pre_loop", guilds.TopicCreatePreLoop)
common.Plugins["guilds"].AddHook("pre_render_forum", guilds.PreRenderViewForum)
common.Plugins["guilds"].AddHook("simple_forum_check_pre_perms", guilds.ForumCheck)
common.Plugins["guilds"].AddHook("forum_check_pre_perms", guilds.ForumCheck)
func initGuilds(plugin *common.Plugin) (err error) {
plugin.AddHook("intercept_build_widgets", guilds.Widgets)
plugin.AddHook("trow_assign", guilds.TrowAssign)
plugin.AddHook("topic_create_pre_loop", guilds.TopicCreatePreLoop)
plugin.AddHook("pre_render_forum", guilds.PreRenderViewForum)
plugin.AddHook("simple_forum_check_pre_perms", guilds.ForumCheck)
plugin.AddHook("forum_check_pre_perms", guilds.ForumCheck)
// TODO: Auto-grant this perm to admins upon installation?
common.RegisterPluginPerm("CreateGuild")
router.HandleFunc("/guilds/", guilds.RouteGuildList)
@ -54,13 +54,13 @@ func initGuilds() (err error) {
return acc.FirstError()
}
func deactivateGuilds() {
common.Plugins["guilds"].RemoveHook("intercept_build_widgets", guilds.Widgets)
common.Plugins["guilds"].RemoveHook("trow_assign", guilds.TrowAssign)
common.Plugins["guilds"].RemoveHook("topic_create_pre_loop", guilds.TopicCreatePreLoop)
common.Plugins["guilds"].RemoveHook("pre_render_forum", guilds.PreRenderViewForum)
common.Plugins["guilds"].RemoveHook("simple_forum_check_pre_perms", guilds.ForumCheck)
common.Plugins["guilds"].RemoveHook("forum_check_pre_perms", guilds.ForumCheck)
func deactivateGuilds(plugin *common.Plugin) {
plugin.RemoveHook("intercept_build_widgets", guilds.Widgets)
plugin.RemoveHook("trow_assign", guilds.TrowAssign)
plugin.RemoveHook("topic_create_pre_loop", guilds.TopicCreatePreLoop)
plugin.RemoveHook("pre_render_forum", guilds.PreRenderViewForum)
plugin.RemoveHook("simple_forum_check_pre_perms", guilds.ForumCheck)
plugin.RemoveHook("forum_check_pre_perms", guilds.ForumCheck)
common.DeregisterPluginPerm("CreateGuild")
_ = router.RemoveFunc("/guilds/")
_ = router.RemoveFunc("/guild/")
@ -76,7 +76,7 @@ func deactivateGuilds() {
}
// TODO: Stop accessing the query builder directly and add a feature in Gosora which is more easily reversed, if an error comes up during the installation process
func installGuilds() error {
func installGuilds(plugin *common.Plugin) error {
guildTableStmt, err := qgen.Builder.CreateTable("guilds", "utf8mb4", "utf8mb4_general_ci",
[]qgen.DBTableColumn{
qgen.DBTableColumn{"guildID", "int", 0, false, true, ""},
@ -125,6 +125,6 @@ func installGuilds() error {
}
// TO-DO; Implement an uninstallation system into Gosora. And a better installation system.
func uninstallGuilds() error {
func uninstallGuilds(plugin *common.Plugin) error {
return nil
}

View File

@ -587,7 +587,7 @@ type GenRouter struct {
}
func NewGenRouter(uploads http.Handler) (*GenRouter, error) {
f, err := os.OpenFile("./logs/requests.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
f, err := os.OpenFile("./logs/reqs-"+strconv.FormatInt(common.StartTime.Unix(),10)+".log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
if err != nil {
return nil, err
}

View File

@ -6,11 +6,11 @@ import (
"log"
"net/http"
"net/http/httptest"
"runtime/debug"
"strconv"
"strings"
"testing"
"time"
"runtime/debug"
"github.com/pkg/errors"
@ -66,6 +66,10 @@ func gloinit() (err error) {
return errors.WithStack(err)
}
err = common.InitTemplates()
if err != nil {
return errors.WithStack(err)
}
common.Themes, err = common.NewThemeList()
if err != nil {
return errors.WithStack(err)
@ -342,6 +346,14 @@ func BenchmarkTopicsGuestRouteParallelWithRouter(b *testing.B) {
obRoute(b, "/topics/")
}
func BenchmarkTopicsGuestJSRouteParallelWithRouter(b *testing.B) {
obRoute(b, "/topics/?js=1")
}
func BenchmarkTopicsGuestEJSRouteParallelWithRouter(b *testing.B) {
obRoute(b, "/topics/?ejs=1")
}
func BenchmarkForumsGuestRouteParallelWithRouter(b *testing.B) {
obRoute(b, "/forums/")
}

1
go.mod
View File

@ -10,6 +10,7 @@ require (
github.com/go-sql-driver/mysql v1.4.0
github.com/gorilla/websocket v1.4.0
github.com/lib/pq v1.0.0
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329
github.com/oschwald/geoip2-golang v1.2.1
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
github.com/pkg/errors v0.8.0

2
go.sum
View File

@ -41,6 +41,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=

View File

@ -537,7 +537,6 @@
"status.closed_tooltip":"Status: Closed",
"status.pinned_tooltip":"Status: Pinned",
"topics_head":"All Topics",
"topics_locked_tooltip":"You don't have the permissions needed to create a topic",
"topics_locked_aria":"You don't have the permissions needed to make a topic anywhere",
"topics_list_aria":"A list containing topics from every forum",
@ -624,12 +623,16 @@
"topic.your_information":"Your information",
"paginator_less_than":"&lt;",
"paginator_greater_than":"&gt;",
"paginator_prev_page":"Prev",
"paginator_prev_page_aria":"Go to the previous page",
"paginator_next_page":"Next",
"paginator_next_page_aria":"Go to the next page",
"paginator.less_than":"&lt;",
"paginator.greater_than":"&gt;",
"paginator.first_page":"",
"paginator.first_page_aria":"Go to the first page",
"paginator.last_page":"",
"paginator.last_page_aria":"Go to the last page",
"paginator.prev_page":"",
"paginator.prev_page_aria":"Go to the previous page",
"paginator.next_page":"",
"paginator.next_page_aria":"Go to the next page",
"profile_login_for_options":"Login for options",
"profile_add_friend":"Add Friend",
@ -754,6 +757,7 @@
"panel_forum_edit_button":"Edit",
"panel_forum_short_update_button":"Update",
"panel_forum_full_edit_button":"Full Edit",
"panel_forum_delete_are_you_sure":"Are you sure you want to delete the '%s' forum?",
"panel_groups_head":"Groups",
"panel_groups_rank_prefix":"Rank ",

19
main.go
View File

@ -19,6 +19,7 @@ import (
"os/signal"
"runtime"
"runtime/pprof"
"strconv"
"strings"
"syscall"
"time"
@ -55,10 +56,6 @@ func afterDBInit() (err error) {
return errors.WithStack(err)
}
err = common.InitTemplates()
if err != nil {
return errors.WithStack(err)
}
err = phrases.InitPhrases(common.Site.Language)
if err != nil {
return errors.WithStack(err)
@ -222,10 +219,11 @@ func main() {
return
}
}()*/
common.StartTime = time.Now()
// TODO: Have a file for each run with the time/date the server started as the file name?
// TODO: Log panics with recover()
f, err := os.OpenFile("./logs/ops.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
f, err := os.OpenFile("./logs/ops-"+strconv.FormatInt(common.StartTime.Unix(), 10)+".log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
if err != nil {
log.Fatal(err)
}
@ -233,7 +231,6 @@ func main() {
log.SetOutput(logWriter)
log.Print("Running Gosora v" + common.SoftwareVersion.String())
fmt.Println("")
common.StartTime = time.Now()
// TODO: Add a flag for enabling the profiler
if false {
@ -261,6 +258,10 @@ func main() {
log.Fatal(err)
}
err = common.InitTemplates()
if err != nil {
log.Fatal(err)
}
common.Themes, err = common.NewThemeList()
if err != nil {
log.Fatal(err)
@ -332,7 +333,6 @@ func main() {
for {
select {
case event := <-watcher.Events:
log.Println("event:", event)
// TODO: Handle file deletes (and renames more graciously by removing the old version of it)
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("modified file:", event.Name)
@ -340,12 +340,15 @@ func main() {
} else if event.Op&fsnotify.Create == fsnotify.Create {
log.Println("new file:", event.Name)
err = modifiedFileEvent(event.Name)
} else {
log.Println("unknown event:", event)
err = nil
}
if err != nil {
common.LogError(err)
}
case err = <-watcher.Errors:
common.LogError(err)
common.LogWarning(err)
}
}
}()

View File

@ -861,7 +861,7 @@ func TestPluginManager(t *testing.T) {
expectNilErr(t, err)
expect(t, hasPlugin, "Plugin bbcode should exist in the database")
expect(t, plugin.Init != nil, "Plugin bbcode should have an init function")
expectNilErr(t, plugin.Init())
expectNilErr(t, plugin.Init(plugin))
expectNilErr(t, plugin.SetActive(true))
expect(t, !plugin.Installable, "Plugin bbcode shouldn't be installable")
@ -885,7 +885,7 @@ func TestPluginManager(t *testing.T) {
expectNilErr(t, err)
expect(t, hasPlugin, "Plugin bbcode should still exist in the database")
expect(t, plugin.Deactivate != nil, "Plugin bbcode should have an init function")
plugin.Deactivate() // Returns nothing
plugin.Deactivate(plugin) // Returns nothing
// Not installable, should not be mutated
expect(t, plugin.SetInstalled(true) == common.ErrPluginNotInstallable, "Plugin was set as installed despite not being installable")
@ -949,12 +949,12 @@ func TestPluginManager(t *testing.T) {
expect(t, !hasPlugin, "Plugin markdown shouldn't exist in the database")
expectNilErr(t, plugin2.AddToDatabase(true, false))
expectNilErr(t, plugin2.Init())
expectNilErr(t, plugin2.Init(plugin2))
expectNilErr(t, plugin.SetActive(true))
expectNilErr(t, plugin.Init())
plugin2.Deactivate()
expectNilErr(t, plugin.Init(plugin))
plugin2.Deactivate(plugin2)
expectNilErr(t, plugin2.SetActive(false))
plugin.Deactivate()
plugin.Deactivate(plugin)
expectNilErr(t, plugin.SetActive(false))
// Hook tests

View File

@ -16,14 +16,14 @@ func init() {
})
}
func initAdventure() error {
func initAdventure(plugin *common.Plugin) error {
return nil
}
// TODO: Change the signature to return an error?
func deactivateAdventure() {
func deactivateAdventure(plugin *common.Plugin) {
}
func installAdventure() error {
func installAdventure(plugin *common.Plugin) error {
return nil
}

View File

@ -28,8 +28,8 @@ func init() {
common.Plugins.Add(&common.Plugin{UName: "bbcode", Name: "BBCode", Author: "Azareal", URL: "https://github.com/Azareal", Init: initBbcode, Deactivate: deactivateBbcode})
}
func initBbcode() error {
common.Plugins["bbcode"].AddHook("parse_assign", bbcodeFullParse)
func initBbcode(plugin *common.Plugin) error {
plugin.AddHook("parse_assign", bbcodeFullParse)
bbcodeInvalidNumber = []byte("<span style='color: red;'>[Invalid Number]</span>")
bbcodeNoNegative = []byte("<span style='color: red;'>[No Negative Numbers]</span>")
@ -49,8 +49,8 @@ func initBbcode() error {
return nil
}
func deactivateBbcode() {
common.Plugins["bbcode"].RemoveHook("parse_assign", bbcodeFullParse)
func deactivateBbcode(plugin *common.Plugin) {
plugin.RemoveHook("parse_assign", bbcodeFullParse)
}
func bbcodeRegexParse(msg string) string {

View File

@ -7,13 +7,13 @@ func init() {
}
// init_heythere is separate from init() as we don't want the plugin to run if the plugin is disabled
func initHeythere() error {
common.Plugins["heythere"].AddHook("topic_reply_row_assign", heythereReply)
func initHeythere(plugin *common.Plugin) error {
plugin.AddHook("topic_reply_row_assign", heythereReply)
return nil
}
func deactivateHeythere() {
common.Plugins["heythere"].RemoveHook("topic_reply_row_assign", heythereReply)
func deactivateHeythere(plugin *common.Plugin) {
plugin.RemoveHook("topic_reply_row_assign", heythereReply)
}
func heythereReply(data ...interface{}) interface{} {

View File

@ -22,8 +22,8 @@ func init() {
common.Plugins.Add(&common.Plugin{UName: "markdown", Name: "Markdown", Author: "Azareal", URL: "https://github.com/Azareal", Init: initMarkdown, Deactivate: deactivateMarkdown})
}
func initMarkdown() error {
common.Plugins["markdown"].AddHook("parse_assign", markdownParse)
func initMarkdown(plugin *common.Plugin) error {
plugin.AddHook("parse_assign", markdownParse)
markdownUnclosedElement = []byte("<span style='color: red;'>[Unclosed Element]</span>")
@ -38,8 +38,8 @@ func initMarkdown() error {
return nil
}
func deactivateMarkdown() {
common.Plugins["markdown"].RemoveHook("parse_assign", markdownParse)
func deactivateMarkdown(plugin *common.Plugin) {
plugin.RemoveHook("parse_assign", markdownParse)
}
// An adapter for the parser, so that the parser can call itself recursively.

View File

@ -31,9 +31,9 @@ func init() {
common.Plugins.Add(&common.Plugin{UName: "skeleton", Name: "Skeleton", Author: "Azareal", Init: initSkeleton, Activate: activateSkeleton, Deactivate: deactivateSkeleton})
}
func initSkeleton() error { return nil }
func initSkeleton(plugin *common.Plugin) error { return nil }
// Any errors encountered while trying to activate the plugin are reported back to the admin and the activation is aborted
func activateSkeleton() error { return nil }
func activateSkeleton(plugin *common.Plugin) error { return nil }
func deactivateSkeleton() {}
func deactivateSkeleton(plugin *common.Plugin) {}

View File

@ -1,7 +1,11 @@
package main
import "strconv"
import "testing"
import (
"strconv"
"testing"
"github.com/Azareal/Gosora/common"
)
// go test -v
@ -22,7 +26,7 @@ func (tlist *MEPairList) Add(msg string, expects string) {
func TestBBCodeRender(t *testing.T) {
//t.Skip()
err := initBbcode()
err := initBbcode(common.Plugins["bbcode"])
if err != nil {
t.Fatal(err)
}
@ -211,7 +215,7 @@ func TestBBCodeRender(t *testing.T) {
func TestMarkdownRender(t *testing.T) {
//t.Skip()
err := initMarkdown()
err := initMarkdown(common.Plugins["markdown"])
if err != nil {
t.Fatal(err)
}

View File

@ -17,6 +17,9 @@ go build -o QueryGen "./cmd/query_gen"
echo "Running the query generator"
./QueryGen
echo "Generating the JSON handlers"
easyjson -pkg common
echo "Building Gosora"
go build -o Gosora

View File

@ -303,6 +303,39 @@ function runWebSockets() {
});
})();
// TODO: Use these in .filter_item and pass back an item count from the backend to work with here
// Ported from common/parser.go
function PageOffset(count, page, perPage) {
let offset = 0;
let lastPage = LastPage(count, perPage)
if(page > 1) {
offset = (perPage * page) - perPage
} else if (page == -1) {
page = lastPage
offset = (perPage * page) - perPage
} else {
page = 1
}
// We don't want the offset to overflow the slices, if everything's in memory
if(offset >= (count - 1)) offset = 0;
return {Offset:offset, Page:page, LastPage:lastPage}
}
function LastPage(count, perPage) {
return (count / perPage) + 1
}
function Paginate(count, perPage, maxPages) {
if(count < perPage) return [1];
let page = 0;
let out = [];
for(let current = 0; current < count; current += perPage){
page++;
out.push(page);
if(out.length >= maxPages) break;
}
return out;
}
function mainInit(){
runInitHook("start_init");
@ -343,9 +376,7 @@ function mainInit(){
error: ajaxError,
success: function (data, status, xhr) {
if("success" in data) {
if(data["success"] == "1") {
return;
}
if(data["success"] == "1") return;
}
// addNotice("Failed to add a like: {err}")
likeButton.classList.add("add_like");
@ -369,6 +400,102 @@ function mainInit(){
}
});
function rebuildPaginator(lastPage) {
let urlParams = new URLSearchParams(window.location.search);
let page = urlParams.get('page');
if(page=="") page = 1;
let stopAtPage = lastPage;
if(stopAtPage>5) stopAtPage = 5;
let pageList = [];
for(let i = 0; i < stopAtPage;i++) pageList.push(i+1);
//$(".pageset").html(Template_paginator({PageList: pageList, Page: page, LastPage: lastPage}));
let ok = false;
$(".pageset").each(function(){
this.outerHTML = Template_paginator({PageList: pageList, Page: page, LastPage: lastPage});
ok = true;
});
if(!ok) {
$(Template_paginator({PageList: pageList, Page: page, LastPage: lastPage})).insertAfter("#topic_list");
}
}
function rebindPaginator() {
$(".pageitem a").unbind("click");
$(".pageitem a").click(function() {
event.preventDefault();
// TODO: Take mostviewed into account
let url = "//"+window.location.host+window.location.pathname;
let urlParams = new URLSearchParams(window.location.search);
urlParams.set("page",new URLSearchParams(this.getAttribute("href")).get("page"));
let q = "?";
for(let item of urlParams.entries()) q += item[0]+"="+item[1]+"&";
if(q.length>1) q = q.slice(0,-1);
// TODO: Try to de-duplicate some of these fetch calls
fetch(url+q+"&js=1", {credentials: "same-origin"})
.then((resp) => resp.json())
.then((data) => {
if(!"Topics" in data) throw("no Topics in data");
let topics = data["Topics"];
// TODO: Fix the data race where the function hasn't been loaded yet
let out = "";
for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]);
$(".topic_list").html(out);
let obj = {Title: document.title, Url: url+q};
history.pushState(obj, obj.Title, obj.Url);
rebuildPaginator(data.LastPage);
rebindPaginator();
}).catch((ex) => {
console.log("Unable to get script '"+url+q+"&js=1"+"'");
console.log("ex: ", ex);
console.trace();
});
});
}
// TODO: Render a headless topics.html instead of the individual topic rows and a bit of JS glue
$(".filter_item").click(function(event) {
if(!window.location.pathname.startsWith("/topics/")) return
event.preventDefault();
let that = this;
let fid = this.getAttribute("data-fid");
// TODO: Take mostviewed into account
let url = "//"+window.location.host+"/topics/?fids="+fid;
fetch(url+"&js=1", {credentials: "same-origin"})
.then((resp) => resp.json())
.then((data) => {
if(!"Topics" in data) throw("no Topics in data");
let topics = data["Topics"];
// TODO: Fix the data race where the function hasn't been loaded yet
let out = "";
for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]);
$(".topic_list").html(out);
let obj = {Title: document.title, Url: url};
history.pushState(obj, obj.Title, obj.Url);
rebuildPaginator(data.LastPage)
rebindPaginator();
$(".filter_item").each(function(){
this.classList.remove("filter_selected");
});
that.classList.add("filter_selected");
$(".topic_list_title h1").text(that.innerText);
}).catch((ex) => {
console.log("Unable to get script '"+url+"&js=1"+"'");
console.log("ex: ", ex);
console.trace();
});
});
if (document.getElementById("topicsItemList")!==null) rebindPaginator();
if (document.getElementById("forumItemList")!==null) rebindPaginator();
$(".open_edit").click((event) => {
event.preventDefault();
$('.hide_on_edit').addClass("edit_opened");

View File

@ -90,12 +90,23 @@ function loadScript(name, callback) {
});
}
/*
function loadTmpl(name,callback) {
let url = "/static/"+name
let worker = new Worker(url);
}
*/
function DoNothingButPassBack(item) {
return item;
}
function RelativeTime(date) {
return date;
}
function initPhrases() {
fetchPhrases("status,topic_list,alerts")
fetchPhrases("status,topic_list,alerts,paginator")
}
function fetchPhrases(plist) {
@ -131,6 +142,19 @@ function fetchPhrases(plist) {
(() => {
runInitHook("pre_iife");
let toLoad = 2;
// TODO: Shunt this into loggedIn if there aren't any search and filter widgets?
loadScript("template_topics_topic.js", () => {
console.log("Loaded template_topics_topic.js");
toLoad--;
if(toLoad===0) initPhrases();
});
loadScript("template_paginator.js", () => {
console.log("Loaded template_paginator.js");
toLoad--;
if(toLoad===0) initPhrases();
});
let loggedIn = document.head.querySelector("[property='x-loggedin']").content;
if(loggedIn=="true") {
fetch("/api/me/")
@ -141,13 +165,6 @@ function fetchPhrases(plist) {
me = data;
runInitHook("pre_init");
});
let toLoad = 1;
loadScript("template_topics_topic.js", () => {
console.log("Loaded template_topics_topic.js");
toLoad--;
if(toLoad===0) initPhrases();
});
} else {
me = {User:{ID:0,Session:""},Site:{"MaxRequestSize":0}};
runInitHook("pre_init");

View File

@ -63,6 +63,10 @@ func (build *Accumulator) prepare(res string, err error) *sql.Stmt {
return stmt
}
func (build *Accumulator) RawPrepare(res string) *sql.Stmt {
return build.prepare(res, nil)
}
func (build *Accumulator) query(query string, args ...interface{}) (rows *sql.Rows, err error) {
err = build.FirstError()
if err != nil {

View File

@ -379,7 +379,7 @@ type GenRouter struct {
}
func NewGenRouter(uploads http.Handler) (*GenRouter, error) {
f, err := os.OpenFile("./logs/requests.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
f, err := os.OpenFile("./logs/reqs-"+strconv.FormatInt(common.StartTime.Unix(),10)+".log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
if err != nil {
return nil, err
}

View File

@ -1,7 +1,7 @@
/*
*
* Gosora Route Handlers
* Copyright Azareal 2016 - 2019
* Copyright Azareal 2016 - 2020
*
*/
package main
@ -138,6 +138,13 @@ var cacheControlMaxAge = "max-age=" + strconv.Itoa(int(common.Day))
// TODO: Be careful with exposing the panel phrases here, maybe move them into a different namespace? We also need to educate the admin that phrases aren't necessarily secret
// TODO: Move to the routes package
var phraseWhitelist = []string{
"topic",
"status",
"alerts",
"paginator",
}
func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
// TODO: Don't make this too JSON dependent so that we can swap in newer more efficient formats
h := w.Header()
@ -148,7 +155,6 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user common.User) c
if err != nil {
return common.PreErrorJS("Bad Form", w, r)
}
query := r.FormValue("query")
if query == "" {
return common.PreErrorJS("No query provided", w, r)
@ -183,12 +189,20 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user common.User) c
var plist map[string]string
// A little optimisation to avoid copying entries from one map to the other, if we don't have to mutate it
// TODO: Reduce the amount of duplication here
if len(positives) > 1 {
plist = make(map[string]string)
for _, positive := range positives {
// ! Constrain it to topic and status phrases for now
if !strings.HasPrefix(positive, "topic") && !strings.HasPrefix(positive, "status") && !strings.HasPrefix(positive, "alerts") {
return common.PreErrorJS("Not implemented!", w, r)
// ! Constrain it to a subset of phrases for now
var ok = false
for _, item := range phraseWhitelist {
if strings.HasPrefix(positive, item) {
ok = true
break
}
}
if !ok {
return common.PreErrorJS("Outside of phrase prefix whitelist", w, r)
}
pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positive)
if !ok {
@ -199,9 +213,16 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user common.User) c
}
}
} else {
// ! Constrain it to topic and status phrases for now
if !strings.HasPrefix(positives[0], "topic") && !strings.HasPrefix(positives[0], "status") && !strings.HasPrefix(positives[0], "alerts") {
return common.PreErrorJS("Not implemented!", w, r)
// ! Constrain it to a subset of phrases for now
var ok = false
for _, item := range phraseWhitelist {
if strings.HasPrefix(positives[0], item) {
ok = true
break
}
}
if !ok {
return common.PreErrorJS("Outside of phrase prefix whitelist", w, r)
}
pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positives[0])
if !ok {

View File

@ -2,6 +2,7 @@ package routes
import (
"database/sql"
"encoding/json"
"net/http"
"strconv"
@ -27,6 +28,7 @@ func init() {
})
}
// TODO: Retire this in favour of an alias for /topics/?
func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header, sfid string) common.RouteError {
page, _ := strconv.Atoi(r.FormValue("page"))
_, fid, err := ParseSEOURL(sfid)
@ -41,7 +43,6 @@ func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, header
if !user.Perms.ViewTopic {
return common.NoPermissions(w, r, user)
}
header.Zone = "view_forum"
header.Path = "/forums/"
// TODO: Fix this double-check
@ -108,6 +109,18 @@ func ViewForum(w http.ResponseWriter, r *http.Request, user common.User, header
topicItem.Creator = userList[topicItem.CreatedBy]
topicItem.LastUser = userList[topicItem.LastReplyBy]
}
header.Zone = "view_forum"
header.ZoneID = forum.ID
// TODO: Reduce the amount of boilerplate here
if r.FormValue("js") == "1" {
outBytes, err := json.Marshal(wsTopicList(topicList, lastPage))
if err != nil {
return common.InternalError(err, w, r)
}
w.Write(outBytes)
return nil
}
pageList := common.Paginate(forum.TopicCount, common.Config.ItemsPerPage, 5)
pi := common.ForumPage{header, topicList, forum, common.Paginator{pageList, page, lastPage}}

View File

@ -67,7 +67,7 @@ func CustomPage(w http.ResponseWriter, r *http.Request, user common.User, header
}
// ! Is this safe?
if common.Templates.Lookup("page_"+name+".html") == nil {
if common.DefaultTemplates.Lookup("page_"+name+".html") == nil {
return common.NotFound(w, r, header)
}
@ -77,7 +77,7 @@ func CustomPage(w http.ResponseWriter, r *http.Request, user common.User, header
if common.RunPreRenderHook("pre_render_tmpl_page", w, r, &user, &pi) {
return nil
}
err = common.Templates.ExecuteTemplate(w, "page_"+name+".html", pi)
err = header.Theme.RunTmpl("page_"+name, pi, w)
if err != nil {
return common.InternalError(err, w, r)
}

View File

@ -154,7 +154,7 @@ func AnalyticsViews(w http.ResponseWriter, r *http.Request, user common.User) co
common.DebugLogf("graph: %+v\n", graph)
pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range}
return renderTemplate("panel_analytics_views", w, r, user, &pi)
return renderTemplate("panel_analytics_views", w, r, basePage.Header, &pi)
}
func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user common.User, route string) common.RouteError {
@ -191,7 +191,7 @@ func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user common.Use
common.DebugLogf("graph: %+v\n", graph)
pi := common.PanelAnalyticsRoutePage{basePage, common.SanitiseSingleLine(route), graph, viewItems, timeRange.Range}
return renderTemplate("panel_analytics_route_views", w, r, user, &pi)
return renderTemplate("panel_analytics_route_views", w, r, basePage.Header, &pi)
}
func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.User, agent string) common.RouteError {
@ -234,7 +234,7 @@ func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user common.Use
}
pi := common.PanelAnalyticsAgentPage{basePage, agent, friendlyAgent, graph, timeRange.Range}
return renderTemplate("panel_analytics_agent_views", w, r, user, &pi)
return renderTemplate("panel_analytics_agent_views", w, r, basePage.Header, &pi)
}
func AnalyticsForumViews(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
@ -278,7 +278,7 @@ func AnalyticsForumViews(w http.ResponseWriter, r *http.Request, user common.Use
}
pi := common.PanelAnalyticsAgentPage{basePage, sfid, forum.Name, graph, timeRange.Range}
return renderTemplate("panel_analytics_forum_views", w, r, user, &pi)
return renderTemplate("panel_analytics_forum_views", w, r, basePage.Header, &pi)
}
func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user common.User, system string) common.RouteError {
@ -319,7 +319,7 @@ func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user common.Us
}
pi := common.PanelAnalyticsAgentPage{basePage, system, friendlySystem, graph, timeRange.Range}
return renderTemplate("panel_analytics_system_views", w, r, user, &pi)
return renderTemplate("panel_analytics_system_views", w, r, basePage.Header, &pi)
}
func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user common.User, lang string) common.RouteError {
@ -359,7 +359,7 @@ func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user common.
}
pi := common.PanelAnalyticsAgentPage{basePage, lang, friendlyLang, graph, timeRange.Range}
return renderTemplate("panel_analytics_lang_views", w, r, user, &pi)
return renderTemplate("panel_analytics_lang_views", w, r, basePage.Header, &pi)
}
func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user common.User, domain string) common.RouteError {
@ -393,7 +393,7 @@ func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user common.
common.DebugLogf("graph: %+v\n", graph)
pi := common.PanelAnalyticsAgentPage{basePage, common.SanitiseSingleLine(domain), "", graph, timeRange.Range}
return renderTemplate("panel_analytics_referrer_views", w, r, user, &pi)
return renderTemplate("panel_analytics_referrer_views", w, r, basePage.Header, &pi)
}
func AnalyticsTopics(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -428,7 +428,7 @@ func AnalyticsTopics(w http.ResponseWriter, r *http.Request, user common.User) c
common.DebugLogf("graph: %+v\n", graph)
pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range}
return renderTemplate("panel_analytics_topics", w, r, user, &pi)
return renderTemplate("panel_analytics_topics", w, r, basePage.Header, &pi)
}
func AnalyticsPosts(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -463,7 +463,7 @@ func AnalyticsPosts(w http.ResponseWriter, r *http.Request, user common.User) co
common.DebugLogf("graph: %+v\n", graph)
pi := common.PanelAnalyticsPage{basePage, graph, viewItems, timeRange.Range}
return renderTemplate("panel_analytics_posts", w, r, user, &pi)
return renderTemplate("panel_analytics_posts", w, r, basePage.Header, &pi)
}
func analyticsRowsToNameMap(rows *sql.Rows) (map[string]int, error) {
@ -526,7 +526,7 @@ func AnalyticsForums(w http.ResponseWriter, r *http.Request, user common.User) c
}
pi := common.PanelAnalyticsAgentsPage{basePage, forumItems, timeRange.Range}
return renderTemplate("panel_analytics_forums", w, r, user, &pi)
return renderTemplate("panel_analytics_forums", w, r, basePage.Header, &pi)
}
func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -559,7 +559,7 @@ func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, user common.User) c
}
pi := common.PanelAnalyticsRoutesPage{basePage, routeItems, timeRange.Range}
return renderTemplate("panel_analytics_routes", w, r, user, &pi)
return renderTemplate("panel_analytics_routes", w, r, basePage.Header, &pi)
}
func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -597,7 +597,7 @@ func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user common.User) c
}
pi := common.PanelAnalyticsAgentsPage{basePage, agentItems, timeRange.Range}
return renderTemplate("panel_analytics_agents", w, r, user, &pi)
return renderTemplate("panel_analytics_agents", w, r, basePage.Header, &pi)
}
func AnalyticsSystems(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -635,7 +635,7 @@ func AnalyticsSystems(w http.ResponseWriter, r *http.Request, user common.User)
}
pi := common.PanelAnalyticsAgentsPage{basePage, systemItems, timeRange.Range}
return renderTemplate("panel_analytics_systems", w, r, user, &pi)
return renderTemplate("panel_analytics_systems", w, r, basePage.Header, &pi)
}
func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -674,7 +674,7 @@ func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user common.User
}
pi := common.PanelAnalyticsAgentsPage{basePage, langItems, timeRange.Range}
return renderTemplate("panel_analytics_langs", w, r, user, &pi)
return renderTemplate("panel_analytics_langs", w, r, basePage.Header, &pi)
}
func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -707,5 +707,5 @@ func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, user common.User
}
pi := common.PanelAnalyticsAgentsPage{basePage, refItems, timeRange.Range}
return renderTemplate("panel_analytics_referrers", w, r, user, &pi)
return renderTemplate("panel_analytics_referrers", w, r, basePage.Header, &pi)
}

View File

@ -51,5 +51,5 @@ func Backups(w http.ResponseWriter, r *http.Request, user common.User, backupURL
}
pi := common.PanelBackupPage{basePage, backupList}
return renderTemplate("panel_backups", w, r, user, &pi)
return renderTemplate("panel_backups", w, r, basePage.Header, &pi)
}

View File

@ -21,12 +21,12 @@ func successRedirect(dest string, w http.ResponseWriter, r *http.Request, isJs b
return nil
}
func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, user common.User, pi interface{}) common.RouteError {
if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &user, pi) {
func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, header *common.Header, pi interface{}) common.RouteError {
if common.RunPreRenderHook("pre_render_"+tmplName, w, r, &header.CurrentUser, pi) {
return nil
}
// TODO: Prepend this with panel_?
err := common.Templates.ExecuteTemplate(w, tmplName+".html", pi)
err := header.Theme.RunTmpl(tmplName, pi, w)
if err != nil {
return common.InternalError(err, w, r)
}

View File

@ -209,5 +209,5 @@ func Dashboard(w http.ResponseWriter, r *http.Request, user common.User) common.
}
pi := common.PanelDashboardPage{basePage, gridElements}
return renderTemplate("panel_dashboard", w, r, user, &pi)
return renderTemplate("panel_dashboard", w, r, basePage.Header, &pi)
}

View File

@ -42,5 +42,5 @@ func Debug(w http.ResponseWriter, r *http.Request, user common.User) common.Rout
runtime.ReadMemStats(&memStats)
pi := common.PanelDebugPage{basePage, goVersion, dbVersion, uptime, openConnCount, qgen.Builder.GetAdapter().GetName(), goroutines, cpus, memStats}
return renderTemplate("panel_debug", w, r, user, &pi)
return renderTemplate("panel_debug", w, r, basePage.Header, &pi)
}

View File

@ -47,7 +47,7 @@ func Forums(w http.ResponseWriter, r *http.Request, user common.User) common.Rou
}
pi := common.PanelPage{basePage, forumList, nil}
return renderTemplate("panel_forums", w, r, user, &pi)
return renderTemplate("panel_forums", w, r, basePage.Header, &pi)
}
func ForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -96,19 +96,14 @@ func ForumsDelete(w http.ResponseWriter, r *http.Request, user common.User, sfid
return common.InternalError(err, w, r)
}
// TODO: Make this a phrase
confirmMsg := "Are you sure you want to delete the '" + forum.Name + "' forum?"
confirmMsg := phrases.GetTmplPhrasef("panel_forum_delete_are_you_sure", forum.Name)
yousure := common.AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirmMsg}
pi := common.PanelPage{basePage, tList, yousure}
if common.RunPreRenderHook("pre_render_panel_delete_forum", w, r, &user, &pi) {
return nil
}
err = common.Templates.ExecuteTemplate(w, "are_you_sure.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
return renderTemplate("panel_are_you_sure", w, r, basePage.Header, &pi)
}
func ForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
@ -184,15 +179,7 @@ func ForumsEdit(w http.ResponseWriter, r *http.Request, user common.User, sfid s
}
pi := common.PanelEditForumPage{basePage, forum.ID, forum.Name, forum.Desc, forum.Active, forum.Preset, gplist}
if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) {
return nil
}
err = common.Templates.ExecuteTemplate(w, "panel_forum_edit.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
return renderTemplate("panel_forum_edit", w, r, basePage.Header, &pi)
}
func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sfid string) common.RouteError {
@ -350,15 +337,7 @@ func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, user common.
}
pi := common.PanelEditForumGroupPage{basePage, forum.ID, gid, forum.Name, forum.Desc, forum.Active, forum.Preset, formattedPermList}
if common.RunPreRenderHook("pre_render_panel_edit_forum", w, r, &user, &pi) {
return nil
}
err = common.Templates.ExecuteTemplate(w, "panel_forum_edit_perms.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
return renderTemplate("panel_forum_edit_perms", w, r, basePage.Header, &pi)
}
func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, user common.User, paramList string) common.RouteError {

View File

@ -59,7 +59,7 @@ func Groups(w http.ResponseWriter, r *http.Request, user common.User) common.Rou
pageList := common.Paginate(basePage.Stats.Groups, perPage, 5)
pi := common.PanelGroupPage{basePage, groupList, common.Paginator{pageList, page, lastPage}}
return renderTemplate("panel_groups", w, r, user, &pi)
return renderTemplate("panel_groups", w, r, basePage.Header, &pi)
}
func GroupsEdit(w http.ResponseWriter, r *http.Request, user common.User, sgid string) common.RouteError {
@ -107,7 +107,7 @@ func GroupsEdit(w http.ResponseWriter, r *http.Request, user common.User, sgid s
disableRank := !user.Perms.EditGroupGlobalPerms || (group.ID == 6)
pi := common.PanelEditGroupPage{basePage, group.ID, group.Name, group.Tag, rank, disableRank}
return renderTemplate("panel_group_edit", w, r, user, pi)
return renderTemplate("panel_group_edit", w, r, basePage.Header, pi)
}
func GroupsEditPerms(w http.ResponseWriter, r *http.Request, user common.User, sgid string) common.RouteError {
@ -186,7 +186,7 @@ func GroupsEditPerms(w http.ResponseWriter, r *http.Request, user common.User, s
addGlobalPerm("UploadFiles", group.Perms.UploadFiles)
pi := common.PanelEditGroupPermsPage{basePage, group.ID, group.Name, localPerms, globalPerms}
return renderTemplate("panel_group_edit_perms", w, r, user, pi)
return renderTemplate("panel_group_edit_perms", w, r, basePage.Header, pi)
}
func GroupsEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sgid string) common.RouteError {

View File

@ -33,7 +33,7 @@ func LogsRegs(w http.ResponseWriter, r *http.Request, user common.User) common.R
pageList := common.Paginate(logCount, perPage, 5)
pi := common.PanelRegLogsPage{basePage, llist, common.Paginator{pageList, page, lastPage}}
return renderTemplate("panel_reglogs", w, r, user, &pi)
return renderTemplate("panel_reglogs", w, r, basePage.Header, &pi)
}
// TODO: Log errors when something really screwy is going on?
@ -125,7 +125,7 @@ func LogsMod(w http.ResponseWriter, r *http.Request, user common.User) common.Ro
pageList := common.Paginate(logCount, perPage, 5)
pi := common.PanelLogsPage{basePage, llist, common.Paginator{pageList, page, lastPage}}
return renderTemplate("panel_modlogs", w, r, user, &pi)
return renderTemplate("panel_modlogs", w, r, basePage.Header, &pi)
}
func LogsAdmin(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -152,5 +152,5 @@ func LogsAdmin(w http.ResponseWriter, r *http.Request, user common.User) common.
pageList := common.Paginate(logCount, perPage, 5)
pi := common.PanelLogsPage{basePage, llist, common.Paginator{pageList, page, lastPage}}
return renderTemplate("panel_adminlogs", w, r, user, &pi)
return renderTemplate("panel_adminlogs", w, r, basePage.Header, &pi)
}

View File

@ -33,7 +33,7 @@ func Pages(w http.ResponseWriter, r *http.Request, user common.User) common.Rout
pageList := common.Paginate(pageCount, perPage, 5)
pi := common.PanelCustomPagesPage{basePage, cPages, common.Paginator{pageList, page, lastPage}}
return renderTemplate("panel_pages", w, r, user, &pi)
return renderTemplate("panel_pages", w, r, basePage.Header, &pi)
}
func PagesCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -90,7 +90,7 @@ func PagesEdit(w http.ResponseWriter, r *http.Request, user common.User, spid st
}
pi := common.PanelCustomPageEditPage{basePage, page}
return renderTemplate("panel_pages_edit", w, r, user, &pi)
return renderTemplate("panel_pages_edit", w, r, basePage.Header, &pi)
}
func PagesEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, spid string) common.RouteError {

View File

@ -23,7 +23,7 @@ func Plugins(w http.ResponseWriter, r *http.Request, user common.User) common.Ro
}
pi := common.PanelPage{basePage, pluginList, nil}
return renderTemplate("panel_plugins", w, r, user, &pi)
return renderTemplate("panel_plugins", w, r, basePage.Header, &pi)
}
// TODO: Abstract more of the plugin activation / installation / deactivation logic, so we can test all that more reliably and easily
@ -51,7 +51,7 @@ func PluginsActivate(w http.ResponseWriter, r *http.Request, user common.User, u
}
if plugin.Activate != nil {
err = plugin.Activate()
err = plugin.Activate(plugin)
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
@ -70,7 +70,7 @@ func PluginsActivate(w http.ResponseWriter, r *http.Request, user common.User, u
}
log.Printf("Activating plugin '%s'", plugin.Name)
err = plugin.Init()
err = plugin.Init(plugin)
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
@ -106,7 +106,7 @@ func PluginsDeactivate(w http.ResponseWriter, r *http.Request, user common.User,
return common.InternalError(err, w, r)
}
if plugin.Deactivate != nil {
plugin.Deactivate()
plugin.Deactivate(plugin)
}
http.Redirect(w, r, "/panel/plugins/", http.StatusSeeOther)
@ -143,14 +143,14 @@ func PluginsInstall(w http.ResponseWriter, r *http.Request, user common.User, un
}
if plugin.Install != nil {
err = plugin.Install()
err = plugin.Install(plugin)
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
}
if plugin.Activate != nil {
err = plugin.Activate()
err = plugin.Activate(plugin)
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}
@ -170,7 +170,7 @@ func PluginsInstall(w http.ResponseWriter, r *http.Request, user common.User, un
}
log.Printf("Installing plugin '%s'", plugin.Name)
err = plugin.Init()
err = plugin.Init(plugin)
if err != nil {
return common.LocalError(err.Error(), w, r, user)
}

View File

@ -50,7 +50,7 @@ func Settings(w http.ResponseWriter, r *http.Request, user common.User) common.R
}
pi := common.PanelPage{basePage, tList, settingList}
return renderTemplate("panel_settings", w, r, user, &pi)
return renderTemplate("panel_settings", w, r, basePage.Header, &pi)
}
func SettingEdit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError {
@ -90,7 +90,7 @@ func SettingEdit(w http.ResponseWriter, r *http.Request, user common.User, sname
pSetting := &common.PanelSetting{setting, phrases.GetSettingPhrase(setting.Name)}
pi := common.PanelSettingPage{basePage, itemList, pSetting}
return renderTemplate("panel_setting", w, r, user, &pi)
return renderTemplate("panel_setting", w, r, basePage.Header, &pi)
}
func SettingEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, sname string) common.RouteError {

View File

@ -34,7 +34,7 @@ func Themes(w http.ResponseWriter, r *http.Request, user common.User) common.Rou
}
pi := common.PanelThemesPage{basePage, pThemeList, vThemeList}
return renderTemplate("panel_themes", w, r, user, &pi)
return renderTemplate("panel_themes", w, r, basePage.Header, &pi)
}
func ThemesSetDefault(w http.ResponseWriter, r *http.Request, user common.User, uname string) common.RouteError {
@ -86,7 +86,7 @@ func ThemesMenus(w http.ResponseWriter, r *http.Request, user common.User) commo
}
pi := common.PanelMenuListPage{basePage, menuList}
return renderTemplate("panel_themes_menus", w, r, user, &pi)
return renderTemplate("panel_themes_menus", w, r, basePage.Header, &pi)
}
func ThemesMenusEdit(w http.ResponseWriter, r *http.Request, user common.User, smid string) common.RouteError {
@ -133,7 +133,7 @@ func ThemesMenusEdit(w http.ResponseWriter, r *http.Request, user common.User, s
}
pi := common.PanelMenuPage{basePage, mid, menuList}
return renderTemplate("panel_themes_menus_items", w, r, user, &pi)
return renderTemplate("panel_themes_menus_items", w, r, basePage.Header, &pi)
}
func ThemesMenuItemEdit(w http.ResponseWriter, r *http.Request, user common.User, sitemID string) common.RouteError {
@ -159,7 +159,7 @@ func ThemesMenuItemEdit(w http.ResponseWriter, r *http.Request, user common.User
}
pi := common.PanelMenuItemPage{basePage, menuItem}
return renderTemplate("panel_themes_menus_item_edit", w, r, user, &pi)
return renderTemplate("panel_themes_menus_item_edit", w, r, basePage.Header, &pi)
}
func themesMenuItemSetters(r *http.Request, menuItem common.MenuItem) common.MenuItem {
@ -361,7 +361,7 @@ func ThemesWidgets(w http.ResponseWriter, r *http.Request, user common.User) com
}
pi := common.PanelWidgetListPage{basePage, docks, common.WidgetEdit{&common.Widget{ID: 0, Type: "simple"}, make(map[string]string)}}
return renderTemplate("panel_themes_widgets", w, r, user, &pi)
return renderTemplate("panel_themes_widgets", w, r, basePage.Header, &pi)
}
func widgetsParseInputs(r *http.Request, widget *common.Widget) (*common.WidgetEdit, error) {

View File

@ -25,7 +25,7 @@ func Users(w http.ResponseWriter, r *http.Request, user common.User) common.Rout
pageList := common.Paginate(basePage.Stats.Users, perPage, 5)
pi := common.PanelUserPage{basePage, users, common.Paginator{pageList, page, lastPage}}
return renderTemplate("panel_users", w, r, user, &pi)
return renderTemplate("panel_users", w, r, basePage.Header, &pi)
}
func UsersEdit(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError {
@ -75,14 +75,7 @@ func UsersEdit(w http.ResponseWriter, r *http.Request, user common.User, suid st
}
pi := common.PanelPage{basePage, groupList, targetUser}
if common.RunPreRenderHook("pre_render_panel_edit_user", w, r, &user, &pi) {
return nil
}
err = common.Templates.ExecuteTemplate(w, "panel_user_edit.html", pi)
if err != nil {
return common.InternalError(err, w, r)
}
return nil
return renderTemplate("panel_user_edit", w, r, basePage.Header, &pi)
}
func UsersEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, suid string) common.RouteError {

View File

@ -25,7 +25,7 @@ func WordFilters(w http.ResponseWriter, r *http.Request, user common.User) commo
}
pi := common.PanelPage{basePage, tList, filterList}
return renderTemplate("panel_word_filters", w, r, user, &pi)
return renderTemplate("panel_word_filters", w, r, basePage.Header, &pi)
}
func WordFiltersCreateSubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
@ -67,7 +67,7 @@ func WordFiltersEdit(w http.ResponseWriter, r *http.Request, user common.User, w
_ = wfid
pi := common.PanelPage{basePage, tList, nil}
return renderTemplate("panel_word_filters_edit", w, r, user, &pi)
return renderTemplate("panel_word_filters_edit", w, r, basePage.Header, &pi)
}
func WordFiltersEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, wfid string) common.RouteError {

View File

@ -4,17 +4,14 @@ import (
"log"
"net/http"
"strconv"
"strings"
"github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/common/phrases"
)
// TODO: Implement search
func TopicList(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header) common.RouteError {
header.Title = phrases.GetTitlePhrase("topics")
header.Zone = "topics"
header.Path = "/topics/"
header.MetaDesc = header.Settings["meta_desc"].(string)
group, err := common.Groups.Get(user.Group)
if err != nil {
log.Printf("Group #%d doesn't exist despite being used by common.User #%d", user.Group, user.ID)
@ -23,29 +20,70 @@ func TopicList(w http.ResponseWriter, r *http.Request, user common.User, header
// Get the current page
page, _ := strconv.Atoi(r.FormValue("page"))
sfids := r.FormValue("fids")
var fids []int
if sfids != "" {
for _, sfid := range strings.Split(sfids, ",") {
fid, err := strconv.Atoi(sfid)
if err != nil {
return common.LocalError("Invalid fid", w, r, user)
}
fids = append(fids, fid)
}
}
// TODO: Pass a struct back rather than passing back so many variables
var topicList []*common.TopicsRow
var forumList []common.Forum
var paginator common.Paginator
if user.IsSuperAdmin {
topicList, forumList, paginator, err = common.TopicList.GetList(page, "")
topicList, forumList, paginator, err = common.TopicList.GetList(page, "", fids)
} else {
topicList, forumList, paginator, err = common.TopicList.GetListByGroup(group, page, "")
topicList, forumList, paginator, err = common.TopicList.GetListByGroup(group, page, "", fids)
}
if err != nil {
return common.InternalError(err, w, r)
}
// ! Need an inline error not a page level error
if len(topicList) == 0 {
return common.NotFound(w, r, header)
}
// TODO: Reduce the amount of boilerplate here
if r.FormValue("js") == "1" {
outBytes, err := wsTopicList(topicList, paginator.LastPage).MarshalJSON()
if err != nil {
return common.InternalError(err, w, r)
}
w.Write(outBytes)
return nil
}
header.Title = phrases.GetTitlePhrase("topics")
header.Zone = "topics"
header.Path = "/topics/"
header.MetaDesc = header.Settings["meta_desc"].(string)
if len(fids) == 1 {
forum, err := common.Forums.Get(fids[0])
if err != nil {
return common.LocalError("Invalid fid forum", w, r, user)
}
header.Title = forum.Name
header.ZoneID = forum.ID
}
pi := common.TopicListPage{header, topicList, forumList, common.Config.DefaultForum, common.TopicListSort{"lastupdated", false}, paginator}
return renderTemplate("topics", w, r, header, pi)
}
func wsTopicList(topicList []*common.TopicsRow, lastPage int) *common.WsTopicList {
wsTopicList := make([]*common.WsTopicsRow, len(topicList))
for i, topicRow := range topicList {
wsTopicList[i] = topicRow.WebSockets()
}
return &common.WsTopicList{wsTopicList, lastPage}
}
func TopicListMostViewed(w http.ResponseWriter, r *http.Request, user common.User, header *common.Header) common.RouteError {
header.Title = phrases.GetTitlePhrase("topics")
header.Zone = "topics"
@ -60,25 +98,54 @@ func TopicListMostViewed(w http.ResponseWriter, r *http.Request, user common.Use
// Get the current page
page, _ := strconv.Atoi(r.FormValue("page"))
sfids := r.FormValue("fids")
var fids []int
if sfids != "" {
for _, sfid := range strings.Split(sfids, ",") {
fid, err := strconv.Atoi(sfid)
if err != nil {
return common.LocalError("Invalid fid", w, r, user)
}
fids = append(fids, fid)
}
if len(fids) == 1 {
forum, err := common.Forums.Get(fids[0])
if err != nil {
return common.LocalError("Invalid fid forum", w, r, user)
}
header.Title = forum.Name
header.ZoneID = forum.ID
}
}
// TODO: Pass a struct back rather than passing back so many variables
var topicList []*common.TopicsRow
var forumList []common.Forum
var paginator common.Paginator
if user.IsSuperAdmin {
topicList, forumList, paginator, err = common.TopicList.GetList(page, "most-viewed")
topicList, forumList, paginator, err = common.TopicList.GetList(page, "most-viewed", fids)
} else {
topicList, forumList, paginator, err = common.TopicList.GetListByGroup(group, page, "most-viewed")
topicList, forumList, paginator, err = common.TopicList.GetListByGroup(group, page, "most-viewed", fids)
}
if err != nil {
return common.InternalError(err, w, r)
}
// ! Need an inline error not a page level error
if len(topicList) == 0 {
return common.NotFound(w, r, header)
}
//MarshalJSON() ([]byte, error)
// TODO: Reduce the amount of boilerplate here
if r.FormValue("js") == "1" {
outBytes, err := wsTopicList(topicList, paginator.LastPage).MarshalJSON()
if err != nil {
return common.InternalError(err, w, r)
}
w.Write(outBytes)
return nil
}
pi := common.TopicListPage{header, topicList, forumList, common.Config.DefaultForum, common.TopicListSort{"mostviewed", false}, paginator}
return renderTemplate("topics", w, r, header, pi)
}

View File

@ -17,6 +17,9 @@ go build -o QueryGen "./cmd/query_gen"
echo "Running the query generator"
./QueryGen
echo "Generating the JSON handlers"
easyjson -pkg common
echo "Building Gosora"
go build -o Gosora

View File

@ -23,6 +23,9 @@ cd ../..
echo "Running the query generator"
./QueryGen
echo "Generating the JSON handlers"
easyjson -pkg common
echo "Building Gosora"
go build -o Gosora -tags no_ws

View File

@ -1,5 +1,7 @@
echo "Generating the dynamic code"
go generate
echo Generating the JSON handlers
easyjson -pkg common
echo "Running tests"
go build -o mssqlBuild -tags mssql
go test -coverprofile c.out

View File

@ -40,6 +40,9 @@ if %errorlevel% neq 0 (
exit /b %errorlevel%
)
echo Generating the JSON handlers
easyjson -pkg common
echo Building the executable
go build -o gosora.exe -tags no_ws
if %errorlevel% neq 0 (

View File

@ -40,6 +40,9 @@ if %errorlevel% neq 0 (
exit /b %errorlevel%
)
echo Generating the JSON handlers
easyjson -pkg common
echo Building the executable
go build -o gosora.exe
if %errorlevel% neq 0 (

View File

@ -40,6 +40,9 @@ if %errorlevel% neq 0 (
exit /b %errorlevel%
)
echo Generating the JSON handlers
easyjson -pkg common
echo Building the executable
go build -o gosora.exe -tags mssql
if %errorlevel% neq 0 (

View File

@ -40,6 +40,9 @@ if %errorlevel% neq 0 (
exit /b %errorlevel%
)
echo Generating the JSON handlers
easyjson -pkg common
echo Building the executable
go test
if %errorlevel% neq 0 (

View File

@ -40,6 +40,9 @@ if %errorlevel% neq 0 (
exit /b %errorlevel%
)
echo Generating the JSON handlers
easyjson -pkg common
echo Building the executable
go test -tags mssql
if %errorlevel% neq 0 (

View File

@ -15,6 +15,4 @@
</div>
{{end}}
</div>
{{if gt .LastPage 1}}
{{template "paginator.html" . }}
{{end}}
{{template "paginator.html" . }}

View File

@ -1,7 +1,7 @@
{{template "header.html" . }}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="{{lang "paginator_prev_page_aria"}}" rel="prev" href="{{.Forum.Link}}?page={{subtract .Page 1}}">{{lang "paginator_less_than"}}</a></div>{{end}}
{{if ne .LastPage .Page}}<div id="nextFloat" class="next_button"><a class="next_link" aria-label="{{lang "paginator_next_page_aria"}}" rel="next" href="{{.Forum.Link}}?page={{add .Page 1}}">{{lang "paginator_greater_than"}}</a></div>{{end}}
{{if gt .Page 1}}<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="{{lang "paginator.prev_page_aria"}}" rel="prev" href="{{.Forum.Link}}?page={{subtract .Page 1}}">{{lang "paginator.less_than"}}</a></div>{{end}}
{{if ne .LastPage .Page}}<div id="nextFloat" class="next_button"><a class="next_link" aria-label="{{lang "paginator.next_page_aria"}}" rel="next" href="{{.Forum.Link}}?page={{add .Page 1}}">{{lang "paginator.greater_than"}}</a></div>{{end}}
<link rel="canonical" href="//{{.Site.URL}}{{.Forum.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}" />
<main id="forumItemList" itemscope itemtype="http://schema.org/ItemList">
@ -40,25 +40,7 @@
</div>
</div>
</div>
<div class="formrow topic_content_row">
<div class="formitem">
<textarea form="quick_post_form" id="input_content" name="topic-content" placeholder="{{lang "quick_topic.content_placeholder"}}" required></textarea>
</div>
</div>
<div class="formrow poll_content_row auto_hide">
<div class="formitem">Poll stuff</div>
</div>
<div class="formrow quick_button_row">
<div class="formitem">
<button form="quick_post_form" name="topic-button" class="formbutton">{{lang "quick_topic.create_topic_button"}}</button>
<button form="quick_post_form" class="formbutton" id="add_poll_button">{{lang "quick_topic.add_poll_button"}}</button>
{{if .CurrentUser.Perms.UploadFiles}}
<input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">{{lang "quick_topic.add_file_button"}}</label>
<div id="upload_file_dock"></div>{{end}}
<button class="formbutton close_form">{{lang "quick_topic.cancel_button"}}</button>
</div>
</div>
{{template "topics_quick_topic.html" . }}
</div>
</div>
{{end}}
@ -100,9 +82,6 @@
</div>{{else}}<div class="rowitem passive rowmsg">{{lang "forum_no_topics"}}{{if .CurrentUser.Loggedin}}{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/{{.Forum.ID}}">{{lang "forum_start_one"}}</a>{{end}}{{end}}</div>{{end}}
</div>
{{if gt .LastPage 1}}
{{template "paginator.html" . }}
{{end}}
</main>
{{template "footer.html" . }}

View File

@ -1,10 +1,14 @@
{{if gt .LastPage 1}}
<div class="pageset">
{{if gt .Page 1}}<div class="pageitem"><a href="?page={{subtract .Page 1}}" rel="prev" aria-label="{{lang "paginator_prev_page_aria"}}">{{lang "paginator_prev_page"}}</a></div>
{{if gt .Page 1}}<div class="pageitem pagefirst"><a href="?page=1" rel="prev" aria-label="{{lang "paginator.first_page_aria"}}">{{lang "paginator.first_page"}}</a></div>
<div class="pageitem pageprev"><a href="?page={{subtract .Page 1}}" rel="prev" aria-label="{{lang "paginator.prev_page_aria"}}">{{lang "paginator.prev_page"}}</a></div>
<link rel="prev" href="?page={{subtract .Page 1}}" />{{end}}
{{range .PageList}}
<div class="pageitem"><a href="?page={{.}}">{{.}}</a></div>
{{end}}
{{if ne .LastPage .Page}}
<link rel="next" href="?page={{add .Page 1}}" />
<div class="pageitem"><a href="?page={{add .Page 1}}" rel="next" aria-label="{{lang "paginator_next_page_aria"}}">{{lang "paginator_next_page"}}</a></div>{{end}}
</div>
<div class="pageitem pagenext"><a href="?page={{add .Page 1}}" rel="next" aria-label="{{lang "paginator.next_page_aria"}}">{{lang "paginator.next_page"}}</a></div>
<div class="pageitem pagelast"><a href="?page={{.LastPage}}" rel="next" aria-label="{{lang "paginator.last_page_aria"}}">{{lang "paginator.last_page"}}</a></div>{{end}}
</div>
{{end}}

View File

@ -19,9 +19,7 @@
</div>
{{end}}
</div>
{{if gt .LastPage 1}}
{{template "paginator.html" . }}
{{end}}
</main>
</div>
{{template "footer.html" . }}

View File

@ -0,0 +1,15 @@
{{template "header.html" . }}
<div class="colstack panel_stack">
{{template "panel_menu.html" . }}
<main class="colstack_right">
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "areyousure_head"}}</h1></div>
</div>
<div class="colstack_item">
<div class="rowitem passive rowmsg">{{.Something.Message}}<br /><br />
<a class="username" href="{{.Something.URL}}?session={{.CurrentUser.Session}}">{{lang "areyousure_continue"}}</a>
</div>
</div>
</main>
</div>
{{template "footer.html" . }}

View File

@ -19,9 +19,8 @@
</div>
{{end}}
</div>
{{if gt .LastPage 1}}
{{template "paginator.html" . }}
{{end}}
{{if .CurrentUser.Perms.EditGroup}}
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_groups_create_head"}}</h1></div>

View File

@ -19,9 +19,7 @@
</div>
{{end}}
</div>
{{if gt .LastPage 1}}
{{template "paginator.html" . }}
{{end}}
</main>
</div>
{{template "footer.html" . }}

View File

@ -19,9 +19,7 @@
</div>
{{end}}
</div>
{{if gt .LastPage 1}}
{{template "paginator.html" . }}
{{end}}
</main>
</div>
{{template "footer.html" . }}

View File

@ -6,7 +6,7 @@
<option value="simple"{{if eq .Type "simple"}} selected{{end}}>{{lang "panel_themes_widgets_type_simple"}}</option>
<option value="wol"{{if eq .Type "wol"}} selected{{end}}>{{lang "panel_themes_widgets_type_wol"}}</option>
<option value="wol_context"{{if eq .Type "wol_context"}} selected{{end}}>{{lang "panel_themes_widgets_type_wol_context"}}</option>
<!--<option value="search_and_filter"{{if eq .Type "search_and_filter"}} selected{{end}}>{{lang "panel_themes_widgets_type_search_and_filter"}}</option>-->
<option value="search_and_filter"{{if eq .Type "search_and_filter"}} selected{{end}}>{{lang "panel_themes_widgets_type_search_and_filter"}}</option>
</select>
</div>
</div>

View File

@ -20,9 +20,7 @@
</div>
{{end}}
</div>
{{if gt .LastPage 1}}
{{template "paginator.html" . }}
{{end}}
</main>
</div>

View File

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

View File

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

View File

@ -2,11 +2,11 @@
<form id="edit_topic_form" action='/topic/edit/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}' method="post"></form>
{{if gt .Page 1}}<link rel="prev" href="{{.Topic.Link}}?page={{subtract .Page 1}}" />
<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="{{lang "paginator_prev_page_aria"}}" rel="prev" href="{{.Topic.Link}}?page={{subtract .Page 1}}">{{lang "paginator_less_than"}}</a></div>{{end}}
<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="{{lang "paginator.prev_page_aria"}}" rel="prev" href="{{.Topic.Link}}?page={{subtract .Page 1}}">{{lang "paginator.less_than"}}</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender next" href="{{.Topic.Link}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button">
<a class="next_link" aria-label="{{lang "paginator_next_page_aria"}}" rel="next" href="{{.Topic.Link}}?page={{add .Page 1}}">{{lang "paginator_greater_than"}}</a>
<a class="next_link" aria-label="{{lang "paginator.next_page_aria"}}" rel="next" href="{{.Topic.Link}}?page={{add .Page 1}}">{{lang "paginator.greater_than"}}</a>
</div>{{end}}
<link rel="canonical" href="//{{.Site.URL}}{{.Topic.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}" />

View File

@ -1,10 +1,10 @@
{{template "header.html" . }}
{{if gt .Page 1}}<link rel="prev" href="{{.Topic.Link}}?page={{subtract .Page 1}}" />
<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="{{lang "paginator_prev_page_aria"}}" rel="prev" href="{{.Topic.Link}}?page={{subtract .Page 1}}">{{lang "paginator_less_than"}}</a></div>{{end}}
<div id="prevFloat" class="prev_button"><a class="prev_link" aria-label="{{lang "paginator.prev_page_aria"}}" rel="prev" href="{{.Topic.Link}}?page={{subtract .Page 1}}">{{lang "paginator.less_than"}}</a></div>{{end}}
{{if ne .LastPage .Page}}<link rel="prerender next" href="{{.Topic.Link}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" aria-label="{{lang "paginator_next_page_aria"}}" rel="next" href="{{.Topic.Link}}?page={{add .Page 1}}">{{lang "paginator_greater_than"}}</a></div>{{end}}
<div id="nextFloat" class="next_button"><a class="next_link" aria-label="{{lang "paginator.next_page_aria"}}" rel="next" href="{{.Topic.Link}}?page={{add .Page 1}}">{{lang "paginator.greater_than"}}</a></div>{{end}}
<link rel="canonical" href="//{{.Site.URL}}{{.Topic.Link}}{{if gt .Page 1}}?page={{.Page}}{{end}}" />
@ -111,9 +111,7 @@
</article>
{{template "topic_alt_posts.html" . }}
</div>
{{if gt .LastPage 1}}
{{template "paginator.html" . }}
{{end}}
{{if .CurrentUser.Loggedin}}
{{if .CurrentUser.Perms.CreateReply}}

View File

@ -3,7 +3,7 @@
<main id="topicsItemList" itemscope itemtype="http://schema.org/ItemList">
<div class="rowblock rowhead topic_list_title_block{{if .CurrentUser.Loggedin}} has_opt{{end}}">
<div class="rowitem topic_list_title"><h1 itemprop="name">{{lang "topics_head"}}</h1></div>
<div class="rowitem topic_list_title"><h1 itemprop="name">{{.Title}}</h1></div>
{{if .CurrentUser.Loggedin}}
<div class="optbox">
{{if .ForumList}}
@ -55,7 +55,6 @@
</div>
<div class="rowblock topic_create_form quick_create_form" style="display: none;" aria-label="{{lang "quick_topic.aria"}}">
<form name="topic_create_form_form" id="quick_post_form" enctype="multipart/form-data" action="/topic/create/submit/?session={{.CurrentUser.Session}}" method="post"></form>
<input form="quick_post_form" id="has_poll_input" name="has_poll" value="0" type="hidden" />
<img class="little_row_avatar" src="{{.CurrentUser.MicroAvatar}}" height="64" alt="{{lang "quick_topic.avatar_alt"}}" title="{{lang "quick_topic.avatar_tooltip"}}" />
<div class="main_form">
<div class="topic_meta">
@ -70,31 +69,7 @@
</div>
</div>
</div>
<div class="formrow topic_content_row">
<div class="formitem">
<textarea form="quick_post_form" id="input_content" name="topic-content" placeholder="{{lang "quick_topic.content_placeholder"}}" required></textarea>
</div>
</div>
<div class="formrow poll_content_row auto_hide">
<div class="formitem">
<div class="pollinput" data-pollinput="0">
<input type="checkbox" disabled />
<label class="pollinputlabel"></label>
<input form="quick_post_form" name="pollinputitem[0]" class="pollinputinput" type="text" placeholder="{{lang "quick_topic.add_poll_option"}}" />
</div>
</div>
</div>
<div class="formrow quick_button_row">
<div class="formitem">
<button form="quick_post_form" class="formbutton">{{lang "quick_topic.create_topic_button"}}</button>
<button form="quick_post_form" class="formbutton" id="add_poll_button">{{lang "quick_topic.add_poll_button"}}</button>
{{if .CurrentUser.Perms.UploadFiles}}
<input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">{{lang "quick_topic.add_file_button"}}</label>
<div id="upload_file_dock"></div>{{end}}
<button class="formbutton close_form">{{lang "quick_topic.cancel_button"}}</button>
</div>
</div>
{{template "topics_quick_topic.html" . }}
</div>
</div>
{{end}}
@ -106,9 +81,6 @@
{{range .TopicList}}{{template "topics_topic.html" . }}{{else}}<div class="rowitem passive rowmsg">{{lang "topics_no_topics"}}{{if .CurrentUser.Loggedin}}{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/">{{lang "topics_start_one"}}</a>{{end}}{{end}}</div>{{end}}
</div>
{{if gt .LastPage 1}}
{{template "paginator.html" . }}
{{end}}
</main>
{{template "footer.html" . }}

View File

@ -0,0 +1,26 @@
<input form="quick_post_form" id="has_poll_input" name="has_poll" value="0" type="hidden" />
<div class="formrow topic_content_row">
<div class="formitem">
<textarea form="quick_post_form" id="input_content" name="topic-content" placeholder="{{lang "quick_topic.content_placeholder"}}" required></textarea>
</div>
</div>
<div class="formrow poll_content_row auto_hide">
<div class="formitem">
<div class="pollinput" data-pollinput="0">
<input type="checkbox" disabled />
<label class="pollinputlabel"></label>
<input form="quick_post_form" name="pollinputitem[0]" class="pollinputinput" type="text" placeholder="{{lang "quick_topic.add_poll_option"}}" />
</div>
</div>
</div>
<div class="formrow quick_button_row">
<div class="formitem">
<button form="quick_post_form" class="formbutton">{{lang "quick_topic.create_topic_button"}}</button>
<button form="quick_post_form" class="formbutton" id="add_poll_button">{{lang "quick_topic.add_poll_button"}}</button>
{{if .CurrentUser.Perms.UploadFiles}}
<input name="upload_files" form="quick_post_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">{{lang "quick_topic.add_file_button"}}</label>
<div id="upload_file_dock"></div>{{end}}
<button class="formbutton close_form">{{lang "quick_topic.cancel_button"}}</button>
</div>
</div>

View File

@ -2,6 +2,6 @@
<input name="widget_search" placeholder="Search" />
</div>
<div class="rowblock filter_list widget_filter">
{{range .Forums}} <div class="rowitem filter_item" data-fid="{{.ID}}">{{.Name}}</div>
{{range .Forums}} <div class="rowitem filter_item{{if .Selected}} filter_selected{{end}}" data-fid="{{.ID}}"><a href="/topics/?fids={{.ID}}" rel="nofollow">{{.Name}}</a></div>
{{end}}
</div>

View File

@ -1,6 +1,6 @@
<div class="rowblock rowhead">
<div class="rowblock rowhead widget_simple">
<div class="rowitem"><h1>{{.Name}}</h1></div>
</div>
<div class="rowblock">
<div class="rowblock widget_simple">
<div class="rowitem">{{.Text}}</div>
</div>

View File

@ -1415,6 +1415,12 @@ textarea {
.pageitem:last-child {
border-right: 1px solid var(--element-border-color);
}
.pagefirst, .pagenext, .pageprev, .pagelast {
padding-top: 5px;
}
.pagefirst a, .pagenext a, .pageprev a, .pagelast a {
font-size: 18px;
}
/* TODO: Make widget_about's CSS less footer centric */
.footerBit, .footer .widget {

View File

@ -0,0 +1 @@
{{template "profile_comments_row_alt.html" . }}

View File

@ -156,6 +156,11 @@ li a {
.sidebar {
width: 320px;
}
.widget_simple .rowitem {
line-height: 18px;
padding-top: 14px !important;
padding-bottom: 14px !important;
}
.the_form {
border-radius: 3px;
background-color: #444444;
@ -193,6 +198,9 @@ li a {
margin-bottom: 5px;
padding: 4px;
}
.filter_item a {
color: #BBBBBB;
}
.colstack_right .colstack_item:not(.colstack_head):not(.rowhead) .rowitem:not(:last-child) {
margin-bottom: 8px;
}
@ -214,6 +222,22 @@ h1, h2, h3, h4, h5 {
white-space: nowrap;
}
/* new */
.filter_list {
margin-top: 5px;
background-color: #444444;
margin-left: 12px;
border-radius: 3px;
}
.filter_item {
margin-left: 0px !important;
}
.filter_selected {
background-color: #555555 !important;
border-radius: 0px !important;
}
/* new end */
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
@ -586,7 +610,6 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) {
display: flex;
margin-top: 8px;
}
.pageitem {
font-size: 17px;
border-radius: 3px;
@ -594,6 +617,13 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) {
padding: 7px;
margin-right: 6px;
}
.pagefirst, .pagenext, .pageprev, .pagelast {
padding-top: 2px;
padding-bottom: 6px;
}
.pagefirst a, .pagenext a, .pageprev a, .pagelast a {
font-size: 22px;
}
#prevFloat, #nextFloat {
display: none;
@ -934,13 +964,15 @@ input[type=checkbox]:checked + label .sel {
margin-top: 4px;
}
.rowlist.bgavatars.not_grid .bgsub {
height: 36px;
width: 36px;
margin-left: 8px;
margin-right: 12px;
height: 28px;
width: 28px;
margin-left: 4px;
margin-right: 10px;
}
.rowlist.bgavatars.not_grid .rowTitle {
font-size: 17px;
margin-left: 0px;
margin-top: 0px;
}
.ip_search_block {

View File

@ -890,6 +890,13 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
text-decoration: none;
}
.sidebar .rowhead:not(:first-child) {
margin-top: 12px;
}
.widget_search {
margin-bottom: 8px;
}
#profile_comments .rowitem {
background-repeat: no-repeat, repeat-y;
background-size: 128px;