Added the init.js file to make certain bits of JS run sooner than others.

The template_*.js files should get cached now.
The phrase API results might get cached now.
Solved an edge case in the attachment parser where a redirect is triggered on a HTTPS site when the attachment is posted as a HTTP URL.
Fixed runHook in the JS files.
This commit is contained in:
Azareal 2018-08-13 20:34:00 +10:00
parent 317ab8856b
commit 3fc2d6a867
6 changed files with 181 additions and 108 deletions

View File

@ -22,7 +22,10 @@
}, },
"globals": { "globals": {
"$": true, "$": true,
"session": true, "addHook": true,
"siteURL": true "runHook": true,
"addInitHook": true,
"runInitHook": true,
"loadScript": true
} }
} }

View File

@ -758,7 +758,7 @@ func parseMediaBytes(data []byte) (media MediaEmbed, ok bool) {
hostname = strings.Split(Site.URL, ":")[0] hostname = strings.Split(Site.URL, ":")[0]
// ?- Test this as I'm not sure it'll do what it should. If someone's running SSL on port 80 or non-SSL on port 443 then... Well... They're in far worse trouble than this... // ?- Test this as I'm not sure it'll do what it should. If someone's running SSL on port 80 or non-SSL on port 443 then... Well... They're in far worse trouble than this...
port = Site.Port port = Site.Port
if scheme == "" && Site.EnableSsl { if Site.EnableSsl {
scheme = "https" scheme = "https"
} }
} }

View File

