1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-04 08:06:23 -07:00

HEAVILY simplify template execution

This commit is contained in:
Joshua Merrell 2018-04-06 01:03:57 -07:00
parent 5745428655
commit ce303c086e
20 changed files with 395 additions and 533 deletions

View file

@ -3,7 +3,7 @@ GOCHAN_DEBUG=1
GOCHAN_VERBOSE=2 GOCHAN_VERBOSE=2
GOCHAN_VERBOSITY=0 # This is set by "make release/debug/verbose" GOCHAN_VERBOSITY=0 # This is set by "make release/debug/verbose"
GOCHAN_VERSION=1.9.1 GOCHAN_VERSION=1.9.2
GOCHAN_BUILDTIME=$(shell date +%y%m%d.%H%M) GOCHAN_BUILDTIME=$(shell date +%y%m%d.%H%M)
ifeq ($(GOOS), windows) ifeq ($(GOOS), windows)
GOCHAN_BIN=gochan.exe GOCHAN_BIN=gochan.exe

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VERSION=1.9.1 VERSION=1.9.2
GOOS_ORIG=$GOOS GOOS_ORIG=$GOOS
function copyStuff { function copyStuff {

View file

@ -7,6 +7,6 @@
<h1>404: File not found</h1> <h1>404: File not found</h1>
<img src="/error/lol 404.gif" border="0" alt=""> <img src="/error/lol 404.gif" border="0" alt="">
<p>The requested file could not be found on this server. Are you just typing random stuff in the address bar? If you followed a link from this site here, then post <a href="/site">here</a></p> <p>The requested file could not be found on this server. Are you just typing random stuff in the address bar? If you followed a link from this site here, then post <a href="/site">here</a></p>
<hr><address>http://gochan.org powered by Gochan v1.9.1</address> <hr><address>http://gochan.org powered by Gochan v1.9.2</address>
</body> </body>
</html> </html>

View file

@ -7,6 +7,6 @@
<h1>500: Internal Server error</h1> <h1>500: Internal Server error</h1>
<img src="/error/derpy server.gif" border="0" alt=""> <img src="/error/derpy server.gif" border="0" alt="">
<p>The server encountered an error while trying to serve the page, and we apologize for the inconvenience. The <a href="https://en.wikipedia.org/wiki/Idiot">system administrator</a> will try to fix things as soon has he/she/it can.</p> <p>The server encountered an error while trying to serve the page, and we apologize for the inconvenience. The <a href="https://en.wikipedia.org/wiki/Idiot">system administrator</a> will try to fix things as soon has he/she/it can.</p>
<hr><address>http://gochan.org powered by Gochan v1.9.1</address> <hr><address>http://gochan.org powered by Gochan v1.9.2</address>
</body> </body>
</html> </html>

View file

@ -1,16 +1,17 @@
package main package main
import ( import (
"os"
"strconv" "strconv"
) )
// set in build.sh via -ldflags // set in Makefile via -ldflags
var version string var version string
// verbose = 0 for no debugging info. Critical errors and general output only // verbose = 0 for no debugging info. Critical errors and general output only
// verbose = 1 for non-critical warnings and important info // verbose = 1 for non-critical warnings and important info
// verbose = 2 for all debugging/benchmarks/warnings // verbose = 2 for all debugging/benchmarks/warnings
// set in build.sh via -ldflags // set in Makefile via -ldflags
var verbosity_str string var verbosity_str string
var buildtime_str string // set in build.sh, format: YRMMDD.HHMM var buildtime_str string // set in build.sh, format: YRMMDD.HHMM
@ -24,7 +25,11 @@ func main() {
connectToSQLServer() connectToSQLServer()
println(0, "Loading and parsing templates...") println(0, "Loading and parsing templates...")
initTemplates() if err := initTemplates(); err != nil {
println(0, err.Error())
os.Exit(2)
}
println(0, "Initializing server...") println(0, "Initializing server...")
if db != nil { if db != nil {
db.Exec("USE `" + config.DBname + "`;") db.Exec("USE `" + config.DBname + "`;")

View file

@ -206,7 +206,7 @@ var manage_functions = map[string]ManageFunction{
} }
manageConfigBuffer := bytes.NewBufferString("") manageConfigBuffer := bytes.NewBufferString("")
if err := renderTemplate(manage_config_tmpl, "manage_config", manageConfigBuffer); err != nil { if err := manage_config_tmpl.Execute(manageConfigBuffer, nil); err != nil {
html += handleError(1, err.Error()) html += handleError(1, err.Error())
return return
} }
@ -733,10 +733,10 @@ var manage_functions = map[string]ManageFunction{
} }
all_sections, _ = getSectionArr("") all_sections, _ = getSectionArr("")
if err := renderTemplate(manage_boards_tmpl, "manage_boards", manageBoardsBuffer, if err := manage_boards_tmpl.Execute(manageBoardsBuffer, map[string]interface{}{
&Wrapper{IName: "board", Data: []interface{}{board}}, "board": []interface{}{board},
&Wrapper{IName: "section_arr", Data: all_sections}, "section_arr": all_sections,
); err != nil { }); err != nil {
html += handleError(1, err.Error()) html += handleError(1, err.Error())
return return
} }
@ -807,8 +807,7 @@ var manage_functions = map[string]ManageFunction{
return return
} }
for _, postInter := range posts { for _, post := range posts {
post := postInter.(PostTable)
stmt, err := db.Prepare("UPDATE `" + config.DBprefix + "posts` SET `message` = ? WHERE `id` = ? AND `boardid` = ?") stmt, err := db.Prepare("UPDATE `" + config.DBprefix + "posts` SET `message` = ? WHERE `id` = ? AND `boardid` = ?")
if err != nil { if err != nil {
html += handleError(1, err.Error()) + "<br />" html += handleError(1, err.Error()) + "<br />"

View file

@ -68,7 +68,6 @@ func buildBoards(all bool, which int) (html string) {
// build archive pages for. The return value is a string of HTML with debug information from the build process. // build archive pages for. The return value is a string of HTML with debug information from the build process.
func buildBoardPages(board *BoardsTable) (html string) { func buildBoardPages(board *BoardsTable) (html string) {
// start_time := benchmarkTimer("buildBoard"+strconv.Itoa(board.ID), time.Now(), true) // start_time := benchmarkTimer("buildBoard"+strconv.Itoa(board.ID), time.Now(), true)
var boardinfo_i []interface{}
var current_page_file *os.File var current_page_file *os.File
var threads []interface{} var threads []interface{}
var thread_pages [][]interface{} var thread_pages [][]interface{}
@ -76,14 +75,10 @@ func buildBoardPages(board *BoardsTable) (html string) {
var nonstickied_threads []interface{} var nonstickied_threads []interface{}
defer func() { defer func() {
// This function cleans up after we return. If there was an error, it prints on the log and the console. // Recover and print, log error (if there is one)
if uhoh, ok := recover().(error); ok { /* if errmsg, panicked := recover().(error); panicked {
errorLog.Print("buildBoardPages failed: " + uhoh.Error()) handleError(0, "Recovered from panic: "+errmsg.Error())
println(0, "buildBoardPages failed: "+uhoh.Error()) } */
}
if current_page_file != nil {
current_page_file.Close()
}
}() }()
// Check that the board's configured directory is indeed a directory // Check that the board's configured directory is indeed a directory
@ -109,20 +104,16 @@ func buildBoardPages(board *BoardsTable) (html string) {
}, " ORDER BY `bumped` DESC") }, " ORDER BY `bumped` DESC")
if err != nil { if err != nil {
html += handleError(1, err.Error()) + "<br />" html += handleError(1, err.Error()) + "<br />"
op_posts = nil //make([]interface{}, 0) op_posts = nil
return return
} }
// For each top level post, start building a Thread struct // For each top level post, start building a Thread struct
for _, op_post_i := range op_posts { for _, op := range op_posts {
var thread Thread var thread Thread
var posts_in_thread []interface{} var posts_in_thread []PostTable
op_post := op_post_i.(PostTable)
thread.IName = "thread" thread.IName = "thread"
// Store the OP post for this thread
//op_post := op_post_i.(PostTable)
// Get the number of replies to this thread. // Get the number of replies to this thread.
stmt, err := db.Prepare("SELECT COUNT(*) FROM `" + config.DBprefix + "posts` WHERE `boardid` = ? AND `parentid` = ? AND `deleted_timestamp` = ?") stmt, err := db.Prepare("SELECT COUNT(*) FROM `" + config.DBprefix + "posts` WHERE `boardid` = ? AND `parentid` = ? AND `deleted_timestamp` = ?")
if err != nil { if err != nil {
@ -130,7 +121,7 @@ func buildBoardPages(board *BoardsTable) (html string) {
} }
defer closeStatement(stmt) defer closeStatement(stmt)
if err = stmt.QueryRow(board.ID, op_post.ID, nil_timestamp).Scan(&thread.NumReplies); err != nil { if err = stmt.QueryRow(board.ID, op.ID, nil_timestamp).Scan(&thread.NumReplies); err != nil {
html += err.Error() + "<br />\n" html += err.Error() + "<br />\n"
} }
@ -139,15 +130,15 @@ func buildBoardPages(board *BoardsTable) (html string) {
if err != nil { if err != nil {
html += err.Error() + "<br />\n" html += err.Error() + "<br />\n"
} }
if err = stmt.QueryRow(board.ID, op_post.ID, nil_timestamp).Scan(&thread.NumImages); err != nil { if err = stmt.QueryRow(board.ID, op.ID, nil_timestamp).Scan(&thread.NumImages); err != nil {
html += err.Error() + "<br />\n" html += err.Error() + "<br />\n"
} }
thread.OP = op_post thread.OP = op
var numRepliesOnBoardPage int var numRepliesOnBoardPage int
if op_post.Stickied { if op.Stickied {
// If the thread is stickied, limit replies on the archive page to the // If the thread is stickied, limit replies on the archive page to the
// configured value for stickied threads. // configured value for stickied threads.
numRepliesOnBoardPage = config.StickyRepliesOnBoardPage numRepliesOnBoardPage = config.StickyRepliesOnBoardPage
@ -158,23 +149,27 @@ func buildBoardPages(board *BoardsTable) (html string) {
posts_in_thread, err = getPostArr(map[string]interface{}{ posts_in_thread, err = getPostArr(map[string]interface{}{
"boardid": board.ID, "boardid": board.ID,
"parentid": op_post.ID, "parentid": op.ID,
"deleted_timestamp": nil_timestamp, "deleted_timestamp": nil_timestamp,
}, fmt.Sprintf(" ORDER BY `id` DESC LIMIT %d", numRepliesOnBoardPage)) }, fmt.Sprintf(" ORDER BY `id` DESC LIMIT %d", numRepliesOnBoardPage))
if err != nil { if err != nil {
html += err.Error() + "<br />" html += err.Error() + "<br />"
} }
posts_in_thread = reverse(posts_in_thread) var reversedPosts []PostTable
for i := len(posts_in_thread); i > 0; i-- {
reversedPosts = append(reversedPosts, posts_in_thread[i-1])
}
if len(posts_in_thread) > 0 { if len(posts_in_thread) > 0 {
// Store the posts to show on board page // Store the posts to show on board page
thread.BoardReplies = posts_in_thread //thread.BoardReplies = posts_in_thread
thread.BoardReplies = reversedPosts
// Count number of images on board page // Count number of images on board page
image_count := 0 image_count := 0
for _, reply := range posts_in_thread { for _, reply := range posts_in_thread {
if reply.(PostTable).Filesize != 0 { if reply.Filesize != 0 {
image_count++ image_count++
} }
} }
@ -183,7 +178,7 @@ func buildBoardPages(board *BoardsTable) (html string) {
} }
// Add thread struct to appropriate list // Add thread struct to appropriate list
if op_post.Stickied { if op.Stickied {
stickied_threads = append(stickied_threads, thread) stickied_threads = append(stickied_threads, thread)
} else { } else {
nonstickied_threads = append(nonstickied_threads, thread) nonstickied_threads = append(nonstickied_threads, thread)
@ -197,10 +192,7 @@ func buildBoardPages(board *BoardsTable) (html string) {
// If there are no posts on the board // If there are no posts on the board
if len(threads) == 0 { if len(threads) == 0 {
board.CurrentPage = 0 board.CurrentPage = 1
boardinfo_i = nil
boardinfo_i = append(boardinfo_i, board)
// Open board.html for writing to the first page. // Open board.html for writing to the first page.
board_page_file, err := os.OpenFile(path.Join(config.DocumentRoot, board.Dir, "board.html"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) board_page_file, err := os.OpenFile(path.Join(config.DocumentRoot, board.Dir, "board.html"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
if err != nil { if err != nil {
@ -210,30 +202,13 @@ func buildBoardPages(board *BoardsTable) (html string) {
// Render board page template to the file, // Render board page template to the file,
// packaging the board/section list, threads, and board info // packaging the board/section list, threads, and board info
if err = renderTemplate(img_header_tmpl, "img_header", board_page_file, if err = img_boardpage_tmpl.Execute(board_page_file, map[string]interface{}{
&Wrapper{IName: "boards_", Data: all_boards}, "config": config,
&Wrapper{IName: "sections_w", Data: all_sections}, "boards": all_boards,
&Wrapper{IName: "posts_w", Data: []interface{}{ "sections": all_sections,
PostTable{BoardID: board.ID}, "threads": threads,
}}, "board": board,
&Wrapper{IName: "op", Data: []interface{}{PostTable{}}}, }); err != nil {
&Wrapper{IName: "board", Data: []interface{}{board}},
); err != nil {
html += handleError(1, fmt.Sprintf("Failed building /%s/res/%d.html: %s", board.Dir, 0, err.Error())) + "<br />"
return
}
if err = renderTemplate(img_boardpage_tmpl, "boardpage", board_page_file,
&Wrapper{IName: "boards", Data: all_boards},
&Wrapper{IName: "sections", Data: all_sections},
&Wrapper{IName: "threads", Data: threads},
&Wrapper{IName: "boardinfo", Data: boardinfo_i},
); err != nil {
html += handleError(1, "Failed building /"+board.Dir+"/: "+err.Error()) + "<br />"
return
}
if err = renderTemplate(global_footer_tmpl, "global_footer", board_page_file); err != nil {
html += handleError(1, "Failed building /"+board.Dir+"/: "+err.Error()) + "<br />" html += handleError(1, "Failed building /"+board.Dir+"/: "+err.Error()) + "<br />"
return return
} }
@ -244,7 +219,6 @@ func buildBoardPages(board *BoardsTable) (html string) {
} else { } else {
// Create the archive pages. // Create the archive pages.
thread_pages = paginate(config.ThreadsPerPage_img, threads) thread_pages = paginate(config.ThreadsPerPage_img, threads)
board.NumPages = len(thread_pages) - 1 board.NumPages = len(thread_pages) - 1
// Create array of page wrapper objects, and open the file. // Create array of page wrapper objects, and open the file.
@ -256,67 +230,49 @@ func buildBoardPages(board *BoardsTable) (html string) {
return return
} }
defer closeFile(catalog_json_file) defer closeFile(catalog_json_file)
currentBoardPage := board.CurrentPage
for page_num, page_threads := range thread_pages { for _, page_threads := range thread_pages {
// Package up board info for the template to use. board.CurrentPage++
board.CurrentPage = page_num
boardinfo_i = nil
boardinfo_i = append(boardinfo_i, board)
// Write to board.html for the first page.
var current_page_filepath string var current_page_filepath string
var pageFilename string pageFilename := strconv.Itoa(board.CurrentPage) + ".html"
if board.CurrentPage == 0 {
pageFilename = "board.html"
} else {
pageFilename = strconv.Itoa(page_num) + ".html"
}
current_page_filepath = path.Join(config.DocumentRoot, board.Dir, pageFilename) current_page_filepath = path.Join(config.DocumentRoot, board.Dir, pageFilename)
current_page_file, err = os.OpenFile(current_page_filepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) current_page_file, err = os.OpenFile(current_page_filepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
if err != nil { if err != nil {
html += handleError(1, "Failed opening board page: "+err.Error()) + "<br />" html += handleError(1, "Failed opening board page: "+err.Error()) + "<br />"
continue continue
} }
defer closeFile(current_page_file)
// Render the boardpage template, given boards, sections, threads, and board info // Render the boardpage template, don't forget config
if err = renderTemplate(img_header_tmpl, "img_header", current_page_file, if err = img_boardpage_tmpl.Execute(current_page_file, map[string]interface{}{
&Wrapper{IName: "boards_", Data: all_boards}, "config": config,
&Wrapper{IName: "sections_w", Data: all_sections}, "boards": all_boards,
&Wrapper{IName: "posts_w", Data: []interface{}{ "sections": all_sections,
"threads": page_threads,
"board": board,
"posts": []interface{}{
PostTable{BoardID: board.ID}, PostTable{BoardID: board.ID},
}}, },
&Wrapper{IName: "op", Data: []interface{}{PostTable{}}}, }); err != nil {
&Wrapper{IName: "board", Data: []interface{}{board}},
); err != nil {
html += handleError(1, "Failed building /"+board.Dir+"/ header: "+err.Error()) + "<br />"
return
}
if err = renderTemplate(img_boardpage_tmpl, "boardpage", current_page_file,
&Wrapper{IName: "boards", Data: all_boards},
&Wrapper{IName: "sections", Data: all_sections},
&Wrapper{IName: "threads", Data: page_threads},
&Wrapper{IName: "boardinfo", Data: boardinfo_i},
); err != nil {
html += handleError(1, "Failed building /"+board.Dir+"/ boardpage: "+err.Error()) + "<br />" html += handleError(1, "Failed building /"+board.Dir+"/ boardpage: "+err.Error()) + "<br />"
return return
} }
if err = renderTemplate(global_footer_tmpl, "global_footer", current_page_file); err != nil { if board.CurrentPage == 1 {
html += handleError(1, "Failed building /"+board.Dir+"/ footer: "+err.Error()) boardPage := path.Join(config.DocumentRoot, board.Dir, "board.html")
return os.Remove(boardPage)
if err = syscall.Symlink(current_page_filepath, boardPage); !os.IsExist(err) && err != nil {
html += handleError(1, "Failed building /"+board.Dir+"/: "+err.Error()) + "<br />"
}
} }
current_page_file.Close()
// Collect up threads for this page. // Collect up threads for this page.
var page_obj BoardPageJSON var page_obj BoardPageJSON
page_obj.Page = page_num page_obj.Page = board.CurrentPage
for _, thread_int := range page_threads { for _, thread_int := range page_threads {
thread := thread_int.(Thread) thread := thread_int.(Thread)
post_json := makePostJSON(thread.OP.(PostTable), board.Anonymous) post_json := makePostJSON(thread.OP, board.Anonymous)
var thread_json ThreadJSON var thread_json ThreadJSON
thread_json.PostJSON = &post_json thread_json.PostJSON = &post_json
thread_json.Replies = thread.NumReplies thread_json.Replies = thread.NumReplies
@ -332,7 +288,7 @@ func buildBoardPages(board *BoardsTable) (html string) {
thread_json.OmittedPosts = thread.NumReplies - config.RepliesOnBoardPage thread_json.OmittedPosts = thread.NumReplies - config.RepliesOnBoardPage
} }
} }
if thread.OP.(PostTable).Locked { if thread.OP.Locked {
thread_json.Locked = 1 thread_json.Locked = 1
} }
page_obj.Threads = append(page_obj.Threads, thread_json) page_obj.Threads = append(page_obj.Threads, thread_json)
@ -340,13 +296,13 @@ func buildBoardPages(board *BoardsTable) (html string) {
pages_obj = append(pages_obj, page_obj) pages_obj = append(pages_obj, page_obj)
} }
board.CurrentPage = currentBoardPage
catalog_json, err := json.Marshal(pages_obj) catalog_json, err := json.Marshal(pages_obj)
if err != nil { if err != nil {
html += handleError(1, "Failed to marshal to JSON: "+err.Error()) + "<br />" html += handleError(1, "Failed to marshal to JSON: "+err.Error()) + "<br />"
return return
} }
if _, err = catalog_json_file.Write(catalog_json); err != nil { if _, err = catalog_json_file.Write(catalog_json); err != nil {
html += handleError(1, "Failed writing /"+board.Dir+"/catalog.json: "+err.Error()) + "<br />" html += handleError(1, "Failed writing /"+board.Dir+"/catalog.json: "+err.Error()) + "<br />"
return return
@ -369,8 +325,7 @@ func buildThreads(all bool, boardid, threadid int) (html string) {
"deleted_timestamp": nil_timestamp, "deleted_timestamp": nil_timestamp,
}, "") }, "")
thread := threads[0] thread := threads[0]
thread_struct := thread.(PostTable) html += buildThreadPages(&thread) + "<br />\n"
html += buildThreadPages(&thread_struct) + "<br />\n"
return return
} }
@ -382,8 +337,8 @@ func buildThreads(all bool, boardid, threadid int) (html string) {
if len(threads) == 0 { if len(threads) == 0 {
return return
} }
for _, op_i := range threads {
op := op_i.(PostTable) for _, op := range threads {
html += buildThreadPages(&op) + "<br />\n" html += buildThreadPages(&op) + "<br />\n"
} }
return return
@ -391,33 +346,23 @@ func buildThreads(all bool, boardid, threadid int) (html string) {
// buildThreadPages builds the pages for a thread given by a PostTable object. // buildThreadPages builds the pages for a thread given by a PostTable object.
func buildThreadPages(op *PostTable) (html string) { func buildThreadPages(op *PostTable) (html string) {
var boardDir string var replies []PostTable
var anonymous string
var replies []interface{}
var current_page_file *os.File var current_page_file *os.File
board, err := getBoardFromID(op.BoardID)
stmt, err := db.Prepare("SELECT `dir`,`anonymous` FROM `" + config.DBprefix + "boards` WHERE `id` = ?")
if err != nil { if err != nil {
// possibly a syntax error? This normally shouldn't happen so this might be removed
html += handleError(1, err.Error()) html += handleError(1, err.Error())
return
}
if err = stmt.QueryRow(op.BoardID).Scan(&boardDir, &anonymous); err != nil {
html += handleError(1, "Failed getting board directory and board's anonymous setting from post: "+err.Error())
return
} }
replies, err = getPostArr(map[string]interface{}{ replies, err = getPostArr(map[string]interface{}{
"boardid": op.BoardID, "boardid": op.BoardID,
"parentid": op.ID, "parentid": op.ID,
"deleted_timestamp": nil_timestamp, "deleted_timestamp": nil_timestamp,
}, "ORDER BY `ID` ASC") }, "ORDER BY `id` ASC")
if err != nil { if err != nil {
html += handleError(1, "Error building thread "+strconv.Itoa(op.ID)+":"+err.Error()) html += handleError(1, "Error building thread "+strconv.Itoa(op.ID)+":"+err.Error())
return return
} }
os.Remove(path.Join(config.DocumentRoot, boardDir, "res", strconv.Itoa(op.ID)+".html")) os.Remove(path.Join(config.DocumentRoot, board.Dir, "res", strconv.Itoa(op.ID)+".html"))
var repliesInterface []interface{} var repliesInterface []interface{}
for _, reply := range replies { for _, reply := range replies {
@ -426,47 +371,33 @@ func buildThreadPages(op *PostTable) (html string) {
//thread_pages := paginate(config.PostsPerThreadPage, replies) //thread_pages := paginate(config.PostsPerThreadPage, replies)
thread_pages := paginate(config.PostsPerThreadPage, repliesInterface) thread_pages := paginate(config.PostsPerThreadPage, repliesInterface)
for i, _ := range thread_pages { deleteMatchingFiles(path.Join(config.DocumentRoot, board.Dir, "res"), "^"+strconv.Itoa(op.ID)+"p")
thread_pages[i] = append([]interface{}{op}, thread_pages[i]...)
}
deleteMatchingFiles(path.Join(config.DocumentRoot, boardDir, "res"), "^"+strconv.Itoa(op.ID)+"p")
op.NumPages = len(thread_pages) op.NumPages = len(thread_pages)
current_page_filepath := path.Join(config.DocumentRoot, boardDir, "res", strconv.Itoa(op.ID)+".html") current_page_filepath := path.Join(config.DocumentRoot, board.Dir, "res", strconv.Itoa(op.ID)+".html")
current_page_file, err = os.OpenFile(current_page_filepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) current_page_file, err = os.OpenFile(current_page_filepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
if err != nil { if err != nil {
html += handleError(1, "Failed opening "+current_page_filepath+": "+err.Error()) html += handleError(1, "Failed opening "+current_page_filepath+": "+err.Error())
return return
} }
// render main page // render main page
if err = renderTemplate(img_header_tmpl, "img_header", current_page_file, if err = img_threadpage_tmpl.Execute(current_page_file, map[string]interface{}{
&Wrapper{IName: "boards_", Data: all_boards}, "config": config,
&Wrapper{IName: "sections_w", Data: all_sections}, "boards": all_boards,
&Wrapper{IName: "posts_w", Data: append([]interface{}{op}, replies...)}, "board": board,
); err != nil { "sections": all_sections,
html += handleError(1, "Failed building /"+boardDir+"/res/"+strconv.Itoa(op.ID)+" header: "+err.Error()) + "<br />\n" "posts": replies,
return "op": op,
} }); err != nil {
html += handleError(1, "Failed building /%s/res/%d threadpage: ", board.Dir, op.ID, err.Error()) + "<br />\n"
if err = renderTemplate(img_threadpage_tmpl, "threadpage", current_page_file,
&Wrapper{IName: "boards_", Data: all_boards},
&Wrapper{IName: "sections_w", Data: all_sections},
&Wrapper{IName: "posts_w", Data: append([]interface{}{op}, replies...)},
); err != nil {
html += handleError(1, "Failed building /%s/res/%d threadpage: ", boardDir, op.ID, err.Error()) + "<br />\n"
return
}
if err = renderTemplate(global_footer_tmpl, "global_footer", current_page_file); err != nil {
html += handleError(1, "Failed building /%s/res/%d.html: %s", boardDir, op.ID, err.Error())
return return
} }
// Put together the thread JSON // Put together the thread JSON
threadJSONFile, err := os.OpenFile(path.Join(config.DocumentRoot, boardDir, "res", strconv.Itoa(op.ID)+".json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) threadJSONFile, err := os.OpenFile(path.Join(config.DocumentRoot, board.Dir, "res", strconv.Itoa(op.ID)+".json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
if err != nil { if err != nil {
html += handleError(1, "Failed opening /%s/res/%d.json: %s", boardDir, op.ID, err.Error()) html += handleError(1, "Failed opening /%s/res/%d.json: %s", board.Dir, op.ID, err.Error())
return return
} }
defer closeFile(threadJSONFile) defer closeFile(threadJSONFile)
@ -475,64 +406,51 @@ func buildThreadPages(op *PostTable) (html string) {
thread_json_wrapper := new(ThreadJSONWrapper) thread_json_wrapper := new(ThreadJSONWrapper)
// Handle the OP, of type *PostTable // Handle the OP, of type *PostTable
op_post_obj := makePostJSON(*op, anonymous) op_post_obj := makePostJSON(*op, board.Anonymous)
thread_json_wrapper.Posts = append(thread_json_wrapper.Posts, op_post_obj) thread_json_wrapper.Posts = append(thread_json_wrapper.Posts, op_post_obj)
// Iterate through each reply, which are of type PostTable // Iterate through each reply, which are of type PostTable
for _, post_int := range replies { for _, reply := range replies {
post := post_int.(PostTable) postJSON := makePostJSON(reply, board.Anonymous)
post_obj := makePostJSON(post, anonymous) thread_json_wrapper.Posts = append(thread_json_wrapper.Posts, postJSON)
thread_json_wrapper.Posts = append(thread_json_wrapper.Posts, post_obj)
} }
threadJSON, err := json.Marshal(thread_json_wrapper) threadJSON, err := json.Marshal(thread_json_wrapper)
if err != nil { if err != nil {
html += handleError(1, "Failed to marshal to JSON: %s", err.Error()) + "<br />" html += handleError(1, "Failed to marshal to JSON: %s", err.Error()) + "<br />"
return return
} }
if _, err = threadJSONFile.Write(threadJSON); err != nil { if _, err = threadJSONFile.Write(threadJSON); err != nil {
html += handleError(1, "Failed writing /%s/res/%d.json: %s", boardDir, op.ID, err.Error()) + "<br />" html += handleError(1, "Failed writing /%s/res/%d.json: %s", board.Dir, op.ID, err.Error()) + "<br />"
return return
} }
success_text := fmt.Sprintf("Built /%s/%d successfully", boardDir, op.ID) success_text := fmt.Sprintf("Built /%s/%d successfully", board.Dir, op.ID)
html += success_text + "<br />\n" html += success_text + "<br />\n"
println(2, success_text) println(2, success_text)
for page_num, page_posts := range thread_pages { for page_num, page_posts := range thread_pages {
op.CurrentPage = page_num op.CurrentPage = page_num + 1
current_page_filepath := path.Join(config.DocumentRoot, boardDir, "res", strconv.Itoa(op.ID)+"p"+strconv.Itoa(op.CurrentPage+1)+".html") current_page_filepath := path.Join(config.DocumentRoot, board.Dir, "res", strconv.Itoa(op.ID)+"p"+strconv.Itoa(op.CurrentPage)+".html")
current_page_file, err = os.OpenFile(current_page_filepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777) current_page_file, err = os.OpenFile(current_page_filepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
if err != nil { if err != nil {
html += handleError(1, "Failed opening "+current_page_filepath+": "+err.Error()) + "<br />\n" html += handleError(1, "Failed opening "+current_page_filepath+": "+err.Error()) + "<br />\n"
return return
} }
if err = renderTemplate(img_header_tmpl, "img_header", current_page_file, if err = img_threadpage_tmpl.Execute(current_page_file, map[string]interface{}{
&Wrapper{IName: "boards_", Data: all_boards}, "config": config,
&Wrapper{IName: "sections_w", Data: all_sections}, "boards": all_boards,
&Wrapper{IName: "posts_w", Data: append([]interface{}{op}, replies...)}, "board": board,
); err != nil { "sections": all_sections,
html += handleError(1, fmt.Sprintf("Failed building /%s/%d: %s", boardDir, op.ID, err.Error())) "posts": page_posts,
"op": op,
}); err != nil {
html += handleError(1, "Failed building /%s/%d: %s", board.Dir, op.ID, err.Error())
return return
} }
if err = renderTemplate(img_threadpage_tmpl, "threadpage", current_page_file, success_text := fmt.Sprintf("Built /%s/%dp%d successfully", board.Dir, op.ID, op.CurrentPage)
&Wrapper{IName: "boards_", Data: all_boards},
&Wrapper{IName: "sections_w", Data: all_sections},
&Wrapper{IName: "posts_w", Data: page_posts},
); err != nil {
html += handleError(1, "Failed building /%s/%d: %s", boardDir, op.ID, err.Error())
return
}
if err = renderTemplate(global_footer_tmpl, "global_footer", current_page_file); err != nil {
html += handleError(1, "Failed building /%s/%d: %s", boardDir, op.ID, err.Error())
return
}
success_text := fmt.Sprintf("Built /%s/%dp%d successfully", boardDir, op.ID, op.CurrentPage+1)
html += success_text + "<br />\n" html += success_text + "<br />\n"
println(2, success_text) println(2, success_text)
} }
@ -556,12 +474,14 @@ func buildFrontPage() (html string) {
if err != nil { if err != nil {
return handleError(1, "Failed getting front page rows: "+err.Error()) return handleError(1, "Failed getting front page rows: "+err.Error())
} }
defer closeRows(rows)
for rows.Next() { for rows.Next() {
frontpage := new(FrontTable) frontpage := new(FrontTable)
frontpage.IName = "front page" frontpage.IName = "front page"
if err = rows.Scan(&frontpage.ID, &frontpage.Page, &frontpage.Order, &frontpage.Subject, if err = rows.Scan(&frontpage.ID, &frontpage.Page, &frontpage.Order, &frontpage.Subject,
&frontpage.Message, &frontpage.Timestamp, &frontpage.Poster, &frontpage.Email); err != nil { &frontpage.Message, &frontpage.Timestamp, &frontpage.Poster, &frontpage.Email); err != nil {
return handleError(1, err.Error()) + "<br />" return handleError(1, err.Error())
} }
front_arr = append(front_arr, frontpage) front_arr = append(front_arr, frontpage)
} }
@ -578,32 +498,33 @@ func buildFrontPage() (html string) {
"AND `boardid` = `" + config.DBprefix + "boards`.`id` " + "AND `boardid` = `" + config.DBprefix + "boards`.`id` " +
"ORDER BY `timestamp` DESC LIMIT ?") "ORDER BY `timestamp` DESC LIMIT ?")
if err != nil { if err != nil {
return handleError(1, err.Error()) + "<br />" return handleError(1, err.Error())
} }
defer closeStatement(stmt) defer closeStatement(stmt)
rows, err = stmt.Query(nil_timestamp, config.MaxRecentPosts) rows, err = stmt.Query(nil_timestamp, config.MaxRecentPosts)
if err != nil { if err != nil {
return handleError(1, "Failed getting list of recent posts for front page: "+err.Error()) + "<br />" return handleError(1, "Failed getting list of recent posts for front page: "+err.Error())
} }
for rows.Next() { for rows.Next() {
recent_post := new(RecentPost) recent_post := new(RecentPost)
err = rows.Scan(&recent_post.PostID, &recent_post.ParentID, &recent_post.BoardName, &recent_post.BoardID, &recent_post.Name, &recent_post.Tripcode, &recent_post.Message, &recent_post.Filename, &recent_post.ThumbW, &recent_post.ThumbH) err = rows.Scan(&recent_post.PostID, &recent_post.ParentID, &recent_post.BoardName, &recent_post.BoardID, &recent_post.Name, &recent_post.Tripcode, &recent_post.Message, &recent_post.Filename, &recent_post.ThumbW, &recent_post.ThumbH)
if err != nil { if err != nil {
return handleError(1, "Failed getting list of recent posts for front page: "+err.Error()) + "<br />" return handleError(1, "Failed getting list of recent posts for front page: "+err.Error())
} }
recent_posts_arr = append(recent_posts_arr, recent_post) recent_posts_arr = append(recent_posts_arr, recent_post)
} }
if err = renderTemplate(front_page_tmpl, "frontpage", front_file, if err = front_page_tmpl.Execute(front_file, map[string]interface{}{
&Wrapper{IName: "fronts", Data: front_arr}, "config": config,
&Wrapper{IName: "boards", Data: all_boards}, "fronts": front_arr,
&Wrapper{IName: "sections", Data: all_sections}, "boards": all_boards,
&Wrapper{IName: "recent posts", Data: recent_posts_arr}, "sections": all_sections,
); err != nil { "recent_posts": recent_posts_arr,
return handleError(1, "Failed executing front page template: "+err.Error()) + "<br />" }); err != nil {
return handleError(1, "Failed executing front page template: "+err.Error())
} }
return "Front page rebuilt successfully.<br />" return "Front page rebuilt successfully."
} }
func buildBoardListJSON() (html string) { func buildBoardListJSON() (html string) {
@ -640,8 +561,7 @@ func buildBoardListJSON() (html string) {
return "Board list JSON rebuilt successfully.<br />" return "Board list JSON rebuilt successfully.<br />"
} }
// bumps the given thread on the given board and returns true if it exists // bumps the given thread on the given board and returns true if there were no errors
// or false on error
func bumpThread(postID, boardID int) error { func bumpThread(postID, boardID int) error {
stmt, err := db.Prepare("UPDATE `" + config.DBprefix + "posts` SET `bumped` = ? WHERE `id` = ? AND `boardid` = ?") stmt, err := db.Prepare("UPDATE `" + config.DBprefix + "posts` SET `bumped` = ? WHERE `id` = ? AND `boardid` = ?")
if err != nil { if err != nil {
@ -649,11 +569,9 @@ func bumpThread(postID, boardID int) error {
} }
defer closeStatement(stmt) defer closeStatement(stmt)
if _, err = stmt.Exec(time.Now(), postID, boardID); err != nil { _, err = stmt.Exec(time.Now(), postID, boardID)
return err return err
} }
return nil
}
// Checks check poster's name/tripcode/file checksum (from PostTable post) for banned status // Checks check poster's name/tripcode/file checksum (from PostTable post) for banned status
// returns true if the user is banned // returns true if the user is banned
@ -1144,9 +1062,6 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
} }
} }
} }
if post.FilenameOriginal != "" {
//post.FilenameOriginal = html.EscapeString(post.FilenameOriginal)
}
if strings.TrimSpace(post.MessageText) == "" && post.Filename == "" { if strings.TrimSpace(post.MessageText) == "" && post.Filename == "" {
serveErrorPage(w, "Post must contain a message if no image is uploaded.") serveErrorPage(w, "Post must contain a message if no image is uploaded.")
@ -1175,7 +1090,9 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
var banpage_buffer bytes.Buffer var banpage_buffer bytes.Buffer
var banpage_html string var banpage_html string
banpage_buffer.Write([]byte("")) banpage_buffer.Write([]byte(""))
if err = renderTemplate(banpage_tmpl, "banpage", &banpage_buffer, &Wrapper{IName: "bans", Data: isBanned}); err != nil { if err = banpage_tmpl.Execute(&banpage_buffer, map[string]interface{}{
"bans": isBanned,
}); err != nil {
fmt.Fprintf(writer, banpage_html+handleError(1, err.Error())+"\n</body>\n</html>") fmt.Fprintf(writer, banpage_html+handleError(1, err.Error())+"\n</body>\n</html>")
return return
} }

View file

@ -179,6 +179,7 @@ func utilHandler(writer http.ResponseWriter, request *http.Request, data interfa
action := request.FormValue("action") action := request.FormValue("action")
password := request.FormValue("password") password := request.FormValue("password")
board := request.FormValue("board") board := request.FormValue("board")
boardid := request.FormValue("boardid")
fileOnly := request.FormValue("fileonly") == "on" fileOnly := request.FormValue("fileonly") == "on"
deleteBtn := request.PostFormValue("delete_btn") deleteBtn := request.PostFormValue("delete_btn")
reportBtn := request.PostFormValue("report_btn") reportBtn := request.PostFormValue("report_btn")
@ -203,7 +204,33 @@ func utilHandler(writer http.ResponseWriter, request *http.Request, data interfa
serveErrorPage(writer, "You can only edit one post at a time.") serveErrorPage(writer, "You can only edit one post at a time.")
return return
} else { } else {
passwordMD5 := md5Sum(password)
rank := getStaffRank()
if passwordMD5 == "" && rank == 0 {
serveErrorPage(writer, "Password required for post editing")
return
}
var post PostTable
post.ID, _ = strconv.Atoi(postsArr[0])
post.BoardID, _ = strconv.Atoi(boardid)
stmt, err := db.Prepare("SELECT `parentid`,` password`,`message_raw` FROM `" + config.DBprefix + "posts` WHERE `id` = ? AND `deleted_timestamp` = ?")
if err != nil {
serveErrorPage(writer, handleError(1, err.Error()+"\n"))
}
defer closeStatement(stmt)
/* var post_edit_buffer bytes.Buffer
if err = renderTemplate(post_edit_tmpl, "post_edit", post_edit_buffer,
&Wrapper{IName: "boards_", Data: all_boards},
&Wrapper{IName: "sections_w", Data: all_sections},
&Wrapper{IName: "posts_w", Data: []interface{}{
PostTable{BoardID: board.ID},
}},
&Wrapper{IName: "op", Data: []interface{}{PostTable{}}},
&Wrapper{IName: "board", Data: []interface{}{board}},
); err != nil {
html += handleError(1, fmt.Sprintf("Failed building /%s/res/%d.html: %s", board.Dir, 0, err.Error())) + "<br />"
return
} */
} }
} }
@ -218,23 +245,19 @@ func utilHandler(writer http.ResponseWriter, request *http.Request, data interfa
} }
for _, checkedPostID := range postsArr { for _, checkedPostID := range postsArr {
// var parentID int
// var fileName string
var fileType string var fileType string
var thumbType string var thumbType string
// var passwordChecksum string
// var boardid int
var post PostTable var post PostTable
post.ID, _ = strconv.Atoi(checkedPostID) post.ID, _ = strconv.Atoi(checkedPostID)
post.BoardID, _ = strconv.Atoi(boardid)
stmt, err := db.Prepare("SELECT `parentID`, `filename`, `password` FROM `" + config.DBprefix + "posts` WHERE `id` = ? AND `deleted_timestamp` = ?") stmt, err := db.Prepare("SELECT `parentid`, `filename`, `password` FROM `" + config.DBprefix + "posts` WHERE `id` = ? AND `boardid` = ? AND `deleted_timestamp` = ?")
if err != nil { if err != nil {
serveErrorPage(writer, handleError(1, err.Error()+"\n")) serveErrorPage(writer, handleError(1, err.Error()+"\n"))
} }
defer closeStatement(stmt) defer closeStatement(stmt)
err = stmt.QueryRow(&post.ID, nil_timestamp).Scan(&post.ParentID, &post.Filename, &post.Password) err = stmt.QueryRow(&post.ID, &post.BoardID, nil_timestamp).Scan(&post.ParentID, &post.Filename, &post.Password)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
//the post has already been deleted //the post has already been deleted
writer.Header().Add("refresh", "4;url="+request.Referer()) writer.Header().Add("refresh", "4;url="+request.Referer())
@ -279,7 +302,8 @@ func utilHandler(writer http.ResponseWriter, request *http.Request, data interfa
_board, _ := getBoardArr(map[string]interface{}{"id": post.BoardID}, "") _board, _ := getBoardArr(map[string]interface{}{"id": post.BoardID}, "")
buildBoardPages(&_board[0]) buildBoardPages(&_board[0])
_post, _ := getPostArr(map[string]interface{}{"id": post.ID, "boardid": post.BoardID}, "") _post, _ := getPostArr(map[string]interface{}{"id": post.ID, "boardid": post.BoardID}, "")
postBoard := _post[0].(PostTable) postBoard := _post[0]
// postBoard := _post[0].(PostTable)
buildThreadPages(&postBoard) buildThreadPages(&postBoard)
writer.Header().Add("refresh", "4;url="+request.Referer()) writer.Header().Add("refresh", "4;url="+request.Referer())

View file

@ -3,21 +3,13 @@ package main
import ( import (
"fmt" "fmt"
"html" "html"
"io"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
"time"
) )
type FooterData struct {
Version string
GeneratedTime float32
}
var funcMap = template.FuncMap{ var funcMap = template.FuncMap{
"add": func(a, b int) int { "add": func(a, b int) int {
return a + b return a + b
@ -40,27 +32,33 @@ var funcMap = template.FuncMap{
"gt": func(a int, b int) bool { "gt": func(a int, b int) bool {
return a > b return a > b
}, },
"gte": func(a int, b int) bool { "ge": func(a int, b int) bool {
return a >= b return a >= b
}, },
"lt": func(a int, b int) bool { "lt": func(a int, b int) bool {
return a < b return a < b
}, },
"lte": func(a int, b int) bool { "le": func(a int, b int) bool {
return a <= b return a <= b
}, },
"makeLoop": func(n int) []struct{} { "makeLoop": func(n int, offset int) []int {
return make([]struct{}, n) loopArr := make([]int, n)
for i := range loopArr {
loopArr[i] = i + offset
}
return loopArr
},
"printf": func(v int, format string, a ...interface{}) string {
printf(v, format, a...)
return ""
},
"println": func(v int, i ...interface{}) string {
println(v, i...)
return ""
}, },
"stringAppend": func(a, b string) string { "stringAppend": func(a, b string) string {
return a + b return a + b
}, },
"stringEq": func(a, b string) bool {
return a == b
},
"stringNeq": func(a, b string) bool {
return a != b
},
"truncateMessage": func(msg string, limit int, max_lines int) string { "truncateMessage": func(msg string, limit int, max_lines int) string {
var truncated bool var truncated bool
split := strings.SplitN(msg, "<br />", -1) split := strings.SplitN(msg, "<br />", -1)
@ -100,36 +98,17 @@ var funcMap = template.FuncMap{
"escapeString": func(a string) string { "escapeString": func(a string) string {
return html.EscapeString(a) return html.EscapeString(a)
}, },
"isNil": func(i interface{}) bool {
return i == nil
},
"intEq": func(a, b int) bool { "intEq": func(a, b int) bool {
return a == b return a == b
}, },
"intToString": func(a int) string { "intToString": strconv.Itoa,
return strconv.Itoa(a)
},
"isStyleDefault_img": func(style string) bool { "isStyleDefault_img": func(style string) bool {
return style == config.DefaultStyle_img return style == config.DefaultStyle_img
}, },
"isStyleNotDefault_img": func(style string) bool { "formatTimestamp": humanReadableTime,
return style != config.DefaultStyle_img
},
"getElement": func(in []interface{}, element int) interface{} {
if len(in) > element {
return in[element]
}
return nil
},
"getInterface": func(in []interface{}, index int) interface{} {
var nope interface{}
if len(in) == 0 {
return nope
} else if len(in) < index+1 {
return nope
}
return in[index]
},
"formatTimestamp": func(timestamp time.Time) string {
return humanReadableTime(timestamp)
},
"getThreadID": func(post_i interface{}) (thread int) { "getThreadID": func(post_i interface{}) (thread int) {
post := post_i.(PostTable) post := post_i.(PostTable)
if post.ParentID == 0 { if post.ParentID == 0 {
@ -196,121 +175,82 @@ var funcMap = template.FuncMap{
} }
var ( var (
footer_data = FooterData{version, float32(0)}
banpage_tmpl *template.Template banpage_tmpl *template.Template
global_footer_tmpl *template.Template
global_header_tmpl *template.Template global_header_tmpl *template.Template
img_header_tmpl *template.Template
img_boardpage_tmpl *template.Template img_boardpage_tmpl *template.Template
img_threadpage_tmpl *template.Template img_threadpage_tmpl *template.Template
img_post_form_tmpl *template.Template img_post_form_tmpl *template.Template
post_edit_tmpl *template.Template
manage_header_tmpl *template.Template manage_header_tmpl *template.Template
manage_boards_tmpl *template.Template manage_boards_tmpl *template.Template
manage_config_tmpl *template.Template manage_config_tmpl *template.Template
front_page_tmpl *template.Template front_page_tmpl *template.Template
) )
func loadTemplate(name string, filename string, before string) (*template.Template, error) { func loadTemplate(files ...string) (*template.Template, error) {
tmplBytes, err := ioutil.ReadFile(config.TemplateDir + "/" + filename) if len(files) == 0 {
if err != nil { return nil, fmt.Errorf("ERROR: no files named in call to loadTemplate")
return nil, err
} }
tmplStr := before + string(tmplBytes) var templates []string
return template.New(name).Funcs(funcMap).Parse(tmplStr) for i, file := range files {
templates = append(templates, file)
files[i] = config.TemplateDir + "/" + files[i]
}
return template.New(templates[0]).Funcs(funcMap).ParseFiles(files...)
} }
func initTemplates() { func templateError(name string, err error) error {
return fmt.Errorf("Failed loading template \"" + config.TemplateDir + "/" + name + ": \"" + err.Error())
}
func initTemplates() error {
var err error var err error
resetBoardSectionArrays() resetBoardSectionArrays()
banpage_tmpl, err = loadTemplate("banpage.html")
banpage_tmpl, err = loadTemplate("banpage_tmpl", "banpage.html",
"{{$config := getInterface .Data 0}}"+
"{{$ban := getInterface .Data 1}}")
if err != nil { if err != nil {
println(0, "Failed loading template \""+config.TemplateDir+"/banpage.html: \""+err.Error()) return templateError("banpage.html", err)
os.Exit(2)
} }
global_footer_tmpl, err = loadTemplate("global_footer_tmpl", "global_footer.html", "{{$config := getInterface .Data 0}}") global_header_tmpl, err = loadTemplate("global_header.html")
if err != nil { if err != nil {
println(0, "Failed loading template \""+config.TemplateDir+"/global_footer.html: \""+err.Error()) return templateError("global_header.html", err)
os.Exit(2)
} }
global_header_tmpl, err = loadTemplate("global_header_tmpl", "global_header.html", "") img_boardpage_tmpl, err = loadTemplate("img_boardpage.html", "img_header.html", "postbox.html", "global_footer.html")
if err != nil { if err != nil {
println(0, "Failed loading template \""+config.TemplateDir+"/global_header.html: \""+err.Error()) return templateError("img_boardpage.html", err)
os.Exit(2)
} }
img_header_tmpl, err = loadTemplate("img_header_tmpl", "img_header.html", img_threadpage_tmpl, err = loadTemplate("img_threadpage.html", "img_header.html", "postbox.html", "global_footer.html")
"{{$config := getInterface .Data 0}}"+
"{{$board_arr := (getInterface .Data 1).Data}}"+
"{{$section_arr := (getInterface .Data 2).Data}}"+
"{{$post_arr := (getInterface .Data 3).Data}}"+
"{{$op := getElement $post_arr 0}}"+
"{{$board := getElement $board_arr (subtract $op.BoardID 1)}}")
if err != nil { if err != nil {
println(0, "Failed loading template \""+config.TemplateDir+"/img_header.html: \""+err.Error()) return templateError("img_threadpage.html", err)
os.Exit(2)
} }
img_boardpage_tmpl, err = loadTemplate("img_boardpage_tmpl", "img_boardpage.html", /* post_edit_tmpl, err = loadTemplate("post_edit_tmpl", "post_edit.html")
"{{$config := getInterface .Data 0}}"+
"{{$board_arr := (getInterface .Data 1).Data}}"+
"{{$section_arr := (getInterface .Data 2).Data}}"+
"{{$thread_arr := (getInterface .Data 3).Data}}"+
"{{$board_info := (getInterface .Data 4).Data}}"+
"{{$board := getInterface $board_info 0}}")
if err != nil { if err != nil {
println(0, "Failed loading template \""+config.TemplateDir+"/img_boardpage.html: \""+err.Error()) return templateError("img_threadpage.html", err)
os.Exit(2) } */
manage_header_tmpl, err = loadTemplate("manage_header.html")
if err != nil {
return templateError("manage_header.html", err)
} }
img_threadpage_tmpl, err = loadTemplate("img_threadpage_tmpl", "img_threadpage.html", manage_boards_tmpl, err = loadTemplate("manage_boards.html")
"{{$config := getInterface .Data 0}}"+
"{{$board_arr := (getInterface .Data 1).Data}}"+
"{{$section_arr := (getInterface .Data 2).Data}}"+
"{{$post_arr := (getInterface .Data 3).Data}}"+
"{{$op := getElement $post_arr 0}}"+
"{{$board := getElement $board_arr (subtract $op.BoardID 1)}}")
if err != nil { if err != nil {
println(0, "Failed loading template \""+config.TemplateDir+"/img_threadpage.html: \""+err.Error()) return templateError("manage_boards.html", err)
os.Exit(2)
} }
manage_header_tmpl, err = loadTemplate("manage_header_tmpl", "manage_header.html", "") manage_config_tmpl, err = loadTemplate("manage_config.html")
if err != nil { if err != nil {
println(0, "Failed loading template \""+config.TemplateDir+"/manage_header.html: \""+err.Error()) return templateError("manage_config.html", err)
os.Exit(2)
} }
manage_boards_tmpl, err = loadTemplate("manage_boards_tmpl", "manage_boards.html", front_page_tmpl, err = loadTemplate("front.html", "global_footer.html")
"{{$config := getInterface .Data 0}}"+
"{{$board := getInterface (getInterface .Data 1).Data 0}}"+
"{{$section_arr := (getInterface .Data 2).Data}}")
if err != nil { if err != nil {
println(0, "Failed loading template \""+config.TemplateDir+"/manage_boards.html: \""+err.Error()) return templateError("front.html", err)
os.Exit(2)
}
manage_config_tmpl, err = loadTemplate("manage_config_tmpl", "manage_config.html", "{{$config := getInterface .Data 0}}")
if err != nil {
println(0, "Failed loading template \""+config.TemplateDir+"/manage_config.html: \""+err.Error())
os.Exit(2)
}
front_page_tmpl, err = loadTemplate("front_page_tmpl", "front.html",
"{{$config := getInterface .Data 0}}"+
"{{$page_arr := getInterface .Data 1}}"+
"{{$board_arr := getInterface .Data 2}}"+
"{{$section_arr := getInterface .Data 3}}"+
"{{$recent_posts_arr := getInterface .Data 4}}")
if err != nil {
println(0, "Failed loading template \""+config.TemplateDir+"/front.html\": "+err.Error())
os.Exit(2)
} }
return nil
} }
func getStyleLinks(w http.ResponseWriter, stylesheet string) { func getStyleLinks(w http.ResponseWriter, stylesheet string) {
@ -324,14 +264,3 @@ func getStyleLinks(w http.ResponseWriter, stylesheet string) {
os.Exit(2) os.Exit(2)
} }
} }
func renderTemplate(tmpl *template.Template, name string, output io.Writer, wrappers ...*Wrapper) error {
var interfaces []interface{}
interfaces = append(interfaces, config)
for _, wrapper := range wrappers {
interfaces = append(interfaces, wrapper)
}
wrapped := &Wrapper{IName: name, Data: interfaces}
return tmpl.Execute(output, wrapped)
}

View file

@ -38,11 +38,11 @@ type RecentPost struct {
type Thread struct { type Thread struct {
IName string IName string
OP interface{} OP PostTable
NumReplies int NumReplies int
NumImages int NumImages int
OmittedImages int OmittedImages int
BoardReplies []interface{} BoardReplies []PostTable
Stickied bool Stickied bool
ThreadPage int ThreadPage int
} }
@ -255,11 +255,6 @@ type WordFiltersTable struct {
RegEx bool RegEx bool
} }
type Wrapper struct {
IName string
Data []interface{}
}
// Types for the JSON files we generate as a sort of "API" // Types for the JSON files we generate as a sort of "API"
type BoardJSONWrapper struct { type BoardJSONWrapper struct {
Boards []BoardJSON `json:"boards"` Boards []BoardJSON `json:"boards"`

View file

@ -95,8 +95,8 @@ func closeStatement(stmt *sql.Stmt) {
} }
/* /*
Deletes files in a folder (root) that match a given regular expression. * Deletes files in a folder (root) that match a given regular expression.
Returns the number of files that were deleted, and any error encountered. * Returns the number of files that were deleted, and any error encountered.
*/ */
func deleteMatchingFiles(root, match string) (filesDeleted int, err error) { func deleteMatchingFiles(root, match string) (filesDeleted int, err error) {
files, err := ioutil.ReadDir(root) files, err := ioutil.ReadDir(root)
@ -234,8 +234,33 @@ func getBoardArr(parameterList map[string]interface{}, extra string) (boards []B
return return
} }
func getBoardFromID(id int) (*BoardsTable, error) {
board := new(BoardsTable)
stmt, err := db.Prepare("SELECT `order`,`dir`,`type`,`upload_type`,`title`,`subtitle`,`description`,`section`," +
"`max_image_size`,`max_pages`,`locale`,`default_style`,`locked`,`created_on`,`anonymous`,`forced_anon`,`max_age`," +
"`autosage_after`,`no_images_after`,`max_message_length`,`embeds_allowed`,`redirect_to_thread`,`require_file`," +
"`enable_catalog` FROM `" + config.DBprefix + "boards` WHERE `id` = ?")
if err != nil {
return nil, err
}
defer closeStatement(stmt)
board.ID = id
if err = stmt.QueryRow(id).Scan(
&board.Order, &board.Dir, &board.Type, &board.UploadType, &board.Title,
&board.Subtitle, &board.Description, &board.Section, &board.MaxImageSize,
&board.MaxPages, &board.Locale, &board.DefaultStyle, &board.Locked, &board.CreatedOn,
&board.Anonymous, &board.ForcedAnon, &board.MaxAge, &board.AutosageAfter,
&board.NoImagesAfter, &board.MaxMessageLength, &board.EmbedsAllowed,
&board.RedirectToThread, &board.RequireFile, &board.EnableCatalog,
); err != nil {
return nil, err
}
return board, nil
}
// if parameterList is nil, ignore it and treat extra like a whole SQL query // if parameterList is nil, ignore it and treat extra like a whole SQL query
func getPostArr(parameterList map[string]interface{}, extra string) (posts []interface{}, err error) { func getPostArr(parameterList map[string]interface{}, extra string) (posts []PostTable, err error) {
queryString := "SELECT * FROM `" + config.DBprefix + "posts` " queryString := "SELECT * FROM `" + config.DBprefix + "posts` "
numKeys := len(parameterList) numKeys := len(parameterList)
var parameterValues []interface{} var parameterValues []interface{}
@ -353,7 +378,7 @@ func getFileExtension(filename string) (extension string) {
return return
} }
func getFormattedFilesize(size float32) string { func getFormattedFilesize(size int) string {
if size < 1000 { if size < 1000 {
return fmt.Sprintf("%fB", size) return fmt.Sprintf("%fB", size)
} else if size <= 100000 { } else if size <= 100000 {

View file

@ -3,12 +3,11 @@
<head> <head>
<title>Banned</title> <title>Banned</title>
<link rel="stylesheet" href="/css/global/front.css" /> <link rel="stylesheet" href="/css/global/front.css" />
{{range $i, $style := $config.Styles_img}} {{range $i, $style := .config.Styles_img}}
<link rel="{{if isStyleNotDefault_img $style}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}} <link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}}
<script type="text/javascript"> <script type="text/javascript">
var styles = [{{range $i, $style := $config.Styles_img}}{{if gt $i 0}}, {{end}}"{{$style}}"{{end}}]; var styles = [{{range $i, $style := .config.Styles_img}}{{if gt $i 0}}, {{end}}"{{$style}}"{{end}}];
var webroot = "{{$config.SiteWebfolder}}" var webroot = "{{.config.SiteWebfolder}}"
</script> </script>
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script> <script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="/javascript/gochan.js"></script> <script type="text/javascript" src="/javascript/gochan.js"></script>
@ -16,8 +15,8 @@
</head> </head>
<body> <body>
<div id="top-pane"> <div id="top-pane">
<span id="site-title">{{$config.SiteName}}</span><br /> <span id="site-title">{{.config.SiteName}}</span><br />
<span id="site-slogan">{{$config.SiteSlogan}}</span> <span id="site-slogan">{{.config.SiteSlogan}}</span>
</div> </div>
<br /> <br />
<br /> <br />
@ -27,29 +26,29 @@
</div> </div>
<div class="section-body" style="padding-top:8px"> <div class="section-body" style="padding-top:8px">
<div id="ban-info" style="float:left"> <div id="ban-info" style="float:left">
{{if stringEq $ban.Boards "*"}} {{if eq .ban.Boards "*"}}
You are banned from posting on <b>all boards</b> for the following reason: You are banned from posting on <b>all boards</b> for the following reason:
{{else}} {{else}}
You are banned from posting on <b>{{$ban.Boards}}</b> for the following reason: You are banned from posting on <b>{{.ban.Boards}}</b> for the following reason:
{{end}} {{end}}
<br /><br /> <br /><br />
<b>{{$ban.Message}}</b> <b>{{.ban.Message}}</b>
<br /><br /> <br /><br />
{{$expires_timestamp := formatTimestamp $ban.Expires}} {{$expires_timestamp := formatTimestamp .ban.Expires}}
{{$appeal_timestamp := formatTimestamp $ban.AppealAt}} {{$appeal_timestamp := formatTimestamp .ban.AppealAt}}
{{if stringEq $expires_timestamp "Mon, January 01, 0001 00:00 AM"}} {{if eq $expires_timestamp "Mon, January 01, 0001 00:00 AM"}}
Your ban was placed on {{formatTimestamp $ban.Timestamp}} and will not expire.<br /> Your ban was placed on {{formatTimestamp .ban.Timestamp}} and will not expire.<br />
{{else}} {{else}}
Your ban was placed on {{formatTimestamp $ban.Timestamp}} and will expire on {{formatTimestamp $ban.Expires}}<br /> Your ban was placed on {{formatTimestamp .ban.Timestamp}} and will expire on {{$expires_timestamp}}<br />
{{end}} {{end}}
<br /> <br />
{{if stringEq $appeal_timestamp "Mon, January 01, 0001 00:00 AM"}} {{if eq .appeal_timestamp "Mon, January 01, 0001 00:00 AM"}}
You may not appeal this ban. You may not appeal this ban.
{{else}} {{else}}
You may appeal this ban {{formatTimestamp $ban.AppealAt}} You may appeal this ban {{$appeal_timestamp}}
{{end}} {{end}}
<br /><br /> <br /><br />
Your IP address is {{$ban.IP}}. Your IP address is {{.ban.IP}}.
</div> </div>
<img id="banpage-image" src="/banned.jpg" style="float:right; margin: 4px 8px 8px 4px"/> <img id="banpage-image" src="/banned.jpg" style="float:right; margin: 4px 8px 8px 4px"/>
</div> </div>

View file

@ -2,31 +2,30 @@
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{{$config.SiteName}}</title> <title>{{.config.SiteName}}</title>
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script> <script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="/javascript/msgpack.js"></script> <script type="text/javascript" src="/javascript/msgpack.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var styles = [{{range $ii, $style := $config.Styles_img}}{{if gt $ii 0}}, {{end}}"{{$style}}"{{end}}]; var styles = [{{range $ii, $style := .config.Styles_img}}{{if gt $ii 0}}, {{end}}"{{$style}}"{{end}}];
var webroot = "{{$config.SiteWebfolder}}" var webroot = "{{.config.SiteWebfolder}}"
</script> </script>
<script type="text/javascript" src="/javascript/gochan.js"></script> <script type="text/javascript" src="/javascript/gochan.js"></script>
<script type="text/javascript" src="/javascript/manage.js"></script> <script type="text/javascript" src="/javascript/manage.js"></script>
<link rel="stylesheet" href="/css/global/front.css" /> <link rel="stylesheet" href="/css/global/front.css" />
{{range $i, $style := $config.Styles_img}} {{range $i, $style := .config.Styles_img}}
<link rel="{{if isStyleNotDefault_img $style}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}} <link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}}
<link rel="shortcut icon" href="/favicon.png"> <link rel="shortcut icon" href="/favicon.png">
</head> </head>
<body> <body>
<div id="topbar"> <div id="topbar">
{{range $i, $board := $board_arr.Data}} {{range $i, $board := .boards}}
<a href="/{{$board.Dir}}/" class="topbar-item">/{{$board.Dir}}/</a> <a href="/{{$board.Dir}}/" class="topbar-item">/{{$board.Dir}}/</a>
{{end}} {{end}}
</div> </div>
<div id="top-pane"> <div id="top-pane">
<span id="site-title">{{$config.SiteName}}</span><br /> <span id="site-title">{{.config.SiteName}}</span><br />
<span id="site-slogan">{{$config.SiteSlogan}}</span> <span id="site-slogan">{{.config.SiteSlogan}}</span>
</div> </div>
<div id="main"> <div id="main">
<div id="tab-bar"> <div id="tab-bar">
@ -43,11 +42,9 @@
<a href="#faq">FAQ</a> <a href="#faq">FAQ</a>
</div> </div>
</div> </div>
<div id="first-page" class="page"> <div id="first-page" class="page">
{{range $ii, $page := $page_arr.Data}} {{range $ii, $page := .page_arr}}
{{if intEq $page.Page 0}} {{if eq $page.Page 0}}
<div class="section-block"> <div class="section-block">
<div class="section-title-block" id="first-page0"> <div class="section-title-block" id="first-page0">
<b>{{$page.Subject}}</b> by <a href="mailto:{{$page.Email}}" >{{$page.Poster}}</a><a href="/#first-page{{$ii}}" class="permalink">#</a> <b>{{$page.Subject}}</b> by <a href="mailto:{{$page.Email}}" >{{$page.Poster}}</a><a href="/#first-page{{$ii}}" class="permalink">#</a>
@ -59,7 +56,6 @@
{{end}} {{end}}
{{end}} {{end}}
</div> </div>
<div id="boards-page" class="page"> <div id="boards-page" class="page">
<div class="section-block"> <div class="section-block">
<div class="section-title-block"> <div class="section-title-block">
@ -67,19 +63,16 @@
</div> </div>
<div class="section-body"> <div class="section-body">
<ul> <ul>
{{range $i, $board := $board_arr.Data}} {{range $_, $board := .boards}}
{{if stringEq $board.Dir $config.Modboard}}{{else}} {{if eq $board.Dir $.config.Modboard}}{{else}}
<li><b>/{{$board.Dir}}/</b> {{$board.Description}}</li> <li><b>/{{$board.Dir}}/</b> {{$board.Description}}</li>{{end}}{{end}}
{{end}}
{{end}}
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
<div id="rules-page" class="page"> <div id="rules-page" class="page">
{{range $ii, $page := $page_arr.Data}} {{range $ii, $page := .page_arr}}
{{if intEq $page.Page 1}} {{if eq $page.Page 1}}
<div class="section-block"> <div class="section-block">
<div class="section-title-block" id="first-page0"> <div class="section-title-block" id="first-page0">
<b>{{$page.Subject}}</b> by <a href="mailto:{{$page.Email}}" >{{$page.Poster}}</a><a href="/#first-page{{$ii}}" class="permalink">#</a> <b>{{$page.Subject}}</b> by <a href="mailto:{{$page.Email}}" >{{$page.Poster}}</a><a href="/#first-page{{$ii}}" class="permalink">#</a>
@ -91,10 +84,9 @@
{{end}} {{end}}
{{end}} {{end}}
</div> </div>
<div id="faq-page" class="page"> <div id="faq-page" class="page">
{{range $ii, $page := $page_arr.Data}} {{range $ii, $page := .page_arr.Data}}
{{if intEq $page.Page 2}} {{if eq $page.Page 2}}
<div class="section-block"> <div class="section-block">
<div class="section-title-block" id="first-page0"> <div class="section-title-block" id="first-page0">
<b>{{$page.Subject}}</b> by <a href="mailto:{{$page.Email}}" >{{$page.Poster}}</a><a href="/#first-page{{$ii}}" class="permalink">#</a> <b>{{$page.Subject}}</b> by <a href="mailto:{{$page.Email}}" >{{$page.Poster}}</a><a href="/#first-page{{$ii}}" class="permalink">#</a>
@ -106,16 +98,16 @@
{{end}} {{end}}
{{end}} {{end}}
</div> </div>
{{if gt $config.MaxRecentPosts 0}} {{if gt .config.MaxRecentPosts 0}}
<div id="recent-posts"> <div id="recent-posts">
<div id="recent-posts-header" class="section-title-block"><b>Recent Posts</b></div> <div id="recent-posts-header" class="section-title-block"><b>Recent Posts</b></div>
{{range $i, $post := $recent_posts_arr.Data}} {{range $i, $post := $.recent_posts}}
<div class="section-block"> <div class="section-block">
<div class="section-title-block"> <div class="section-title-block">
<span class="section-title"><a href="{{$post.BoardName}}/res/{{if intEq $post.ParentID 0}}{{intToString $post.PostID}}.html{{else}}{{intToString $post.ParentID}}.html#{{intToString $post.PostID}}{{end}}">/{{$post.BoardName}}/</a></span> - {{$appended := stringAppend $post.Name $post.Tripcode}}{{if stringEq $appended ""}}<b>Anonymous</b>{{else}}<b>{{$post.Name}}</b>{{if stringNeq $post.Tripcode ""}}!{{$post.Tripcode}}{{end}}{{end}} <span class="section-title"><a href="{{$post.BoardName}}/res/{{if eq $post.ParentID 0}}{{intToString $post.PostID}}.html{{else}}{{intToString $post.ParentID}}.html#{{intToString $post.PostID}}{{end}}">/{{$post.BoardName}}/</a></span> - {{$appended := stringAppend $post.Name $post.Tripcode}}{{if eq $appended ""}}<b>Anonymous</b>{{else}}<b>{{$post.Name}}</b>{{if ne $post.Tripcode ""}}!{{$post.Tripcode}}{{end}}{{end}}
</div> </div>
<div class="section-body"> <div class="section-body">
{{if stringNeq $post.Filename ""}}<a href="{{$post.BoardName}}/src/{{$post.Filename}}" target="_blank"><img src="{{$post.BoardName}}/thumb/{{getThumbnailFilename $post.Filename}}" alt="post thumbnail"/></a>{{end}} {{if ne $post.Filename ""}}<a href="{{$post.BoardName}}/src/{{$post.Filename}}" target="_blank"><img src="{{$post.BoardName}}/thumb/{{getThumbnailFilename $post.Filename}}" alt="post thumbnail"/></a>{{end}}
{{truncateMessage $post.Message 225 12}} {{truncateMessage $post.Message 225 12}}
</div> </div>
</div> </div>
@ -124,8 +116,8 @@
{{end}} {{end}}
</div> </div>
<div id="footer"> <div id="footer">
<a href="{{$config.SiteWebfolder}}">Home</a> | <a href="{{$config.SiteWebfolder}}#boards">Boards</a> | <a href="{{$config.SiteWebfolder}}#rules">Rules</a> | <a href="{{$config.SiteWebfolder}}#faq">FAQ</a><br /> <a href="{{.config.SiteWebfolder}}">Home</a> | <a href="{{.config.SiteWebfolder}}#boards">Boards</a> | <a href="{{.config.SiteWebfolder}}#rules">Rules</a> | <a href="{{.config.SiteWebfolder}}#faq">FAQ</a><br />
Powered by <a href="http://github.com/eggbertx/gochan">Gochan {{$config.Version}}</a><br /> Powered by <a href="http://github.com/eggbertx/gochan">Gochan {{.config.Version}}</a><br />
</div> </div>
</body> </body>
</html> </html>

View file

@ -1,6 +1,6 @@
<div id="footer"> <div id="footer">
<a href="{{$config.SiteWebfolder}}">Home</a> | <a href="{{$config.SiteWebfolder}}#boards">Boards</a> | <a href="{{$config.SiteWebfolder}}#rules">Rules</a> | <a href="{{$config.SiteWebfolder}}#faq">FAQ</a><br /> <a href="{{.SiteWebfolder}}">Home</a> | <a href="{{.SiteWebfolder}}#boards">Boards</a> | <a href="{{.SiteWebfolder}}#rules">Rules</a> | <a href="{{.SiteWebfolder}}#faq">FAQ</a><br />
Powered by <a href="http://github.com/eggbertx/gochan/">Gochan {{$config.Version}}</a><br /> Powered by <a href="http://github.com/eggbertx/gochan/">Gochan {{.Version}}</a><br />
</div> </div>
</body> </body>
</html> </html>

View file

@ -1,37 +1,24 @@
{{template "img_header.html" .}}
<div id="right-sidelinks"> <div id="right-sidelinks">
<a href="{{$config.SiteWebfolder}}{{$board.Dir}}/catalog.html">Board catalog</a><br /> <a href="{{.config.SiteWebfolder}}{{.board.Dir}}/catalog.html">Board catalog</a><br />
</div>
<div id="postbox-area">
<form name="postform" action="/post" method="POST" enctype="multipart/form-data">
<input type="hidden" name="threadid" value="0" />
<input type="hidden" name="boardid" value="{{$board.ID}}" />
<input type="text" name="username" style="display:none" />
<table id="postbox-static">
<tr><th class="postblock">Name</th><td><input type="text" id="postname" name="postname" maxlength="50" size="28" {{/* value="Name" onFocus="if(this.value=='Name') {this,value= ''}" onBlur="if(this.value == '') {this.value = 'Name'}"*/}}/></td></tr>
<tr><th class="postblock">Email</th><td><input type="text" id="postemail" name="postemail" maxlength="50" size="28" /></td></tr>
<tr><th class="postblock">Subject</th><td><input type="text" name="postsubject" maxlength="100" size="35" /><input type="submit" value="Post"/></td></tr>
<tr><th class="postblock">Message</th><td><textarea rows="4" cols="48" name="postmsg" id="postmsg"></textarea></td></tr>
<tr><th class="postblock">File</th><td><input name="imagefile" type="file"><input type="checkbox" id="spoiler" name="spoiler"/><label for="spoiler">Spoiler</label></td></tr>
<tr><th class="postblock">Password</th><td><input type="password" id="postpassword" name="postpassword" size="14" /> (for post/file deletion)</td></tr>
</table>
</form>
</div> </div>
{{template "postbox.html" .}}
<hr /> <hr />
<div id="content"> <div id="content">
<form action="/util" method="POST" id="main-form"> <form action="/util" method="POST" id="main-form">
{{range $t, $thread := $thread_arr}} {{range $t, $thread := .threads}}
{{$op := $thread.OP}} {{$op := $thread.OP}}
<div class="thread"> <div class="thread">
<div class="op-post" id="op{{$op.ID}}"> <div class="op-post" id="op{{$op.ID}}">
{{if stringNeq $op.Filename ""}} {{if ne $op.Filename ""}}
{{if stringNeq $op.Filename "deleted"}} {{if ne $op.Filename "deleted"}}
<div class="file-info">File: <a href="src/{{$op.Filename}}" target="_blank">{{$op.Filename}}</a> - ({{formatFilesize $op.Filesize}} , {{$op.ImageW}}x{{$op.ImageH}}, {{$op.FilenameOriginal}})</div> <div class="file-info">File: <a href="src/{{$op.Filename}}" target="_blank">{{$op.Filename}}</a> - ({{formatFilesize $op.Filesize}} , {{$op.ImageW}}x{{$op.ImageH}}, {{$op.FilenameOriginal}})</div>
<a class="upload-container" href="{{$config.SiteWebfolder}}{{$board.Dir}}/src/{{$op.Filename}}"><img src="{{$config.SiteWebfolder}}{{$board.Dir}}/thumb/{{imageToThumbnailPath $op.Filename}}" width="{{$op.ThumbW}}" height="{{$op.ThumbH}}" class="upload" /></a> <a class="upload-container" href="{{$.config.SiteWebfolder}}{{$.board.Dir}}/src/{{$op.Filename}}"><img src="{{$.config.SiteWebfolder}}{{$.board.Dir}}/thumb/{{imageToThumbnailPath $op.Filename}}" width="{{$op.ThumbW}}" height="{{$op.ThumbH}}" class="upload" /></a>
{{else}} {{else}}
<div class="file-deleted-box" style="text-align:center;">File removed</div> <div class="file-deleted-box" style="text-align:center;">File removed</div>
{{end}} {{end}}
{{end}} {{end}}
<input type="checkbox" id="check{{$op.ID}}" name="check{{$op.ID}}" /><label class="post-info" for="check{{$op.ID}}"> <span class="subject">{{$op.Subject}}</span> <span class="postername">{{if stringNeq $op.Email ""}}<a href="mailto:{{$op.Email}}">{{end}}{{if stringNeq $op.Name ""}}{{$op.Name}}{{else}}{{if stringEq $op.Tripcode ""}}{{$board.Anonymous}}{{end}}{{end}}{{if stringNeq $op.Email ""}}</a>{{end}}</span>{{if stringNeq $op.Tripcode ""}}<span class="tripcode">!{{$op.Tripcode}}</span>{{end}} {{formatTimestamp $op.Timestamp}} </label><a href="/{{$board.Dir}}/res/{{$op.ID}}.html#{{$op.ID}}">No.</a> <a href="javascript:quote({{$op.ID}})" class="backlink-click">{{$op.ID}}</a> <span class="post-links"> <span class="thread-ddown">[<a href="javascript:void(0)">&#9660;</a>]</span> <span>[<a href="/{{$board.Dir}}/res/{{$op.ID}}.html">View</a>]</span></span><br /> <input type="checkbox" id="check{{$op.ID}}" name="check{{$op.ID}}" /><label class="post-info" for="check{{$op.ID}}"> <span class="subject">{{$op.Subject}}</span> <span class="postername">{{if ne $op.Email ""}}<a href="mailto:{{$op.Email}}">{{end}}{{if ne $op.Name ""}}{{$op.Name}}{{else}}{{if eq $op.Tripcode ""}}{{$.board.Anonymous}}{{end}}{{end}}{{if ne $op.Email ""}}</a>{{end}}</span>{{if ne $op.Tripcode ""}}<span class="tripcode">!{{$op.Tripcode}}</span>{{end}} {{formatTimestamp $op.Timestamp}} </label><a href="/{{$.board.Dir}}/res/{{$op.ID}}.html#{{$op.ID}}">No.</a> <a href="javascript:quote({{$op.ID}})" class="backlink-click">{{$op.ID}}</a> <span class="post-links"> <span class="thread-ddown">[<a href="javascript:void(0)">&#9660;</a>]</span> <span>[<a href="/{{$.board.Dir}}/res/{{$op.ID}}.html">View</a>]</span></span><br />
<div class="post-text">{{truncateMessage $op.MessageHTML 2222 18}}</div> <div class="post-text">{{truncateMessage $op.MessageHTML 2222 18}}</div>
{{if gt $thread.NumReplies 3}} {{if gt $thread.NumReplies 3}}
<b>{{subtract $thread.NumReplies 3}} post{{if gt $thread.NumReplies 4}}s{{end}} omitted</b> <b>{{subtract $thread.NumReplies 3}} post{{if gt $thread.NumReplies 4}}s{{end}} omitted</b>
@ -41,11 +28,11 @@
<div class="reply-container" id="replycontainer{{$reply.ID}}"> <div class="reply-container" id="replycontainer{{$reply.ID}}">
<a class="anchor" id="{{$reply.ID}}"></a> <a class="anchor" id="{{$reply.ID}}"></a>
<div class="reply" id="reply{{$reply.ID}}"> <div class="reply" id="reply{{$reply.ID}}">
<input type="checkbox" id="check{{$reply.ID}}" name="check{{$reply.ID}}" /> <label class="post-info" for="check{{$reply.ID}}"> <span class="subject">{{$reply.Subject}}</span> <span class="postername">{{if stringNeq $reply.Email ""}}<a href="mailto:{{$reply.Email}}">{{end}}{{if stringNeq $reply.Name ""}}{{$reply.Name}}{{else}}{{if stringEq $reply.Tripcode ""}}{{$board.Anonymous}}{{end}}{{end}}{{if stringNeq $reply.Email ""}}</a>{{end}}</span>{{if stringNeq $reply.Tripcode ""}}<span class="tripcode">!{{$reply.Tripcode}}</span>{{end}} {{formatTimestamp $reply.Timestamp}} </label><a href="/{{$board.Dir}}/res/{{$op.ID}}.html#{{$reply.ID}}">No.</a> <a href="javascript:quote({{$reply.ID}})" class="backlink-click">{{$reply.ID}}</a> <span class="post-links"><span class="thread-ddown">[<a href="javascript:void(0)">&#9660;</a>]</span></span><br /> <input type="checkbox" id="check{{$reply.ID}}" name="check{{$reply.ID}}" /> <label class="post-info" for="check{{$reply.ID}}"> <span class="subject">{{$reply.Subject}}</span> <span class="postername">{{if ne $reply.Email ""}}<a href="mailto:{{$reply.Email}}">{{end}}{{if ne $reply.Name ""}}{{$reply.Name}}{{else}}{{if eq $reply.Tripcode ""}}{{.board.Anonymous}}{{end}}{{end}}{{if eq $reply.Email ""}}</a>{{end}}</span>{{if ne $reply.Tripcode ""}}<span class="tripcode">!{{$reply.Tripcode}}</span>{{end}} {{formatTimestamp $reply.Timestamp}} </label><a href="/{{$.board.Dir}}/res/{{$op.ID}}.html#{{$reply.ID}}">No.</a> <a href="javascript:quote({{$reply.ID}})" class="backlink-click">{{$reply.ID}}</a> <span class="post-links"><span class="thread-ddown">[<a href="javascript:void(0)">&#9660;</a>]</span></span><br />
{{if stringNeq $reply.Filename ""}} {{if ne $reply.Filename ""}}
{{if stringNeq $reply.Filename "deleted"}} {{if ne $reply.Filename "deleted"}}
<span class="file-info">File: <a href="src/{{$reply.Filename}}" target="_blank">{{$reply.Filename}}</a> - ({{formatFilesize $reply.Filesize}} , {{$reply.ImageW}}x{{$reply.ImageH}}, {{$reply.FilenameOriginal}})</span><br /> <span class="file-info">File: <a href="src/{{$reply.Filename}}" target="_blank">{{$reply.Filename}}</a> - ({{formatFilesize $reply.Filesize}} , {{$reply.ImageW}}x{{$reply.ImageH}}, {{$reply.FilenameOriginal}})</span><br />
<a class="upload-container" href="{{$config.SiteWebfolder}}{{$board.Dir}}/src/{{$reply.Filename}}"><img src="{{$config.SiteWebfolder}}{{$board.Dir}}/thumb/{{imageToThumbnailPath $reply.Filename}}" width="{{$reply.ThumbW}}" height="{{$reply.ThumbH}}" class="upload" /></a> <a class="upload-container" href="{{$.config.SiteWebfolder}}{{$.board.Dir}}/src/{{$reply.Filename}}"><img src="{{$.config.SiteWebfolder}}{{$.board.Dir}}/thumb/{{imageToThumbnailPath $reply.Filename}}" width="{{$reply.ThumbW}}" height="{{$reply.ThumbH}}" class="upload" /></a>
{{else}} {{else}}
<div class="file-deleted-box" style="text-align:center;">File removed</div> <div class="file-deleted-box" style="text-align:center;">File removed</div>
{{end}}{{end}} {{end}}{{end}}
@ -56,7 +43,8 @@
<hr />{{end}} <hr />{{end}}
<div id="right-bottom-content"> <div id="right-bottom-content">
<div id="report-delbox"> <div id="report-delbox">
<input type="hidden" name="board" value="{{$board.Dir}}" /> <input type="hidden" name="board" value="{{.board.Dir}}" />
<input type="hidden" name="boardid" value="{{.board.ID}}" />
<label>[<input type="checkbox" name="fileonly"/>File only]</label> <input type="password" size="10" name="password" id="delete-password" /> <input type="submit" name="delete_btn" value="Delete" onclick="return confirm('Are you sure you want to delete these posts?')" /><br /> <label>[<input type="checkbox" name="fileonly"/>File only]</label> <input type="password" size="10" name="password" id="delete-password" /> <input type="submit" name="delete_btn" value="Delete" onclick="return confirm('Are you sure you want to delete these posts?')" /><br />
Reason: <input type="text" size="10" name="reason" id="reason" /> <input type="submit" name="report_btn" value="Report" /><br /> Reason: <input type="text" size="10" name="reason" id="reason" /> <input type="submit" name="report_btn" value="Report" /><br />
Edit post <input type="submit" name="edit_btn" value="Edit" /> Edit post <input type="submit" name="edit_btn" value="Edit" />
@ -65,25 +53,25 @@
</form> </form>
<div id="left-bottom-content"> <div id="left-bottom-content">
<table id="pages"> <table id="pages">
<tr><td>{{if gt $board.CurrentPage 1}} <tr><td>{{if gt .board.CurrentPage 2}}
<form method="GET" action="{{$config.SiteWebfolder}}{{$board.Dir}}/{{subtract $board.CurrentPage 1}}.html"> <form method="GET" action="{{.config.SiteWebfolder}}{{.board.Dir}}/{{subtract .board.CurrentPage 1}}.html">
<input type="submit" value="Previous" /> <input type="submit" value="Previous" />
</form> </form>
{{else if intEq $board.CurrentPage 1}} {{else if intEq .board.CurrentPage 2}}
<form method="GET" action="{{$config.SiteWebfolder}}{{$board.Dir}}/"> <form method="GET" action="{{.config.SiteWebfolder}}{{.board.Dir}}/board.html">
<input type="submit" value="Previous" /> <input type="submit" value="Previous" />
</form> </form>
{{else}}Previous{{end}}</td> {{else}}Previous{{end}}</td>
<td>{{range $_,$i := makeLoop .board.NumPages 1}} [{{if eq $i $.board.CurrentPage}}<b>{{end}}<a href="{{$.config.SiteWebfolder}}{{$.board.Dir}}/{{if intEq $i 1}}board{{else}}{{$i}}{{end}}.html">{{$i}}</a>{{if eq $i $.board.CurrentPage}}</b>{{end}}]{{end}}</td>
<td>[<a href="{{$config.SiteWebfolder}}{{$board.Dir}}/">Index</a>]{{range $i,$_ := makeLoop $board.NumPages}} [{{if eq $i $board.CurrentPage}}<b>{{end}}<a href="{{$config.SiteWebfolder}}{{$board.Dir}}/{{add $i 1}}.html">{{add $i 1}}</a>{{if eq $i $board.CurrentPage}}</b>{{end}}]{{end}}</td> <td>{{if lt .board.CurrentPage .board.NumPages}}
<td>{{if lt $board.CurrentPage $board.NumPages}} <form method="GET" action="{{.config.SiteWebfolder}}{{.board.Dir}}/{{add .board.CurrentPage 1}}.html">
<form method="GET" action="{{$config.SiteWebfolder}}{{$board.Dir}}/{{add $board.CurrentPage 1}}.html">
<input type="submit" value="Next" /> <input type="submit" value="Next" />
</form> </form>
{{else}}Next{{end}}</td></tr> {{else}}Next{{end}}</td></tr>
</table> </table>
<span id="boardmenu-bottom"> <span id="boardmenu-bottom">
[{{range $i, $boardlink := $board_arr}} {{if gt $i 0}}/{{end}} <a href="/{{$boardlink.Dir}}/">{{$boardlink.Dir}}</a> {{end}}] [{{range $i, $boardlink := $.boards}}{{if gt $i 0}}/{{end}} <a href="/{{$boardlink.Dir}}/">{{$boardlink.Dir}}</a> {{end}}]
</span> </span>
</div> </div>
</div> </div>
{{template "global_footer.html" .}}

View file

@ -2,36 +2,37 @@
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
{{if stringNeq $op.Subject ""}} {{with .op.Subject}}
<title>/{{$board.Dir}}/ - {{truncateString $op.Subject 20 true}}</title> {{if ne .op.Subject ""}}
{{else}}{{if stringNeq $op.MessageHTML ""}} <title>/{{.board.Dir}}/ - {{truncateString .op.Subject 20 true}}</title>
<title>/{{$board.Dir}}/ - {{truncateString $op.MessageText 20 true}}</title> {{else if ne .op.MessageHTML ""}}
{{end}}{{end}} <title>/{{.board.Dir}}/ - {{truncateString .op.MessageText 20 true}}</title>
<title>/{{$board.Dir}}/ - {{$board.Title}}</title> {{end}}
{{else}}<title>/{{.board.Dir}}/ - {{.board.Title}}</title>{{end}}
<title>/{{.board.Dir}}/ - {{.board.Title}}</title>
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script> <script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var styles = [{{range $ii, $style := $config.Styles_img}}{{if gt $ii 0}}, {{end}}"{{$style}}"{{end}}]; var styles = [{{range $ii, $style := $.config.Styles_img}}{{if gt $ii 0}}, {{end}}"{{$style}}"{{end}}];
var webroot = "{{$config.SiteWebfolder}}"; var webroot = "{{$.config.SiteWebfolder}}";
var thread_type = "thread"; var thread_type = "thread";
function changePage(sel) { function changePage(sel) {
window.location = webroot+"test/res/{{$op.ID}}p"+sel.value+".html"; window.location = webroot+"{{$.board.Dir}}/res/{{$.op.ID}}p"+sel.value+".html";
} }
</script> </script>
<script type="text/javascript" src="/javascript/gochan.js"></script> <script type="text/javascript" src="/javascript/gochan.js"></script>
<script type="text/javascript" src="/javascript/manage.js"></script> <script type="text/javascript" src="/javascript/manage.js"></script>
<link rel="stylesheet" href="/css/global/img.css" /> <link rel="stylesheet" href="/css/global/img.css" />
{{range $i, $style := $config.Styles_img}} {{range $_, $style := .config.Styles_img}}
<link rel="{{if isStyleNotDefault_img $style}}alternate {{end}}stylesheet" href="/css/{{$style}}/img.css" />{{end}} <link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/img.css" />{{end}}
<link rel="shortcut icon" href="/favicon.png" /> <link rel="shortcut icon" href="/favicon.png" />
</head> </head>
<body> <body>
<div id="topbar"> <div id="topbar">
{{range $i, $board := $board_arr}} {{range $i, $board := .boards}}<a href="/{{$board.Dir}}/" class="topbar-item">/{{$board.Dir}}/</a>{{end}}
<a href="/{{$board.Dir}}/" class="topbar-item">/{{$board.Dir}}/</a>
{{end}}
</div> </div>
<header> <header>
<h1>/{{$board.Dir}}/ - {{$board.Title}}</h1> <h1>/{{$.board.Dir}}/ - {{$.board.Title}}</h1>
<div id="board-subtitle">{{$board.Subtitle}}</div> <div id="board-subtitle">{{$.board.Subtitle}}</div>
</header> </header>
<hr /> <hr />

View file

@ -1,5 +1,6 @@
{{template "img_header.html" .}}
<div id="threadlinks-top"> <div id="threadlinks-top">
<a href="{{$config.SiteWebfolder}}{{$board.Dir}}/board.html" >Return</a><br /> <a href="{{$.config.SiteWebfolder}}{{$.board.Dir}}/board.html" >Return</a><br />
<select id="changepage" onchange="changePage(this)"> <select id="changepage" onchange="changePage(this)">
<option value="">Select page...</option> <option value="">Select page...</option>
<option value="1">Page 1</option> <option value="1">Page 1</option>
@ -7,62 +8,47 @@
<option value="3">Page 3</option> <option value="3">Page 3</option>
<option value="4">Page 4</option> <option value="4">Page 4</option>
</select> </select>
{{/*}}<a href="{{$op.ID}}-100.html">First 100 posts</a><br />
<a href="{{$op.ID}}+50.html">Last 50 posts</a><br />{{*/}}
</div> </div>
<div id="right-sidelinks"> <div id="right-sidelinks">
<a href="{{$config.SiteWebfolder}}{{$board.Dir}}/catalog.html">Board catalog</a><br /> <a href="{{$.config.SiteWebfolder}}{{$.board.Dir}}/catalog.html">Board catalog</a><br />
</div>
<div id="postbox-area">
<form name="postform" action="/post" method="POST" enctype="multipart/form-data">
<input type="hidden" name="threadid" value="{{$op.ID}}" />
<input type="hidden" name="boardid" value="{{$op.BoardID}}" />
<input type="text" name="username" style="display:none" />
<table id="postbox-static">
<tr><th class="postblock">Name</th><td><input type="text" id="postname" name="postname" maxlength="50" size="28" {{/* value="Name" onFocus="if(this.value=='Name') {this,value= ''}" onBlur="if(this.value == '') {this.value = 'Name'}"*/}}/></td></tr>
<tr><th class="postblock">Email</th><td><input type="text" id="postemail" name="postemail" maxlength="50" size="28" /></td></tr>
<tr><th class="postblock">Subject</th><td><input type="text" name="postsubject" maxlength="100" size="35" /><input type="submit" value="Post"/></td></tr>
<tr><th class="postblock">Message</th><td><textarea rows="4" cols="48" name="postmsg" id="postmsg"></textarea></td></tr>
<tr><th class="postblock">File</th><td><input name="imagefile" type="file"><input type="checkbox" id="spoiler" name="spoiler"/><label for="spoiler">Spoiler</label></td></tr>
<tr><th class="postblock">Password</th><td><input type="password" id="postpassword" name="postpassword" size="14" /> (for post/file deletion)</td></tr>
</table>
</form>
</div> </div>
{{template "postbox.html" .}}
<hr /> <hr />
<div id="content"> <div id="content">
<form action="/util" method="POST" id="main-form"> <form action="/util" method="POST" id="main-form">
<div class="thread" id="{{$op.ID}}"> <div class="thread" id="{{$.op.ID}}">
<div class="op-post" id="op{{$op.ID}}"> <div class="op-post" id="op{{.op.ID}}">
{{if stringNeq $op.Filename ""}} {{if ne $.op.Filename ""}}
{{if stringNeq $op.Filename "deleted"}} {{if ne $.op.Filename "deleted"}}
<div class="file-info">File: <a href="../src/{{$op.Filename}}" target="_blank">{{$op.Filename}}</a> - ({{formatFilesize $op.Filesize}} , {{$op.ImageW}}x{{$op.ImageH}}, {{$op.FilenameOriginal}})</div> <div class="file-info">File: <a href="../src/{{.op.Filename}}" target="_blank">{{.op.Filename}}</a> - ({{formatFilesize .op.Filesize}} , {{.op.ImageW}}x{{.op.ImageH}}, {{.op.FilenameOriginal}})</div>
<a class="upload-container" href="{{$config.SiteWebfolder}}{{$board.Dir}}/src/{{$op.Filename}}"><img src="{{$config.SiteWebfolder}}{{$board.Dir}}/thumb/{{imageToThumbnailPath $op.Filename}}" width="{{$op.ThumbW}}" height="{{$op.ThumbH}}" class="upload" /></a> <a class="upload-container" href="{{.config.SiteWebfolder}}{{.board.Dir}}/src/{{.op.Filename}}"><img src="{{.config.SiteWebfolder}}{{.board.Dir}}/thumb/{{imageToThumbnailPath .op.Filename}}" width="{{.op.ThumbW}}" height="{{.op.ThumbH}}" class="upload" /></a>
{{else}} {{else}}
<div class="file-deleted-box" style="text-align:center;">File removed</div> <div class="file-deleted-box" style="text-align:center;">File removed</div>
{{end}}{{end}} {{end}}{{end}}
<input type="checkbox" id="check{{$op.ID}}" name="check{{$op.ID}}" /><label class="post-info" for="check{{$op.ID}}"> <span class="subject">{{$op.Subject}}</span> <span class="postername">{{if stringNeq $op.Email ""}}<a href="mailto:{{$op.Email}}">{{end}}{{if stringNeq $op.Name ""}}{{$op.Name}}{{else}}{{if stringEq $op.Tripcode ""}}{{$board.Anonymous}}{{end}}{{end}}{{if stringNeq $op.Email ""}}</a>{{end}}</span>{{if stringNeq $op.Tripcode ""}}<span class="tripcode">!{{$op.Tripcode}}</span>{{end}} {{formatTimestamp $op.Timestamp}} </label><a href="/{{$board.Dir}}/res/{{$op.ID}}.html#{{$op.ID}}">No.</a> <a href="javascript:quote({{$op.ID}})" class="backlink-click">{{$op.ID}}</a> <span class="post-links"> <span class="thread-ddown">[<a href="javascript:void(0)">&#9660;</a>]</span></span><br /> <input type="checkbox" id="check{{.op.ID}}" name="check{{.op.ID}}" /><label class="post-info" for="check{{.op.ID}}"> <span class="subject">{{.op.Subject}}</span> <span class="postername">{{if ne .op.Email ""}}<a href="mailto:{{.op.Email}}">{{end}}{{if ne .op.Name ""}}{{.op.Name}}{{else}}{{if eq .op.Tripcode ""}}{{.board.Anonymous}}{{end}}{{end}}{{if ne .op.Email ""}}</a>{{end}}</span>{{if ne .op.Tripcode ""}}<span class="tripcode">!{{.op.Tripcode}}</span>{{end}} {{formatTimestamp .op.Timestamp}} </label><a href="/{{.board.Dir}}/res/{{.op.ID}}.html#{{.op.ID}}">No.</a> <a href="javascript:quote({{.op.ID}})" class="backlink-click">{{.op.ID}}</a> <span class="post-links"> <span class="thread-ddown">[<a href="javascript:void(0)">&#9660;</a>]</span></span><br />
<div class="post-text">{{$op.MessageHTML}}</div> <div class="post-text">{{.op.MessageHTML}}</div>
</div> </div>
{{range $reply_num,$reply := $post_arr}}{{if gt $reply_num 0}} {{range $reply_num,$reply := .posts}}
<div class="reply-container" id="replycontainer{{$reply.ID}}"> <div class="reply-container" id="replycontainer{{$reply.ID}}">
<a class="anchor" id="{{$reply.ID}}"></a> <a class="anchor" id="{{$reply.ID}}"></a>
<div class="reply" id="reply{{$reply.ID}}"> <div class="reply" id="reply{{$reply.ID}}">
<input type="checkbox" id="check{{$reply.ID}}" name="check{{$reply.ID}}" /> <label class="post-info" for="check{{$reply.ID}}"> <span class="subject">{{$reply.Subject}}</span> <span class="postername">{{if stringNeq $reply.Email ""}}<a href="mailto:{{$reply.Email}}">{{end}}{{if stringNeq $reply.Name ""}}{{$reply.Name}}{{else}}{{if stringEq $reply.Tripcode ""}}{{$board.Anonymous}}{{end}}{{end}}{{if stringNeq $reply.Email ""}}</a>{{end}}</span>{{if stringNeq $reply.Tripcode ""}}<span class="tripcode">!{{$reply.Tripcode}}</span>{{end}} {{formatTimestamp $reply.Timestamp}} </label><a href="/{{$board.Dir}}/res/{{$op.ID}}.html#{{$reply.ID}}">No.</a> <a href="javascript:quote({{$reply.ID}})" class="backlink-click">{{$reply.ID}}</a> <span class="post-links"><span class="thread-ddown">[<a href="javascript:void(0)">&#9660;</a>]</span></span><br /> <input type="checkbox" id="check{{$reply.ID}}" name="check{{$reply.ID}}" /> <label class="post-info" for="check{{$reply.ID}}"> <span class="subject">{{$reply.Subject}}</span> <span class="postername">{{if ne $reply.Email ""}}<a href="mailto:{{$reply.Email}}">{{end}}{{if ne $reply.Name ""}}{{$reply.Name}}{{else}}{{if eq $reply.Tripcode ""}}{{$.board.Anonymous}}{{end}}{{end}}{{if ne $reply.Email ""}}</a>{{end}}</span>{{if ne $reply.Tripcode ""}}<span class="tripcode">!{{$reply.Tripcode}}</span>{{end}} {{formatTimestamp $reply.Timestamp}} </label><a href="/{{$.board.Dir}}/res/{{$.op.ID}}.html#{{$reply.ID}}">No.</a> <a href="javascript:quote({{$reply.ID}})" class="backlink-click">{{$reply.ID}}</a> <span class="post-links"><span class="thread-ddown">[<a href="javascript:void(0)">&#9660;</a>]</span></span><br />
{{if stringNeq $reply.Filename ""}} {{if ne $reply.Filename ""}}
{{if stringNeq $reply.Filename "deleted"}} {{if ne $reply.Filename "deleted"}}
<span class="file-info">File: <a href="../src/{{$reply.Filename}}" target="_blank">{{$reply.Filename}}</a> - ({{formatFilesize $reply.Filesize}} , {{$reply.ImageW}}x{{$reply.ImageH}}, {{$reply.FilenameOriginal}})</span><br /> <span class="file-info">File: <a href="../src/{{$reply.Filename}}" target="_blank">{{$reply.Filename}}</a> - ({{formatFilesize $reply.Filesize}} , {{$reply.ImageW}}x{{$reply.ImageH}}, {{$reply.FilenameOriginal}})</span><br />
<a class="upload-container" href="{{$config.SiteWebfolder}}{{$board.Dir}}/src/{{$reply.Filename}}"><img src="{{$config.SiteWebfolder}}{{$board.Dir}}/thumb/{{imageToThumbnailPath $reply.Filename}}" width="{{$reply.ThumbW}}" height="{{$reply.ThumbH}}" class="upload" /></a> <a class="upload-container" href="{{$.config.SiteWebfolder}}{{$.board.Dir}}/src/{{$reply.Filename}}"><img src="{{$.config.SiteWebfolder}}{{$.board.Dir}}/thumb/{{imageToThumbnailPath $reply.Filename}}" width="{{$reply.ThumbW}}" height="{{$reply.ThumbH}}" class="upload" /></a>
{{else}} {{else}}
<div class="file-deleted-box" style="text-align:center;">File removed</div> <div class="file-deleted-box" style="text-align:center;">File removed</div>
{{end}}{{end}} {{end}}{{end}}
<div class="post-text">{{$reply.MessageHTML}}</div> <div class="post-text">{{$reply.MessageHTML}}</div>
</div> </div>
</div>{{end}}{{end}} </div>{{end}}
</div> </div>
<hr /> <hr />
<div id="right-bottom-content"> <div id="right-bottom-content">
<div id="report-delbox"> <div id="report-delbox">
<input type="hidden" name="board" value="{{$board.Dir}}" /> <input type="hidden" name="board" value="{{.board.Dir}}" />
<input type="hidden" name="boardid" value="{{.board.ID}}" />
<label>[<input type="checkbox" name="fileonly"/>File only]</label> <input type="password" size="10" name="password" id="delete-password" /> <input type="submit" name="delete_btn" value="Delete" onclick="return confirm('Are you sure you want to delete these posts?')" /><br /> <label>[<input type="checkbox" name="fileonly"/>File only]</label> <input type="password" size="10" name="password" id="delete-password" /> <input type="submit" name="delete_btn" value="Delete" onclick="return confirm('Are you sure you want to delete these posts?')" /><br />
Reason: <input type="text" size="10" name="reason" id="reason" /> <input type="submit" name="report_btn" value="Report" /><br /> Reason: <input type="text" size="10" name="reason" id="reason" /> <input type="submit" name="report_btn" value="Report" /><br />
Edit post <input type="submit" name="edit_btn" value="Edit" /> Edit post <input type="submit" name="edit_btn" value="Edit" />
@ -72,22 +58,25 @@
<div id="left-bottom-content"> <div id="left-bottom-content">
<table id="pages"> <table id="pages">
<tr> <tr>
<td><a href="{{$config.SiteWebfolder}}{{$board.Dir}}/">Return</a></td> <td><a href="{{.config.SiteWebfolder}}{{.board.Dir}}/">Return</a></td>
<td>{{if gt $op.CurrentPage 1}} <td>{{if gt .op.CurrentPage 1}}
<form method="GET" action="{{$config.SiteWebfolder}}{{$board.Dir}}/res/{{$op.ID}}p{{subtract $op.CurrentPage 1}}.html"> <form method="GET" action="{{.config.SiteWebfolder}}{{.board.Dir}}/res/{{.op.ID}}p{{subtract .op.CurrentPage 1}}.html">
<input type="submit" value="Previous" /> <input type="submit" value="Previous" />
</form> </form>
{{else}}Previous{{end}} {{else}}Previous{{end}}
</td> </td>
<td>[<a href="{{$config.SiteWebfolder}}{{$board.Dir}}/res/{{$op.ID}}.html">All</a>]{{range $i,$_ := makeLoop $op.NumPages}} [{{if eq $i (subtract $op.CurrentPage 1)}}<b>{{end}}<a href="{{$config.SiteWebfolder}}{{$board.Dir}}/res/{{$op.ID}}p{{add $i 1}}.html">{{add $i 1}}</a>{{if eq $i (subtract $op.CurrentPage 1)}}</b>{{end}}]{{end}}</td> <td>[<a href="{{.config.SiteWebfolder}}{{.board.Dir}}/res/{{.op.ID}}.html">All</a>]
<td>{{if lt $op.CurrentPage $op.NumPages}} {{range $i,$_ := makeLoop .op.NumPages 0}}
<form method="GET" action="{{$config.SiteWebfolder}}{{$board.Dir}}/res/{{$op.ID}}p{{add $op.CurrentPage 1}}.html"> [{{if eq $i (subtract $.op.CurrentPage 1)}}<b>{{end}}<a href="{{$.config.SiteWebfolder}}{{$.board.Dir}}/res/{{$.op.ID}}p{{add $i 1}}.html">{{add $i 1}}</a>{{if eq $i (subtract $.op.CurrentPage 1)}}</b>{{end}}]{{end}}</td>
<td>{{if lt .op.CurrentPage .op.NumPages}}
<form method="GET" action="{{.config.SiteWebfolder}}{{.board.Dir}}/res/{{.op.ID}}p{{add .op.CurrentPage 1}}.html">
<input type="submit" value="Next" /> <input type="submit" value="Next" />
</form> </form>
{{else}}Next{{end}}</td></tr> {{else}}Next{{end}}</td></tr>
</table> </table>
<span id="boardmenu-bottom"> <span id="boardmenu-bottom">
[{{range $i, $boardlink := $board_arr}} {{if gt $i 0}}/{{end}} <a href="/{{$boardlink.Dir}}/">{{$boardlink.Dir}}</a> {{end}}] [{{range $i, $boardlink := .boards}} {{if gt $i 0}}/{{end}} <a href="/{{$boardlink.Dir}}/">{{$boardlink.Dir}}</a> {{end}}]
</span> </span>
</div> </div>
</div> </div>
{{template "global_footer.html" .}}

View file

@ -5,7 +5,7 @@
<tr><td>Directory</td><td><input type="text" name="dir" value="" /></td></tr> <tr><td>Directory</td><td><input type="text" name="dir" value="" /></td></tr>
<tr><td>Section</td><td><select name="section" selected="0"> <tr><td>Section</td><td><select name="section" selected="0">
<option value="none">Select section...</option> <option value="none">Select section...</option>
{{range $_, $section := $section_arr}} {{range $_, $section := .section_arr}}
<option value="{{$section.ID}}">{{$section.Name}}</option>{{end}} <option value="{{$section.ID}}">{{$section.Name}}</option>{{end}}
</select></td></tr> </select></td></tr>
<tr><td>Order</td><td><input type="text" name="order" value="0" /></td></tr> <tr><td>Order</td><td><input type="text" name="order" value="0" /></td></tr>
@ -15,19 +15,18 @@
<tr><td>Max image size</td><td><input type="text" name="maximagesize" value="4718592" /></td></tr> <tr><td>Max image size</td><td><input type="text" name="maximagesize" value="4718592" /></td></tr>
<tr><td>Max pages</td><td><input type="text" name="maxpages" value="11" /></td></tr> <tr><td>Max pages</td><td><input type="text" name="maxpages" value="11" /></td></tr>
<tr><td>Default style</td><td><select name="defaultstyle" selected=""> <tr><td>Default style</td><td><select name="defaultstyle" selected="">
{{range $i, $style := $config.Styles_img}} {{range $_, $style := .config.Styles_img}}<option value="{{$style}}">{{$style}}</option>{{end}}
<option value="{{$style}}">{{$style}}</option>{{end}}
</select></td></tr> </select></td></tr>
<tr><td>Locked</td><td><input type="checkbox" name="locked" {{if $board.Locked}}checked{{end}}/></td></tr> <tr><td>Locked</td><td><input type="checkbox" name="locked" {{if .board.Locked}}checked{{end}}/></td></tr>
<tr><td>Forced anonymity</td><td><input type="checkbox" name="forcedanon" {{if $board.ForcedAnon}}checked{{end}}/></td></tr> <tr><td>Forced anonymity</td><td><input type="checkbox" name="forcedanon" {{if .board.ForcedAnon}}checked{{end}}/></td></tr>
<tr><td>Anonymous name</td><td><input type="text" name="anonymous" value="{{$board.Anonymous}}" /></td></tr> <tr><td>Anonymous name</td><td><input type="text" name="anonymous" value="{{.board.Anonymous}}" /></td></tr>
<tr><td>Max age</td><td><input type="text" name="maxage" value="{{$board.MaxAge}}"/></td></tr> <tr><td>Max age</td><td><input type="text" name="maxage" value="{{.board.MaxAge}}"/></td></tr>
<tr><td>Bump limit<sup title="After this many posts, the thread will stop being bumped when a new post is made. ">[?]</sup></td><td><input type="text" name="autosageafter" value="{{$board.AutosageAfter}}"/></td></tr> <tr><td>Bump limit<sup title="After this many posts, the thread will stop being bumped when a new post is made. ">[?]</sup></td><td><input type="text" name="autosageafter" value="{{.board.AutosageAfter}}"/></td></tr>
<tr><td>No images after</td><td><input type="text" name="noimagesafter" value="{{$board.NoImagesAfter}}"/></td></tr> <tr><td>No images after</td><td><input type="text" name="noimagesafter" value="{{.board.NoImagesAfter}}"/></td></tr>
<tr><td>Max message length</td><td><input type="text" name="maxmessagelength" value="{{$board.MaxMessageLength}}"/></td></tr> <tr><td>Max message length</td><td><input type="text" name="maxmessagelength" value="{{.board.MaxMessageLength}}"/></td></tr>
<tr><td>Embeds allowed</td><td><input type="checkbox" name="embedsallowed" {{if $board.EmbedsAllowed}}checked{{end}}/></td></tr> <tr><td>Embeds allowed</td><td><input type="checkbox" name="embedsallowed" {{if .board.EmbedsAllowed}}checked{{end}}/></td></tr>
<tr><td>Redirect to thread<sup title="If checked, posts will redirect to the thread by default without requiring 'noko' in the email">[?]</sup></td><td><input type="checkbox" name="redirecttothread" {{if $board.RedirectToThread}}checked{{end}}/></td></tr> <tr><td>Redirect to thread<sup title="If checked, posts will redirect to the thread by default without requiring 'noko' in the email">[?]</sup></td><td><input type="checkbox" name="redirecttothread" {{if .board.RedirectToThread}}checked{{end}}/></td></tr>
<tr><td>Require an uploaded file</td><td><input type="checkbox" name="require_file" {{if $board.RequireFile}}checked{{end}}/></td></tr> <tr><td>Require an uploaded file</td><td><input type="checkbox" name="require_file" {{if .board.RequireFile}}checked{{end}}/></td></tr>
<tr><td>Enable catalog</td><td><input type="checkbox" name="enablecatalog" {{if $board.EnableCatalog}}checked{{end}}/></td></tr> <tr><td>Enable catalog</td><td><input type="checkbox" name="enablecatalog" {{if .board.EnableCatalog}}checked{{end}}/></td></tr>
</table> </table>
<input type="submit" /></form> <input type="submit" /></form>

View file

@ -2,21 +2,21 @@
<form action="/manage?action=config" method="POST"> <form action="/manage?action=config" method="POST">
<table> <table>
<tr><th>Name</th><th>Value</th><th>Description</th></tr> <tr><th>Name</th><th>Value</th><th>Description</th></tr>
<tr><th>DocumentRoot</th><td><input type="text" value="{{$config.DocumentRoot}}"/></td></tr> <tr><th>DocumentRoot</th><td><input type="text" value="{{.config.DocumentRoot}}"/></td></tr>
<tr><th>TemplateDir</th><td><input type="text" value="{{$config.TemplateDir}}" /></td></tr> <tr><th>TemplateDir</th><td><input type="text" value="{{.config.TemplateDir}}" /></td></tr>
<tr><th>LogDir</th><td><input type="text" value="{{$config.LogDir}}" /></td></tr> <tr><th>LogDir</th><td><input type="text" value="{{.config.LogDir}}" /></td></tr>
<tr><th>Lockdown</th><td><input type="checkbox" {{if $config.Lockdown}}checked{{end}}/></td></tr> <tr><th>Lockdown</th><td><input type="checkbox" {{if .config.Lockdown}}checked{{end}}/></td></tr>
<tr><th>LockdownMessage</th><td><input type="text" value="{{$config.LockdownMessage}}" /></td></tr> <tr><th>LockdownMessage</th><td><input type="text" value="{{.config.LockdownMessage}}" /></td></tr>
<tr><th>UseSillytags</th><td><input type="checkbox" /></td></tr> <tr><th>UseSillytags</th><td><input type="checkbox" /></td></tr>
<tr><th>Modboard</th><td><input type="text" value="{{$config.Modboard}}"></td></tr> <tr><th>Modboard</th><td><input type="text" value="{{.config.Modboard}}"></td></tr>
<tr><th>SiteName</th><td><input type="text" value="{{$config.SiteName}}" /></td></tr> <tr><th>SiteName</th><td><input type="text" value="{{.config.SiteName}}" /></td></tr>
<tr><th>SiteSlogan</th><td><input type="text" value="{{$config.SiteSlogan}}" /></td></tr> <tr><th>SiteSlogan</th><td><input type="text" value="{{.config.SiteSlogan}}" /></td></tr>
<tr><th>SiteHeaderURL</th><td><input type="text" value="{{$config.SiteHeaderURL}}" /></td></tr> <tr><th>SiteHeaderURL</th><td><input type="text" value="{{.config.SiteHeaderURL}}" /></td></tr>
{{/*<tr><th>SiteWebfolder</th><td><input type="text" value="{{$config.SiteWebFolder}}" /></td></tr>*/}} {{/*<tr><th>SiteWebfolder</th><td><input type="text" value="{{.config.SiteWebFolder}}" /></td></tr>*/}}
<tr><th>DomainRegex</th><td><input type="text" value="{{$config.DomainRegex}}" /></td><td>Don't touch this unless you know what you're doing! This could fuck your shit up<br />Default: (https|http)://(.*)/(.*)</td></tr> <tr><th>DomainRegex</th><td><input type="text" value="{{.config.DomainRegex}}" /></td><td>Don't touch this unless you know what you're doing! This could fuck your shit up<br />Default: (https|http)://(.*)/(.*)</td></tr>
</table> </table>
<input type="submit" /> <input type="submit" />
</form> </form>

View file

@ -1,7 +1,7 @@
<title>Gochan Manage page</title> <title>Gochan Manage page</title>
<link rel="stylesheet" href="/css/global/manage.css" /> <link rel="stylesheet" href="/css/global/manage.css" />
{{range $i, $style := .Styles_img}} {{range $i, $style := .Styles_img}}
<link rel="{{if isStyleNotDefault_img $style}}alternate {{end}}stylesheet" href="/css/{{$style}}/manage.css" />{{end}} <link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/manage.css" />{{end}}
<link rel="shortcut icon" href="/favicon.png" /> <link rel="shortcut icon" href="/favicon.png" />
<script type="text/javascript"> <script type="text/javascript">
var styles = [{{range $i, $style := .Styles_img}}{{if gt $i 0}}, {{end}}"{{$style}}"{{end}}]; var styles = [{{range $i, $style := .Styles_img}}{{if gt $i 0}}, {{end}}"{{$style}}"{{end}}];