1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-09-01 07:26:23 -07:00

Merge gochan.js and manage.js, add option to minify gochan.js

Also add manage page titles to be used for an API
This commit is contained in:
Eggbertx 2019-12-11 20:33:02 -08:00
parent e361223084
commit cbb5f1ec20
11 changed files with 246 additions and 277 deletions

View file

@ -71,6 +71,9 @@
"MakeURLsHyperlinked": true,
"NewTabOnOutlinks": true,
"MinifyHTML": true,
"MinifyJS": true,
"DateTimeFormat": "Mon, January 02, 2006 15:04 PM",
"AkismetAPIKey": "",
"UseCaptcha": false,

View file

@ -14,6 +14,169 @@ var movable_postpreviews = true;
var expandable_postrefs = true;
var opRegex = /(\d)+(p(\d)+)?.html$/;
var Staff = function(name,rank,boards) {
this.name = name;
this.rank = rank;
this.boards = boards;
}
function addStaffButtons() {
$jq("input#delete-password").remove();
$jq("input[value=Delete]").after("<input type=\"submit\" name=\"Ban\" value=\"Ban\" onclick=\"banSelectedPost(); return false;\" />")
}
function getManagePage() {
}
function banSelectedPost() {
var board_dir_arr = location.pathname.split("/");
if(board_dir_arr.length < 2) return;
var board_dir = board_dir_arr[1];
var checks = $jq("input[type=checkbox]");
if(checks.length == 0) {
alert("No posts selected");
return false;
}
var post_id = 0;
for(var i = 0; i < checks.length; i++) {
if(checks[i].id.indexOf("check") == 0) {
post_id = checks[i].id.replace("check", "");
break;
}
}
window.location = webroot + "manage?action=bans&dir=" + board_dir + "&postid=" + post_id;
}
function makeNewStaff() {
var on_manage_page = false; // true to submit, false for ajax;
if(window.location.pathname == "/manage") {
on_manage_page = true;
} else {
var username_txt = $jq("input#username").val();
var password_txt = $jq("input#password").val();
var rank_sel = $jq("select#rank").val();
$jq.ajax({
method: 'POST',
url: webroot+"manage?action=staff",
data: {
"do":"add",
username: username_txt,
password: password_txt,
rank: rank_sel,
boards: "all"
},
cache: false,
async:true,
success: function(result) {
var rank_str;
switch(rank_sel) {
case "3":
rank_str = "admin";
break;
case "2":
rank_str = "mod";
break;
case "1":
rank_str = "janitor";
break;
}
$jq("table#stafftable tr:last").after("<tr><td>"+username_txt+"</td><td>"+rank_str+"</td><td>all</td><td>now</td><td></td></tr>")
},
error: function() {
alert("Something went wrong...")
}
});
}
return on_manage_page;
}
function getStaff() {
var s;
$jq.ajax({
method: 'GET',
url: webroot+"manage",
data: {
action: 'getstaffjquery',
},
dataType:"text",
cache: true,
async:false,
success: function(result) {
var return_data = result.trim().split(";");
s = new Staff(return_data[0],return_data[1],return_data[2].split(","));
},
error: function() {
s = new Staff("nobody","0","");
}
});
return s;
}
function getStaffMenuHTML() {
var s = "<ul class=\"staffmenu\">";
$jq.ajax({
method: 'GET',
url: webroot+"manage",
data: {
action: 'staffmenu',
},
dataType:"text",
cache: true,
async:false,
success: function(result) {
var lines = result.substring(result.indexOf("body>")+5,result.indexOf("</body")).trim().split("\n")
var num_lines = lines.length;
for(var l = 0; l < num_lines; l++) {
if(lines[l] != "") {
if(lines[l].indexOf("<a href=") > -1) {
s += lines[l].substr(0,lines[l].indexOf("\">")+2)+"<li>"+$jq(lines[l]).text()+"</li></a>";
} else {
s += "<li>"+lines[l]+"</li>";
}
}
}
},
error: function() {
s = "Something went wrong :/";
}
});
return s+"</ul>";
}
function openStaffLightBox(action_url) {
$jq.ajax({
method: 'GET',
url: webroot+"manage",
data: {
action: action_url,
},
dataType:"html",
async:false,
success: function(result) {
var body = '<div id="body-mock">' + result.replace(/^[\s\S]*<body.*?>|<\/body>[\s\S]*$/ig, '') + '</div>';
var $body = $jq(body);
var header = $body.find("h1");
var header_text = header.text();
header.remove();
if(header_text == "") header_text = "Manage";
showLightBox(header_text,$body.html());
},
error: function(result) {
var responsetext = result.responseText;
header = responsetext.substring(responsetext.indexOf("<h1>")+4,responsetext.indexOf("</h1>"))
responsetext = responsetext.substring(responsetext.indexOf("</h1>") + 5, responsetext.indexOf("</body>"));
if(header == "") {
showLightBox("Manage",responsetext);
} else {
showLightBox(header,responsetext);
}
}
});
}
function preparePostPreviews(is_inline) {
var m_type = "mousemove";
if(!movable_postpreviews) m_type = "mouseover";

View file

@ -1,166 +0,0 @@
var Staff = function(name,rank,boards) {
this.name = name;
this.rank = rank;
this.boards = boards;
}
function addStaffButtons() {
$jq("input#delete-password").remove();
$jq("input[value=Delete]").after("<input type=\"submit\" name=\"Ban\" value=\"Ban\" onclick=\"banSelectedPost(); return false;\" />")
}
function getManagePage() {
}
function banSelectedPost() {
var board_dir_arr = location.pathname.split("/");
if(board_dir_arr.length < 2) return;
var board_dir = board_dir_arr[1];
var checks = $jq("input[type=checkbox]");
if(checks.length == 0) {
alert("No posts selected");
return false;
}
var post_id = 0;
for(var i = 0; i < checks.length; i++) {
if(checks[i].id.indexOf("check") == 0) {
post_id = checks[i].id.replace("check", "");
break;
}
}
window.location = webroot + "manage?action=bans&dir=" + board_dir + "&postid=" + post_id;
}
function makeNewStaff() {
var on_manage_page = false; // true to submit, false for ajax;
if(window.location.pathname == "/manage") {
on_manage_page = true;
} else {
var username_txt = $jq("input#username").val();
var password_txt = $jq("input#password").val();
var rank_sel = $jq("select#rank").val();
$jq.ajax({
method: 'POST',
url: webroot+"manage?action=staff",
data: {
"do":"add",
username: username_txt,
password: password_txt,
rank: rank_sel,
boards: "all"
},
cache: false,
async:true,
success: function(result) {
var rank_str;
switch(rank_sel) {
case "3":
rank_str = "admin";
break;
case "2":
rank_str = "mod";
break;
case "1":
rank_str = "janitor";
break;
}
$jq("table#stafftable tr:last").after("<tr><td>"+username_txt+"</td><td>"+rank_str+"</td><td>all</td><td>now</td><td></td></tr>")
},
error: function() {
alert("Something went wrong...")
}
});
}
return on_manage_page;
}
function getStaff() {
var s;
$jq.ajax({
method: 'GET',
url: webroot+"manage",
data: {
action: 'getstaffjquery',
},
dataType:"text",
cache: true,
async:false,
success: function(result) {
var return_data = result.trim().split(";");
s = new Staff(return_data[0],return_data[1],return_data[2].split(","));
},
error: function() {
s = new Staff("nobody","0","");
}
});
return s;
}
function getStaffMenuHTML() {
var s = "<ul class=\"staffmenu\">";
$jq.ajax({
method: 'GET',
url: webroot+"manage",
data: {
action: 'staffmenu',
},
dataType:"text",
cache: true,
async:false,
success: function(result) {
var lines = result.substring(result.indexOf("body>")+5,result.indexOf("</body")).trim().split("\n")
var num_lines = lines.length;
for(var l = 0; l < num_lines; l++) {
if(lines[l] != "") {
if(lines[l].indexOf("<a href=") > -1) {
s += lines[l].substr(0,lines[l].indexOf("\">")+2)+"<li>"+$jq(lines[l]).text()+"</li></a>";
} else {
s += "<li>"+lines[l]+"</li>";
}
}
}
},
error: function() {
s = "Something went wrong :/";
}
});
return s+"</ul>";
}
function openStaffLightBox(action_url) {
$jq.ajax({
method: 'GET',
url: webroot+"manage",
data: {
action: action_url,
},
dataType:"html",
async:false,
success: function(result) {
var body = '<div id="body-mock">' + result.replace(/^[\s\S]*<body.*?>|<\/body>[\s\S]*$/ig, '') + '</div>';
var $body = $jq(body);
var header = $body.find("h1");
var header_text = header.text();
header.remove();
if(header_text == "") header_text = "Manage";
showLightBox(header_text,$body.html());
},
error: function(result) {
var responsetext = result.responseText;
header = responsetext.substring(responsetext.indexOf("<h1>")+4,responsetext.indexOf("</h1>"))
responsetext = responsetext.substring(responsetext.indexOf("</h1>") + 5, responsetext.indexOf("</body>"));
if(header == "") {
showLightBox("Manage",responsetext);
} else {
showLightBox(header,responsetext);
}
}
});
}
/* $jq(document).ready(function() {
}); */

View file

@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strconv"
@ -419,21 +420,41 @@ func buildBoards(which ...int) (html string) {
return
}
func buildJSConstants() string {
err := initTemplates("js")
func buildJS() string {
// minify gochan.js (if enabled)
gochanMinJSPath := path.Join(config.DocumentRoot, "javascript", "gochan.min.js")
gochanMinJSFile, err := os.OpenFile(gochanMinJSPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
defer closeHandle(gochanMinJSFile)
if err != nil {
return handleError(1, "Error opening '"+gochanMinJSPath+"' for writing: "+err.Error())
}
gochanJSPath := path.Join(config.DocumentRoot, "javascript", "gochan.js")
gochanJSBytes, err := ioutil.ReadFile(gochanJSPath)
if err != nil {
return handleError(1, "Error reading '"+gochanJSPath+": "+err.Error())
}
if _, err := minifyWriter(gochanMinJSFile, gochanJSBytes, "text/javascript"); err != nil {
config.UseMinifiedGochanJS = false
return handleError(1, "Error minifying '"+gochanMinJSPath+"': "+err.Error())
}
config.UseMinifiedGochanJS = true
// build consts.js from template
if err = initTemplates("js"); err != nil {
return err.Error()
}
jsPath := path.Join(config.DocumentRoot, "javascript", "consts.js")
jsFile, err := os.OpenFile(jsPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
constsJSPath := path.Join(config.DocumentRoot, "javascript", "consts.js")
constsJSFile, err := os.OpenFile(constsJSPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
defer closeHandle(constsJSFile)
if err != nil {
return handleError(1, "Error opening '"+jsPath+"' for writing: "+err.Error())
return handleError(1, "Error opening '"+constsJSPath+"' for writing: "+err.Error())
}
if err = minifyTemplate(jsTmpl, config, jsFile, "text/javascript"); err != nil {
return handleError(1, "Error building '"+jsPath+"': "+err.Error())
if err = minifyTemplate(jsTmpl, config, constsJSFile, "text/javascript"); err != nil {
return handleError(1, "Error building '"+constsJSPath+"': "+err.Error())
}
return "Built '" + jsPath + "' successfully."
return "Built gochan.min.js and consts.js successfully."
}
func buildCatalog(which int) string {

View file

@ -32,7 +32,7 @@ func main() {
handleError(0, customError(err))
os.Exit(2)
}
println(1, buildJSConstants())
println(1, buildJS())
initCaptcha()
tempCleanerTicker = time.NewTicker(time.Minute * 5)
go tempCleaner()

17
src/gochan_test.go Normal file
View file

@ -0,0 +1,17 @@
package main
import (
"testing"
)
const (
tName = "UrA"
tTripcode = "#EAA30Kmm"
tEmail = "something@example.com"
tSubject = "Subject line"
tMessage = "Message text"
)
func TestGochan(t *testing.T) {
t.Fatal("This doesn't do anything interesting yet.")
}

View file

@ -20,10 +20,16 @@ import (
)
// ManageFunction represents the functions accessed by staff members at /manage?action=<functionname>.
// Eventually a plugin system might allow you to add more
type ManageFunction struct {
Title string
Permissions int // 0 -> non-staff, 1 => janitor, 2 => moderator, 3 => administrator
Callback func(writer http.ResponseWriter, request *http.Request) string //return string of html output
Callback func(writer http.ResponseWriter, request *http.Request) string `json:"-"` //return string of html output
}
func getManageFunctionsJSON() string {
var jsonStr string
return jsonStr
}
func callManageFunction(writer http.ResponseWriter, request *http.Request) {
@ -179,6 +185,7 @@ func createSession(key string, username string, password string, request *http.R
var manageFunctions = map[string]ManageFunction{
"cleanup": {
Title: "Cleanup",
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
html = "<h2 class=\"manage-header\">Cleanup</h2><br />"
@ -219,6 +226,7 @@ var manageFunctions = map[string]ManageFunction{
return
}},
"config": {
Title: "Configuration",
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
do := request.FormValue("do")
@ -430,7 +438,7 @@ var manageFunctions = map[string]ManageFunction{
status = "Error writing gochan.json: %s\n" + err.Error()
} else {
status = "Wrote gochan.json successfully <br />"
buildJSConstants()
buildJS()
}
}
}
@ -444,84 +452,8 @@ var manageFunctions = map[string]ManageFunction{
html += manageConfigBuffer.String()
return
}},
/*"purgeeverything": {
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
html = "<img src=\"/css/purge.jpg\" />"
rows, err := querySQL("SELECT dir FROM " + config.DBprefix + "boards")
defer closeHandle(rows)
if err != nil {
html += err.Error()
handleError(1, customError(err))
return
}
var board string
for rows.Next() {
if err = rows.Scan(&board); err != nil {
html += err.Error()
handleError(1, customError(err))
return
}
if _, err = deleteMatchingFiles(path.Join(config.DocumentRoot, board), ".html"); err != nil {
html += err.Error()
handleError(1, customError(err))
return
}
if _, err = deleteMatchingFiles(path.Join(config.DocumentRoot, board, "res"), ".*"); err != nil {
html += err.Error()
handleError(1, customError(err))
return
}
if _, err = deleteMatchingFiles(path.Join(config.DocumentRoot, board, "src"), ".*"); err != nil {
html += err.Error()
handleError(1, customError(err))
return
}
if _, err = deleteMatchingFiles(path.Join(config.DocumentRoot, board, "thumb"), ".*"); err != nil {
html += err.Error()
handleError(1, customError(err))
return
}
}
truncateSQL := "TRUNCATE " + config.DBprefix + "posts"
var values []interface{} // only used for SQLite since it doesn't have a proper TRUNCATE
if config.DBtype == "sqlite3" {
truncateSQL = "DELETE FROM " + config.DBprefix + "posts; DELETE FROM sqlite_sequence WHERE name = ?;"
values = append(values, config.DBprefix+"posts")
} else if config.DBtype == "postgres" {
truncateSQL += " RESTART IDENTITY"
}
if _, err = execSQL(truncateSQL, values...); err != nil {
html += handleError(0, err.Error()) + "<br />\n"
return
}
if _, err = execSQL("ALTER TABLE `" + config.DBprefix + "posts` AUTO_INCREMENT = 1"); err != nil {
html += handleError(0, err.Error()) + "<br />\n"
return
}
html += "<br />Everything purged, rebuilding all<br />" +
buildBoards() + "<hr />\n" +
buildFrontPage()
return
}},*/
"executesql": {
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
statement := request.FormValue("sql")
html = "<h1 class=\"manage-header\">Execute SQL statement(s)</h1><form method = \"POST\" action=\"/manage?action=executesql\">\n<textarea name=\"sql\" id=\"sql-statement\">" + statement + "</textarea>\n<input type=\"submit\" />\n</form>"
if statement != "" {
html += "<hr />"
if _, sqlerr := execSQL(statement); sqlerr != nil {
html += handleError(1, sqlerr.Error())
} else {
html += "Statement esecuted successfully."
}
}
return
}},
"login": {
Title: "Login",
Permissions: 0,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
if getStaffRank(request) > 0 {
@ -549,6 +481,7 @@ var manageFunctions = map[string]ManageFunction{
return
}},
"logout": {
Title: "Logout",
Permissions: 1,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
cookie, _ := request.Cookie("sessiondata")
@ -558,6 +491,7 @@ var manageFunctions = map[string]ManageFunction{
return "Logged out successfully"
}},
"announcements": {
Title: "Announcements",
Permissions: 1,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
html = "<h1 class=\"manage-header\">Announcements</h1><br />"
@ -588,6 +522,7 @@ var manageFunctions = map[string]ManageFunction{
return
}},
"bans": {
Title: "Bans",
Permissions: 1,
Callback: func(writer http.ResponseWriter, request *http.Request) (pageHTML string) {
var post Post
@ -714,6 +649,7 @@ var manageFunctions = map[string]ManageFunction{
return
}},
"boards": {
Title: "Boards",
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
do := request.FormValue("do")
@ -910,6 +846,7 @@ var manageFunctions = map[string]ManageFunction{
return
}},
"staffmenu": {
Title: "Staff menu",
Permissions: 1,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
rank := getStaffRank(request)
@ -940,12 +877,14 @@ var manageFunctions = map[string]ManageFunction{
return
}},
"rebuildfront": {
Title: "Rebuild front page",
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
initTemplates()
return buildFrontPage()
}},
"rebuildall": {
Title: "Rebuild everything",
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
initTemplates()
@ -953,15 +892,17 @@ var manageFunctions = map[string]ManageFunction{
return buildFrontPage() + "<hr />\n" +
buildBoardListJSON() + "<hr />\n" +
buildBoards() + "<hr />\n" +
buildJSConstants() + "<hr />\n"
buildJS() + "<hr />\n"
}},
"rebuildboards": {
Title: "Rebuild boards",
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
initTemplates()
return buildBoards()
}},
"reparsehtml": {
Title: "Reparse HTML",
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
posts, err := getPostArr(map[string]interface{}{
@ -988,6 +929,7 @@ var manageFunctions = map[string]ManageFunction{
return
}},
"recentposts": {
Title: "Recent posts",
Permissions: 1,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
limit := request.FormValue("limit")
@ -1033,13 +975,8 @@ var manageFunctions = map[string]ManageFunction{
html += "</table>"
return
}},
"killserver": {
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
os.Exit(0)
return
}},
"postinfo": {
Title: "Post info",
Permissions: 2,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
boardDir := request.FormValue("dir")
@ -1071,6 +1008,7 @@ var manageFunctions = map[string]ManageFunction{
return jsonStr
}},
"staff": {
Title: "Staff",
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
do := request.FormValue("do")
@ -1132,6 +1070,7 @@ var manageFunctions = map[string]ManageFunction{
return
}},
"tempposts": {
Title: "Temporary posts lists",
Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
html += "<h1 class=\"manage-header\">Temporary posts</h1>"

View file

@ -95,15 +95,7 @@ func (s GochanServer) serveFile(writer http.ResponseWriter, request *http.Reques
writer.Header().Add("Cache-Control", "max-age=5, must-revalidate")
fileBytes, _ = ioutil.ReadFile(filePath)
writer.Header().Add("Cache-Control", "max-age=86400")
if extension == "html" {
minifyWriter(writer, fileBytes, "text/html")
} else if extension == "js" {
minifyWriter(writer, fileBytes, "text/javascript")
} else if extension == "json" {
minifyWriter(writer, fileBytes, "application/json")
} else {
writer.Write(fileBytes)
}
writer.Write(fileBytes)
}
func serveNotFound(writer http.ResponseWriter, request *http.Request) {
@ -111,7 +103,7 @@ func serveNotFound(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(404)
errorPage, err := ioutil.ReadFile(config.DocumentRoot + "/error/404.html")
if err != nil {
writer.Write([]byte("Requested page not found, and 404 error page not found"))
writer.Write([]byte("Requested page not found, and /error/404.html not found"))
} else {
minifyWriter(writer, errorPage, "text/html")
}

View file

@ -338,8 +338,9 @@ type GochanConfig struct {
MakeURLsHyperlinked bool `description:"If checked, URLs in posts will be turned into a hyperlink. If unchecked, ExpandButton and NewTabOnOutlinks are ignored." default:"checked"`
NewTabOnOutlinks bool `description:"If checked, links to external sites will open in a new tab." default:"checked"`
MinifyHTML bool `description:"If checked, gochan will minify html files when building" default:"checked"`
MinifyJS bool `description:"If checked, gochan will minify js and json files when building" default:"checked"`
MinifyHTML bool `description:"If checked, gochan will minify html files when building" default:"checked"`
MinifyJS bool `description:"If checked, gochan will minify js and json files when building" default:"checked"`
UseMinifiedGochanJS bool `json:"-"`
DateTimeFormat string `description:"The format used for dates. See <a href=\"https://golang.org/pkg/time/#Time.Format\">here</a> for more info." default:"Mon, January 02, 2006 15:04 PM"`
AkismetAPIKey string `description:"The API key to be sent to Akismet for post spam checking. If the key is invalid, Akismet won't be used."`

View file

@ -4,7 +4,6 @@
<link rel="shortcut icon" href="{{.SiteWebfolder}}favicon.png" />
<script type="text/javascript" src="{{.SiteWebfolder}}javascript/consts.js"></script>
<script type="text/javascript" src="{{.SiteWebfolder}}javascript/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="{{.SiteWebfolder}}javascript/gochan.js"></script>
<script type="text/javascript" src="{{.SiteWebfolder}}javascript/manage.js"></script>
<script type="text/javascript" src="{{$.config.SiteWebfolder}}javascript/gochan{{if $.config.UseMinifiedGochanJS}}.min{{end}}.js"></script>
</head>
<body>

View file

@ -15,7 +15,7 @@
<link rel="shortcut icon" href="{{.config.SiteWebfolder}}favicon.png">
<script type="text/javascript" src="{{$.config.SiteWebfolder}}javascript/consts.js"></script>
<script type="text/javascript" src="{{$.config.SiteWebfolder}}javascript/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="{{$.config.SiteWebfolder}}javascript/gochan.js"></script>
<script type="text/javascript" src="{{$.config.SiteWebfolder}}javascript/gochan{{if $.config.UseMinifiedGochanJS}}.min{{end}}.js"></script>
<script type="text/javascript" src="{{$.config.SiteWebfolder}}javascript/manage.js"></script>
</head>
<body>