@ -1,32 +1,15 @@
'use strict'; 'use strict';
var formVars = {}; var formVars = {};
var tmplInits = {};
var tmplPhrases = []; // [key] array of phrases indexed by order of use
var phraseBox = {};
var alertList = []; var alertList = [];
var alertCount = 0; var alertCount = 0;
var moreTopicCount = 0; var moreTopicCount = 0;
var conn; var conn;
var me = {};
var selectedTopics = []; var selectedTopics = [];
var attachItemCallback = function(){} var attachItemCallback = function(){}
var hooks = {
"start_init": [],
"end_init": [],
};
// Topic move // Topic move
var forumToMoveTo = 0; var forumToMoveTo = 0;
function runHook(name, ...args) {
if(!(name in hooks)) return;
let hook = hooks[name];
for (const callback in hook) {
callback(...args);
}
}
// TODO: Write a friendlier error handler which uses a .notice or something, we could have a specialised one for alerts // TODO: Write a friendlier error handler which uses a .notice or something, we could have a specialised one for alerts
function ajaxError(xhr,status,errstr) { function ajaxError(xhr,status,errstr) {
console.log("The AJAX request failed"); console.log("The AJAX request failed");
@ -193,6 +176,10 @@ function runWebSockets() {
conn = new WebSocket("wss://" + document.location.host + "/ws/"); conn = new WebSocket("wss://" + document.location.host + "/ws/");
} else conn = new WebSocket("ws://" + document.location.host + "/ws/"); } else conn = new WebSocket("ws://" + document.location.host + "/ws/");
conn.onerror = (err) => {
console.log(err);
}
conn.onopen = () => { conn.onopen = () => {
console.log("The WebSockets connection was opened"); console.log("The WebSockets connection was opened");
conn.send("page " + document.location.pathname + '\r'); conn.send("page " + document.location.pathname + '\r');
@ -201,10 +188,12 @@ function runWebSockets() {
Notification.requestPermission(); Notification.requestPermission();
} }
} }
conn.onclose = () => { conn.onclose = () => {
conn = false; conn = false;
console.log("The WebSockets connection was closed"); console.log("The WebSockets connection was closed");
} }
conn.onmessage = (event) => { conn.onmessage = (event) => {
//console.log("WSMessage:", event.data); //console.log("WSMessage:", event.data);
if(event.data[0] == "{") { if(event.data[0] == "{") {
@ -270,102 +259,29 @@ function runWebSockets() {
} }
} }
// Temporary hack for templates
function len(item) {
return item.length;
}
function loadScript(name, callback) {
let url = "/static/"+name
$.getScript(url)
.done(callback)
.fail((e,xhr,settings,ex) => {
console.log("Unable to get script '"+url+"'");
console.log("e: ", e);
console.log("xhr: ", xhr);
console.log("settings: ", settings);
console.log("ex: ",ex);
console.trace();
});
}
function DoNothingButPassBack(item) {
return item;
}
function fetchPhrases() {
fetch("/api/phrases/?query=status,topic_list")
.then((resp) => resp.json())
.then((data) => {
console.log("loaded phrase endpoint data");
console.log("data:",data);
Object.keys(tmplInits).forEach((key) => {
let phrases = [];
let tmplInit = tmplInits[key];
for(let phraseName of tmplInit) {
phrases.push(data[phraseName]);
}
console.log("Adding phrases");
console.log("key:",key);
console.log("phrases:",phrases);
tmplPhrases[key] = phrases;
});
let prefixes = {};
Object.keys(data).forEach((key) => {
let prefix = key.split(".")[0];
if(prefixes[prefix]===undefined) {
prefixes[prefix] = {};
}
prefixes[prefix][key] = data[key];
});
Object.keys(prefixes).forEach((prefix) => {
console.log("adding phrase prefix '"+prefix+"' to box");
phraseBox[prefix] = prefixes[prefix];
});
});
}
(() => { (() => {
runHook("pre_iife"); addInitHook("pre_init", () => {
let loggedIn = document.head.querySelector("[property='x-loggedin']").content;
fetch("/api/me/")
.then((resp) => resp.json())
.then((data) => {
console.log("loaded me endpoint data");
console.log("data:",data);
me = data;
runHook("pre_init");
// We can only get away with this because template_alert has no phrases, otherwise it too would have to be part of the "dance", I miss Go concurrency :( // We can only get away with this because template_alert has no phrases, otherwise it too would have to be part of the "dance", I miss Go concurrency :(
loadScript("template_alert.js", () => { loadScript("template_alert.js", () => {
console.log("Loaded template_alert.js"); console.log("Loaded template_alert.js");
$(document).ready(() => {
alertsInitted = true; alertsInitted = true;
var alertMenuList = document.getElementsByClassName("menu_alerts"); var alertMenuList = document.getElementsByClassName("menu_alerts");
for(var i = 0; i < alertMenuList.length; i++) { for(var i = 0; i < alertMenuList.length; i++) {
loadAlerts(alertMenuList[i]); loadAlerts(alertMenuList[i]);
} }
}); });
});
if(window["WebSocket"]) runWebSockets(); if(window["WebSocket"]) runWebSockets();
else conn = false; else conn = false;
$(document).ready(mainInit); $(document).ready(mainInit);
}); });
if(loggedIn) {
let toLoad = 1;
loadScript("template_topics_topic.js", () => {
console.log("Loaded template_topics_topic.js");
toLoad--;
if(toLoad===0) fetchPhrases();
});
}
})(); })();
function mainInit(){ function mainInit(){
runHook("start_init"); runInitHook("start_init");
$(".more_topics").click((event) => { $(".more_topics").click((event) => {
event.preventDefault(); event.preventDefault();
@ -839,5 +755,5 @@ function mainInit(){
}) })
}); });
runHook("end_init"); runInitHook("end_init");
}; };

146
public/init.js Normal file
View File

