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": {
"$": true,
"session": true,
"siteURL": true
"addHook": 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]
// ?- 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
if scheme == "" && Site.EnableSsl {
if Site.EnableSsl {
scheme = "https"
}
}

View File

@ -1,32 +1,15 @@
'use strict';
var formVars = {};
var tmplInits = {};
var tmplPhrases = []; // [key] array of phrases indexed by order of use
var phraseBox = {};
var alertList = [];
var alertCount = 0;
var moreTopicCount = 0;
var conn;
var me = {};
var selectedTopics = [];
var attachItemCallback = function(){}
var hooks = {
"start_init": [],
"end_init": [],
};
// Topic move
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
function ajaxError(xhr,status,errstr) {
console.log("The AJAX request failed");
@ -193,6 +176,10 @@ function runWebSockets() {
conn = new WebSocket("wss://" + document.location.host + "/ws/");
} else conn = new WebSocket("ws://" + document.location.host + "/ws/");
conn.onerror = (err) => {
console.log(err);
}
conn.onopen = () => {
console.log("The WebSockets connection was opened");
conn.send("page " + document.location.pathname + '\r');
@ -201,10 +188,12 @@ function runWebSockets() {
Notification.requestPermission();
}
}
conn.onclose = () => {
conn = false;
console.log("The WebSockets connection was closed");
}
conn.onmessage = (event) => {
//console.log("WSMessage:", event.data);
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");
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 :(
loadScript("template_alert.js", () => {
console.log("Loaded template_alert.js");
addInitHook("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 :(
loadScript("template_alert.js", () => {
console.log("Loaded template_alert.js");
$(document).ready(() => {
alertsInitted = true;
var alertMenuList = document.getElementsByClassName("menu_alerts");
for(var i = 0; i < alertMenuList.length; i++) {
loadAlerts(alertMenuList[i]);
}
});
if(window["WebSocket"]) runWebSockets();
else conn = false;
$(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();
});
}
if(window["WebSocket"]) runWebSockets();
else conn = false;
$(document).ready(mainInit);
});
})();
function mainInit(){
runHook("start_init");
runInitHook("start_init");
$(".more_topics").click((event) => {
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
}
// 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: Move to the routes package
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
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()
if err != nil {
return common.PreErrorJS("Bad Form", w, r)

View File

@ -6,12 +6,13 @@
{{range .Header.Stylesheets}}
<link href="/static/{{.}}" rel="stylesheet" type="text/css">
{{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/chartist/chartist.min.js"></script>
{{range .Header.Scripts}}
<script type="text/javascript" src="/static/{{.}}"></script>
{{end}}
<meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" />
<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" />
{{if .Header.MetaDesc}}<meta name="description" content="{{.Header.MetaDesc}}" />{{end}}