diff --git a/common/files.go b/common/files.go
index f192e2d9..468945ed 100644
--- a/common/files.go
+++ b/common/files.go
@@ -224,7 +224,7 @@ func (list SFileList) JSTmplInit() error {
for name, _ := range Themes {
if strings.HasSuffix(shortName, "_"+name) {
- data = append(data, "\nlet Template_"+strings.TrimSuffix(shortName, "_"+name)+" = Template_"+shortName+";"...)
+ data = append(data, "\nvar Template_"+strings.TrimSuffix(shortName, "_"+name)+" = Template_"+shortName+";"...)
break
}
}
diff --git a/langs/english.json b/langs/english.json
index b9593f14..09f11142 100644
--- a/langs/english.json
+++ b/langs/english.json
@@ -90,7 +90,7 @@
"id_must_be_integer": "The ID must be an integer.",
"url_id_must_be_integer": "The ID in the URL needs to be a valid integer.",
- "register_might_be_machine":"You might be a machine.",
+ "register_might_be_machine":"Our algorithms have detected that you may be a machine. If not, please try to avoid acting so quickly.",
"register_need_username":"You didn't put in a username.",
"register_need_email":"You didn't put in an email.",
"register_first_word_numeric":"The first word of your name must not be purely numeric",
@@ -709,6 +709,7 @@
"option_no":"No",
"panel_menu_head":"Control Panel",
+ "panel_menu_aria":"The control panel menu",
"panel_menu_users":"Users",
"panel_menu_groups":"Groups",
"panel_menu_forums":"Forums",
diff --git a/public/global.js b/public/global.js
index e8474753..f1f5949f 100644
--- a/public/global.js
+++ b/public/global.js
@@ -7,6 +7,7 @@ var moreTopicCount = 0;
var conn = false;
var selectedTopics = [];
var attachItemCallback = function(){}
+var baseTitle = document.title;
// Topic move
var forumToMoveTo = 0;
@@ -17,9 +18,7 @@ function ajaxError(xhr,status,errstr) {
console.log("xhr", xhr);
console.log("status", status);
console.log("errstr", errstr);
- if(status=="parsererror") {
- console.log("The server didn't respond with a valid JSON response");
- }
+ if(status=="parsererror") console.log("The server didn't respond with a valid JSON response");
console.trace();
}
@@ -30,19 +29,28 @@ function postLink(event) {
}
function bindToAlerts() {
+ console.log("bindToAlerts");
$(".alertItem.withAvatar a").unbind("click");
$(".alertItem.withAvatar a").click(function(event) {
event.stopPropagation();
- $.ajax({ url: "/api/?action=set&module=dismiss-alert", type: "POST", dataType: "json", error: ajaxError, data: { asid: $(this).attr("data-asid") } });
+ event.preventDefault();
+ $.ajax({
+ url: "/api/?action=set&module=dismiss-alert",
+ type: "POST",
+ dataType: "json",
+ data: { asid: $(this).attr("data-asid") },
+ error: ajaxError,
+ success: () => {
+ window.location.href = this.getAttribute("href");
+ }
+ });
});
}
function addAlert(msg, notice = false) {
var mmsg = msg.msg;
if("sub" in msg) {
- for(var i = 0; i < msg.sub.length; i++) {
- mmsg = mmsg.replace("\{"+i+"\}", msg.sub[i]);
- }
+ for(var i = 0; i < msg.sub.length; i++) mmsg = mmsg.replace("\{"+i+"\}", msg.sub[i]);
}
let aItem = Template_alert({
@@ -51,7 +59,10 @@ function addAlert(msg, notice = false) {
Avatar: msg.avatar || "",
Message: mmsg
})
- alertMapping[msg.asid] = aItem;
+ //alertMapping[msg.asid] = aItem;
+ let div = document.createElement('div');
+ div.innerHTML = aItem.trim();
+ alertMapping[msg.asid] = div.firstChild;
alertList.push(msg.asid);
if(notice) {
@@ -74,21 +85,31 @@ function updateAlertList(menuAlerts) {
let alertCounterNode = menuAlerts.getElementsByClassName("alert_counter")[0];
alertCounterNode.textContent = "0";
- let outList = "";
+ alertListNode.innerHTML = "";
+ let any = false;
+ /*let outList = "";
let j = 0;
for(var i = 0; i < alertList.length && j < 8; i++) {
outList += alertMapping[alertList[i]];
j++;
+ }*/
+ let j = 0;
+ for(var i = 0; i < alertList.length && j < 8; i++) {
+ any = true;
+ alertListNode.appendChild(alertMapping[alertList[i]]);
+ //outList += alertMapping[alertList[i]];
+ j++;
}
-
- if(outList == "") outList = "
"+phraseBox["alerts"]["alerts.no_alerts"]+"
";
- alertListNode.innerHTML = outList;
+ if(!any) alertListNode.innerHTML = ""+phraseBox["alerts"]["alerts.no_alerts"]+"
";
if(alertCount != 0) {
alertCounterNode.textContent = alertCount;
menuAlerts.classList.add("has_alerts");
+ let nTitle = "("+alertCount+") "+baseTitle;
+ if(document.title!=nTitle) document.title = nTitle;
} else {
menuAlerts.classList.remove("has_alerts");
+ if(document.title!=baseTitle) document.title = baseTitle;
}
bindToAlerts();
@@ -155,14 +176,20 @@ function SplitN(data,ch,n) {
}
function wsAlertEvent(data) {
+ console.log("wsAlertEvent:",data)
addAlert(data, true);
+ alertCount++;
- var alist = "";
- for (var i = 0; i < alertList.length; i++) alist += alertMapping[alertList[i]];
+ let aTmp = alertList;
+ alertList = [alertList[alertList.length-1]];
+ aTmp = aTmp.slice(0,-1);
+ for(let i = 0; i < aTmp.length; i++) alertList.push(aTmp[i]);
+ //var alist = "";
+ //for (var i = 0; i < alertList.length; i++) alist += alertMapping[alertList[i]];
// TODO: Add support for other alert feeds like PM Alerts
var generalAlerts = document.getElementById("general_alerts");
// TODO: Make sure we update alertCount here
- updateAlertList(generalAlerts, alist);
+ updateAlertList(generalAlerts/*, alist*/);
}
function runWebSockets() {
@@ -179,9 +206,7 @@ function runWebSockets() {
console.log("The WebSockets connection was opened");
conn.send("page " + document.location.pathname + '\r');
// TODO: Don't ask again, if it's denied. We could have a setting in the UCP which automatically requests this when someone flips desktop notifications on
- if(me.User.ID > 0) {
- Notification.requestPermission();
- }
+ if(me.User.ID > 0) Notification.requestPermission();
}
conn.onclose = () => {
@@ -291,8 +316,13 @@ function runWebSockets() {
(() => {
addInitHook("pre_init", () => {
+ console.log("before notify on alert")
// 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 :(
- notifyOnScriptW("/static/template_alert", () => {}, () => {
+ notifyOnScriptW("template_alert", (e) => {
+ if(e!=undefined) console.log("failed alert? why?", e)
+ }, () => {
+ console.log("ha")
+ if(!Template_alert) throw("template function not found");
addInitHook("after_phrases", () => {
// TODO: The load part of loadAlerts could be done asynchronously while the update of the DOM could be deferred
$(document).ready(() => {
@@ -438,10 +468,13 @@ function mainInit(){
// TODO: Try to de-duplicate some of these fetch calls
fetch(url+q+"&js=1", {credentials: "same-origin"})
- .then((resp) => resp.json())
- .then((data) => {
+ .then((resp) => {
+ if(!resp.ok) throw(url+q+"&js=1 failed to load");
+ return resp.json();
+ }).then((data) => {
if(!"Topics" in data) throw("no Topics in data");
let topics = data["Topics"];
+ console.log("ajax navigated to different page");
// TODO: Fix the data race where the function hasn't been loaded yet
let out = "";
@@ -470,10 +503,14 @@ function mainInit(){
let url = "//"+window.location.host+"/topics/?fids="+fid;
fetch(url+"&js=1", {credentials: "same-origin"})
- .then((resp) => resp.json())
- .then((data) => {
+ .then((resp) => {
+ if(!resp.ok) throw(url+"&js=1 failed to load");
+ return resp.json();
+ }).then((data) => {
+ console.log("data:",data);
if(!"Topics" in data) throw("no Topics in data");
let topics = data["Topics"];
+ console.log("ajax navigated to "+that.innerText);
// TODO: Fix the data race where the function hasn't been loaded yet
let out = "";
@@ -481,6 +518,9 @@ function mainInit(){
$(".topic_list").html(out);
//$(".topic_list").addClass("single_forum");
+ baseTitle = that.innerText;
+ if(alertCount > 0) document.title = "("+alertCount+") "+baseTitle;
+ else document.title = baseTitle;
let obj = {Title: document.title, Url: url};
history.pushState(obj, obj.Title, obj.Url);
rebuildPaginator(data.LastPage)
@@ -515,18 +555,23 @@ function mainInit(){
// TODO: Try to de-duplicate some of these fetch calls
fetch(url+q+"&js=1", {credentials: "same-origin"})
- .then((resp) => resp.json())
- .then((data) => {
+ .then((resp) => {
+ if(!resp.ok) throw(url+q+"&js=1 failed to load");
+ return resp.json();
+ }).then((data) => {
if(!"Topics" in data) throw("no Topics in data");
let topics = data["Topics"];
+ console.log("ajax navigated to search page");
// TODO: Fix the data race where the function hasn't been loaded yet
let out = "";
for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]);
$(".topic_list").html(out);
- document.title = phraseBox["topic_list"]["topic_list.search_head"];
+ baseTitle = phraseBox["topic_list"]["topic_list.search_head"];
$(".topic_list_title h1").text(phraseBox["topic_list"]["topic_list.search_head"]);
+ if(alertCount > 0) document.title = "("+alertCount+") "+baseTitle;
+ else document.title = baseTitle;
let obj = {Title: document.title, Url: url+q};
history.pushState(obj, obj.Title, obj.Url);
rebuildPaginator(data.LastPage);
diff --git a/public/init.js b/public/init.js
index 1c1e0d1e..5c50a79d 100644
--- a/public/init.js
+++ b/public/init.js
@@ -81,23 +81,30 @@ function asyncGetScript(source) {
}
function notifyOnScript(source) {
+ source = "/static/"+source;
return new Promise((resolve, reject) => {
+ let ss = source.replace("/static/","");
+ try {
+ let ssp = ss.charAt(0).toUpperCase() + ss.slice(1)
+ console.log("ssp:",ssp)
+ if(window[ssp]) {
+ resolve();
+ return;
+ }
+ } catch(e) {}
+
+ console.log("source:",source)
let script = document.querySelectorAll('[src^="'+source+'"]')[0];
+ console.log("script:",script);
if(script===undefined) {
reject("no script found");
return;
}
- if(!script.readyState) {
- resolve();
- return;
- }
- const onloadHandler = (e, isAbort) => {
- if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
- script.onload = null;
- script.onreadystatechange = null;
- isAbort ? reject(e) : resolve();
- }
+ const onloadHandler = (e) => {
+ script.onload = null;
+ script.onreadystatechange = null;
+ resolve();
}
script.onerror = (e) => {
@@ -105,7 +112,6 @@ function notifyOnScript(source) {
};
script.onload = onloadHandler;
script.onreadystatechange = onloadHandler;
- script.src = source;
});
}
@@ -119,7 +125,7 @@ function notifyOnScriptW(name, complete, success) {
console.log("Unable to get script name '"+name+"'");
console.log("e: ", e);
console.trace();
- complete();
+ complete(e);
});
}
@@ -168,6 +174,7 @@ function RelativeTime(date) {
function initPhrases() {
console.log("in initPhrases")
+ console.log("tmlInits:",tmplInits)
fetchPhrases("status,topic_list,alerts,paginator,analytics")
}
@@ -206,11 +213,13 @@ function fetchPhrases(plist) {
runInitHook("pre_iife");
let toLoad = 2;
// TODO: Shunt this into loggedIn if there aren't any search and filter widgets?
- notifyOnScriptW("/static/template_topics_topic", () => {
+ notifyOnScriptW("template_topics_topic", () => {
+ if(!Template_topics_topic) throw("template function not found");
toLoad--;
if(toLoad===0) initPhrases();
});
- notifyOnScriptW("/static/template_paginator", () => {
+ notifyOnScriptW("template_paginator", () => {
+ if(!Template_paginator) throw("template function not found");
toLoad--;
if(toLoad===0) initPhrases();
});
diff --git a/routes.go b/routes.go
index 629fd906..0f74669f 100644
--- a/routes.go
+++ b/routes.go
@@ -63,6 +63,7 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
if common.EnableWebsockets && count > 0 {
_ = common.WsHub.PushMessage(user.ID, `{"event":"dismiss-alert","asid":`+strconv.Itoa(asid)+`}`)
}
+ w.Write(successJSONBytes)
// TODO: Split this into it's own function
case "alerts": // A feed of events tailored for a specific user
if !user.Loggedin {
diff --git a/routes/account.go b/routes/account.go
index a36a3732..78d0c392 100644
--- a/routes/account.go
+++ b/routes/account.go
@@ -778,8 +778,7 @@ func AccountPasswordReset(w http.ResponseWriter, r *http.Request, user common.Us
header.AddNotice("password_reset_email_sent")
}
header.Title = phrases.GetTitlePhrase("password_reset")
- pi := common.Page{header, tList, nil}
- return renderTemplate("password_reset", w, r, header, pi)
+ return renderTemplate("password_reset", w, r, header, common.Page{header, tList, nil})
}
// TODO: Ratelimit this
diff --git a/templates/panel_group_edit.html b/templates/panel_group_edit.html
index 2a25fc7d..205f50b9 100644
--- a/templates/panel_group_edit.html
+++ b/templates/panel_group_edit.html
@@ -1,16 +1,6 @@
{{template "header.html" . }}
-
+{{template "panel_group_menu.html" . }}
{{.Name}}{{lang "panel_group_head_suffix"}}
diff --git a/templates/panel_group_edit_perms.html b/templates/panel_group_edit_perms.html
index 93390594..c479d0e3 100644
--- a/templates/panel_group_edit_perms.html
+++ b/templates/panel_group_edit_perms.html
@@ -1,16 +1,6 @@
{{template "header.html" . }}
-
+{{template "panel_group_menu.html" . }}
{{.Name}}{{lang "panel_group_head_suffix"}}
diff --git a/templates/panel_group_menu.html b/templates/panel_group_menu.html
new file mode 100644
index 00000000..6794ebd4
--- /dev/null
+++ b/templates/panel_group_menu.html
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/templates/panel_menu.html b/templates/panel_menu.html
index d0bc677d..dd39062a 100644
--- a/templates/panel_menu.html
+++ b/templates/panel_menu.html
@@ -1 +1 @@
-
+
diff --git a/themes/nox/overrides/panel_group_menu.html b/themes/nox/overrides/panel_group_menu.html
new file mode 100644
index 00000000..98be77e0
--- /dev/null
+++ b/themes/nox/overrides/panel_group_menu.html
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/themes/nox/overrides/panel_inner_menu.html b/themes/nox/overrides/panel_inner_menu.html
new file mode 100644
index 00000000..bdc5d943
--- /dev/null
+++ b/themes/nox/overrides/panel_inner_menu.html
@@ -0,0 +1,91 @@
+
+
+
+
+
+
diff --git a/themes/nox/overrides/panel_menu.html b/themes/nox/overrides/panel_menu.html
new file mode 100644
index 00000000..09fb2b3d
--- /dev/null
+++ b/themes/nox/overrides/panel_menu.html
@@ -0,0 +1,5 @@
+
diff --git a/themes/nox/public/panel.css b/themes/nox/public/panel.css
index 176d1235..3d143b4c 100644
--- a/themes/nox/public/panel.css
+++ b/themes/nox/public/panel.css
@@ -24,6 +24,9 @@
.menu_stats {
margin-left: 4px;
}
+.back_to_site {
+ font-size: 18px;
+}
.colstack_right {
background-color: #333333;