@ -0,0 +1,146 @@
'use strict';
var me = {};
var phraseBox = {};
var tmplInits = {};
var tmplPhrases = []; // [key] array of phrases indexed by order of use
var hooks = {
"pre_iffe": [],
"pre_init": [],
"start_init": [],
"end_init": [],
};
var ranInitHooks = {}
function runHook(name, ...args) {
if(!(name in hooks)) {
console.log("Couldn't find hook '" + name + "'");
return;
}
console.log("Running hook '"+name+"'");
let hook = hooks[name];
for (const index in hook) {
hook[index](...args);
}
}
function addHook(name, callback) {
hooks[name].push(callback);
}
// InitHooks are slightly special, as if they are run, then any adds after the initial run will run immediately, this is to deal with the async nature of script loads
function runInitHook(name) {
runHook(name);
ranInitHooks[name] = true;
}
function addInitHook(name, callback) {
addHook(name, callback);
if(name in ranInitHooks) {
callback();
}
}
// Temporary hack for templates
function len(item) {
return item.length;
}
const asyncGetScript = (source) => {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
script.async = true;
const onloadHander = (haha, isAbort) => {
if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = null;
script.onreadystatechange = null;
script = undefined;
isAbort ? reject(haha) : resolve();
}
}
script.onerror = (haha) => {
reject(haha);
};
script.onload = onloadHander;
script.onreadystatechange = onloadHander;
script.src = source;
const prior = document.getElementsByTagName('script')[0];
prior.parentNode.insertBefore(script, prior);
});
};
function loadScript(name, callback) {
let url = "/static/"+name
asyncGetScript(url)
.then(callback)
.catch((haha) => {
console.log("Unable to get script '"+url+"'");
console.log("haha: ", haha);
console.trace();
});
}
function DoNothingButPassBack(item) {
return item;
}
function fetchPhrases() {
fetch("/api/phrases/?query=status,topic_list")
.then((resp) => resp.json())
.then((data) => {
console.log("loaded phrase endpoint data");
console.log("data:",data);
Object.keys(tmplInits).forEach((key) => {
let phrases = [];
let tmplInit = tmplInits[key];
for(let phraseName of tmplInit) {
phrases.push(data[phraseName]);
}
console.log("Adding phrases");
console.log("key:",key);
console.log("phrases:",phrases);
tmplPhrases[key] = phrases;
});
let prefixes = {};
Object.keys(data).forEach((key) => {
let prefix = key.split(".")[0];
if(prefixes[prefix]===undefined) {
prefixes[prefix] = {};
}
prefixes[prefix][key] = data[key];
});
Object.keys(prefixes).forEach((prefix) => {
console.log("adding phrase prefix '"+prefix+"' to box");
phraseBox[prefix] = prefixes[prefix];
});
});
}
(() => {
runInitHook("pre_iife");
let loggedIn = document.head.querySelector("[property='x-loggedin']").content;
fetch("/api/me/")
.then((resp) => resp.json())
.then((data) => {
console.log("loaded me endpoint data");
console.log("data:",data);
me = data;
runInitHook("pre_init");
});
if(loggedIn) {
let toLoad = 1;
loadScript("template_topics_topic.js", () => {
console.log("Loaded template_topics_topic.js");
toLoad--;
if(toLoad===0) fetchPhrases();
});
}
})();

View File

@ -101,10 +101,17 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
return nil return nil
} }
// TODO: Remove this line after we move routeAPIPhrases to the routes package
var cacheControlMaxAge = "max-age=" + strconv.Itoa(int(common.Day))
// TODO: Be careful with exposing the panel phrases here, maybe move them into a different namespace? We also need to educate the admin that phrases aren't necessarily secret // TODO: Be careful with exposing the panel phrases here, maybe move them into a different namespace? We also need to educate the admin that phrases aren't necessarily secret
// TODO: Move to the routes package
func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError { func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
// TODO: Don't make this too JSON dependent so that we can swap in newer more efficient formats // TODO: Don't make this too JSON dependent so that we can swap in newer more efficient formats
w.Header().Set("Content-Type", "application/json") h := w.Header()
h.Set("Content-Type", "application/json")
h.Set("Cache-Control", cacheControlMaxAge) //Cache-Control: max-age=31536000
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
return common.PreErrorJS("Bad Form", w, r) return common.PreErrorJS("Bad Form", w, r)

View File

@ -6,12 +6,13 @@
{{range .Header.Stylesheets}} {{range .Header.Stylesheets}}
<link href="/static/{{.}}" rel="stylesheet" type="text/css"> <link href="/static/{{.}}" rel="stylesheet" type="text/css">
{{end}} {{end}}
<meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" />
<script type="text/javascript" src="/static/init.js"></script>
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script> <script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="/static/chartist/chartist.min.js"></script> <script type="text/javascript" src="/static/chartist/chartist.min.js"></script>
{{range .Header.Scripts}} {{range .Header.Scripts}}
<script type="text/javascript" src="/static/{{.}}"></script> <script type="text/javascript" src="/static/{{.}}"></script>
{{end}} {{end}}
<meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" />
<script type="text/javascript" src="/static/global.js"></script> <script type="text/javascript" src="/static/global.js"></script>
<meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" /> <meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" />
{{if .Header.MetaDesc}}<meta name="description" content="{{.Header.MetaDesc}}" />{{end}} {{if .Header.MetaDesc}}<meta name="description" content="{{.Header.MetaDesc}}" />{{end}}