From 3fc2d6a86799c12069f20c3e46a9f3836f9ed027 Mon Sep 17 00:00:00 2001 From: Azareal Date: Mon, 13 Aug 2018 20:34:00 +1000 Subject: [PATCH] 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. --- .eslintrc.json | 7 +- common/parser.go | 2 +- public/global.js | 122 ++++++----------------------------- public/init.js | 146 ++++++++++++++++++++++++++++++++++++++++++ routes.go | 9 ++- templates/header.html | 3 +- 6 files changed, 181 insertions(+), 108 deletions(-) create mode 100644 public/init.js diff --git a/.eslintrc.json b/.eslintrc.json index 702fb65a..44fd050f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,7 +22,10 @@ }, "globals": { "$": true, - "session": true, - "siteURL": true + "addHook": true, + "runHook": true, + "addInitHook": true, + "runInitHook": true, + "loadScript": true } } \ No newline at end of file diff --git a/common/parser.go b/common/parser.go index 2b58c587..59287749 100644 --- a/common/parser.go +++ b/common/parser.go @@ -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" } } diff --git a/public/global.js b/public/global.js index 2237eb25..eb66d260 100644 --- a/public/global.js +++ b/public/global.js @@ -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"); }; diff --git a/public/init.js b/public/init.js new file mode 100644 index 00000000..a49a8441 --- /dev/null +++ b/public/init.js @@ -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(); + }); + } +})(); \ No newline at end of file diff --git a/routes.go b/routes.go index 07bb8951..0bd9b04c 100644 --- a/routes.go +++ b/routes.go @@ -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) diff --git a/templates/header.html b/templates/header.html index b37fc87c..886d5718 100644 --- a/templates/header.html +++ b/templates/header.html @@ -6,12 +6,13 @@ {{range .Header.Stylesheets}} {{end}} + + {{range .Header.Scripts}} {{end}} - {{if .Header.MetaDesc}}{{end}}