The number of unread alerts now appears in the page title.
Added the Back to Site link to Nox's Control Panel. Added the panel_group_menu template and used it to de-dupe the group menu HTML. Fixed a potential race condition with dismiss alert. Fixed a strange bug where new alerts wouldn't appear. Fixed a race condition where client templates sometimes wouldn't load. Dramatically cut down on the number of DOM rebuilds for the alert list. Added some missing error handling for ajax page block loads. Fixed a bug where the dimiss alert endpoint wasn't sending a success payload. Made the register_might_be_machine phrase more descriptive. Added the panel_menu_aria phrase.
This commit is contained in:
parent
414d9c4817
commit
cb58c1c83f
|
@ -224,7 +224,7 @@ func (list SFileList) JSTmplInit() error {
|
||||||
|
|
||||||
for name, _ := range Themes {
|
for name, _ := range Themes {
|
||||||
if strings.HasSuffix(shortName, "_"+name) {
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@
|
||||||
"id_must_be_integer": "The ID must be an integer.",
|
"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.",
|
"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_username":"You didn't put in a username.",
|
||||||
"register_need_email":"You didn't put in an email.",
|
"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",
|
"register_first_word_numeric":"The first word of your name must not be purely numeric",
|
||||||
|
@ -709,6 +709,7 @@
|
||||||
"option_no":"No",
|
"option_no":"No",
|
||||||
|
|
||||||
"panel_menu_head":"Control Panel",
|
"panel_menu_head":"Control Panel",
|
||||||
|
"panel_menu_aria":"The control panel menu",
|
||||||
"panel_menu_users":"Users",
|
"panel_menu_users":"Users",
|
||||||
"panel_menu_groups":"Groups",
|
"panel_menu_groups":"Groups",
|
||||||
"panel_menu_forums":"Forums",
|
"panel_menu_forums":"Forums",
|
||||||
|
|
|
@ -7,6 +7,7 @@ var moreTopicCount = 0;
|
||||||
var conn = false;
|
var conn = false;
|
||||||
var selectedTopics = [];
|
var selectedTopics = [];
|
||||||
var attachItemCallback = function(){}
|
var attachItemCallback = function(){}
|
||||||
|
var baseTitle = document.title;
|
||||||
|
|
||||||
// Topic move
|
// Topic move
|
||||||
var forumToMoveTo = 0;
|
var forumToMoveTo = 0;
|
||||||
|
@ -17,9 +18,7 @@ function ajaxError(xhr,status,errstr) {
|
||||||
console.log("xhr", xhr);
|
console.log("xhr", xhr);
|
||||||
console.log("status", status);
|
console.log("status", status);
|
||||||
console.log("errstr", errstr);
|
console.log("errstr", errstr);
|
||||||
if(status=="parsererror") {
|
if(status=="parsererror") console.log("The server didn't respond with a valid JSON response");
|
||||||
console.log("The server didn't respond with a valid JSON response");
|
|
||||||
}
|
|
||||||
console.trace();
|
console.trace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,19 +29,28 @@ function postLink(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindToAlerts() {
|
function bindToAlerts() {
|
||||||
|
console.log("bindToAlerts");
|
||||||
$(".alertItem.withAvatar a").unbind("click");
|
$(".alertItem.withAvatar a").unbind("click");
|
||||||
$(".alertItem.withAvatar a").click(function(event) {
|
$(".alertItem.withAvatar a").click(function(event) {
|
||||||
event.stopPropagation();
|
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) {
|
function addAlert(msg, notice = false) {
|
||||||
var mmsg = msg.msg;
|
var mmsg = msg.msg;
|
||||||
if("sub" in msg) {
|
if("sub" in msg) {
|
||||||
for(var i = 0; i < msg.sub.length; i++) {
|
for(var i = 0; i < msg.sub.length; i++) mmsg = mmsg.replace("\{"+i+"\}", msg.sub[i]);
|
||||||
mmsg = mmsg.replace("\{"+i+"\}", msg.sub[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let aItem = Template_alert({
|
let aItem = Template_alert({
|
||||||
|
@ -51,7 +59,10 @@ function addAlert(msg, notice = false) {
|
||||||
Avatar: msg.avatar || "",
|
Avatar: msg.avatar || "",
|
||||||
Message: mmsg
|
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);
|
alertList.push(msg.asid);
|
||||||
|
|
||||||
if(notice) {
|
if(notice) {
|
||||||
|
@ -74,21 +85,31 @@ function updateAlertList(menuAlerts) {
|
||||||
let alertCounterNode = menuAlerts.getElementsByClassName("alert_counter")[0];
|
let alertCounterNode = menuAlerts.getElementsByClassName("alert_counter")[0];
|
||||||
alertCounterNode.textContent = "0";
|
alertCounterNode.textContent = "0";
|
||||||
|
|
||||||
let outList = "";
|
alertListNode.innerHTML = "";
|
||||||
|
let any = false;
|
||||||
|
/*let outList = "";
|
||||||
let j = 0;
|
let j = 0;
|
||||||
for(var i = 0; i < alertList.length && j < 8; i++) {
|
for(var i = 0; i < alertList.length && j < 8; i++) {
|
||||||
outList += alertMapping[alertList[i]];
|
outList += alertMapping[alertList[i]];
|
||||||
j++;
|
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(!any) alertListNode.innerHTML = "<div class='alertItem'>"+phraseBox["alerts"]["alerts.no_alerts"]+"</div>";
|
||||||
if(outList == "") outList = "<div class='alertItem'>"+phraseBox["alerts"]["alerts.no_alerts"]+"</div>";
|
|
||||||
alertListNode.innerHTML = outList;
|
|
||||||
|
|
||||||
if(alertCount != 0) {
|
if(alertCount != 0) {
|
||||||
alertCounterNode.textContent = alertCount;
|
alertCounterNode.textContent = alertCount;
|
||||||
menuAlerts.classList.add("has_alerts");
|
menuAlerts.classList.add("has_alerts");
|
||||||
|
let nTitle = "("+alertCount+") "+baseTitle;
|
||||||
|
if(document.title!=nTitle) document.title = nTitle;
|
||||||
} else {
|
} else {
|
||||||
menuAlerts.classList.remove("has_alerts");
|
menuAlerts.classList.remove("has_alerts");
|
||||||
|
if(document.title!=baseTitle) document.title = baseTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
bindToAlerts();
|
bindToAlerts();
|
||||||
|
@ -155,14 +176,20 @@ function SplitN(data,ch,n) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function wsAlertEvent(data) {
|
function wsAlertEvent(data) {
|
||||||
|
console.log("wsAlertEvent:",data)
|
||||||
addAlert(data, true);
|
addAlert(data, true);
|
||||||
|
alertCount++;
|
||||||
|
|
||||||
var alist = "";
|
let aTmp = alertList;
|
||||||
for (var i = 0; i < alertList.length; i++) alist += alertMapping[alertList[i]];
|
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
|
// TODO: Add support for other alert feeds like PM Alerts
|
||||||
var generalAlerts = document.getElementById("general_alerts");
|
var generalAlerts = document.getElementById("general_alerts");
|
||||||
// TODO: Make sure we update alertCount here
|
// TODO: Make sure we update alertCount here
|
||||||
updateAlertList(generalAlerts, alist);
|
updateAlertList(generalAlerts/*, alist*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
function runWebSockets() {
|
function runWebSockets() {
|
||||||
|
@ -179,9 +206,7 @@ function runWebSockets() {
|
||||||
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');
|
||||||
// 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
|
// 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) {
|
if(me.User.ID > 0) Notification.requestPermission();
|
||||||
Notification.requestPermission();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.onclose = () => {
|
conn.onclose = () => {
|
||||||
|
@ -291,8 +316,13 @@ function runWebSockets() {
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
addInitHook("pre_init", () => {
|
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 :(
|
// 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", () => {
|
addInitHook("after_phrases", () => {
|
||||||
// TODO: The load part of loadAlerts could be done asynchronously while the update of the DOM could be deferred
|
// TODO: The load part of loadAlerts could be done asynchronously while the update of the DOM could be deferred
|
||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
|
@ -438,10 +468,13 @@ function mainInit(){
|
||||||
|
|
||||||
// TODO: Try to de-duplicate some of these fetch calls
|
// TODO: Try to de-duplicate some of these fetch calls
|
||||||
fetch(url+q+"&js=1", {credentials: "same-origin"})
|
fetch(url+q+"&js=1", {credentials: "same-origin"})
|
||||||
.then((resp) => resp.json())
|
.then((resp) => {
|
||||||
.then((data) => {
|
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");
|
if(!"Topics" in data) throw("no Topics in data");
|
||||||
let topics = data["Topics"];
|
let topics = data["Topics"];
|
||||||
|
console.log("ajax navigated to different page");
|
||||||
|
|
||||||
// TODO: Fix the data race where the function hasn't been loaded yet
|
// TODO: Fix the data race where the function hasn't been loaded yet
|
||||||
let out = "";
|
let out = "";
|
||||||
|
@ -470,10 +503,14 @@ function mainInit(){
|
||||||
let url = "//"+window.location.host+"/topics/?fids="+fid;
|
let url = "//"+window.location.host+"/topics/?fids="+fid;
|
||||||
|
|
||||||
fetch(url+"&js=1", {credentials: "same-origin"})
|
fetch(url+"&js=1", {credentials: "same-origin"})
|
||||||
.then((resp) => resp.json())
|
.then((resp) => {
|
||||||
.then((data) => {
|
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");
|
if(!"Topics" in data) throw("no Topics in data");
|
||||||
let topics = data["Topics"];
|
let topics = data["Topics"];
|
||||||
|
console.log("ajax navigated to "+that.innerText);
|
||||||
|
|
||||||
// TODO: Fix the data race where the function hasn't been loaded yet
|
// TODO: Fix the data race where the function hasn't been loaded yet
|
||||||
let out = "";
|
let out = "";
|
||||||
|
@ -481,6 +518,9 @@ function mainInit(){
|
||||||
$(".topic_list").html(out);
|
$(".topic_list").html(out);
|
||||||
//$(".topic_list").addClass("single_forum");
|
//$(".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};
|
let obj = {Title: document.title, Url: url};
|
||||||
history.pushState(obj, obj.Title, obj.Url);
|
history.pushState(obj, obj.Title, obj.Url);
|
||||||
rebuildPaginator(data.LastPage)
|
rebuildPaginator(data.LastPage)
|
||||||
|
@ -515,18 +555,23 @@ function mainInit(){
|
||||||
|
|
||||||
// TODO: Try to de-duplicate some of these fetch calls
|
// TODO: Try to de-duplicate some of these fetch calls
|
||||||
fetch(url+q+"&js=1", {credentials: "same-origin"})
|
fetch(url+q+"&js=1", {credentials: "same-origin"})
|
||||||
.then((resp) => resp.json())
|
.then((resp) => {
|
||||||
.then((data) => {
|
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");
|
if(!"Topics" in data) throw("no Topics in data");
|
||||||
let topics = data["Topics"];
|
let topics = data["Topics"];
|
||||||
|
console.log("ajax navigated to search page");
|
||||||
|
|
||||||
// TODO: Fix the data race where the function hasn't been loaded yet
|
// TODO: Fix the data race where the function hasn't been loaded yet
|
||||||
let out = "";
|
let out = "";
|
||||||
for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]);
|
for(let i = 0; i < topics.length;i++) out += Template_topics_topic(topics[i]);
|
||||||
$(".topic_list").html(out);
|
$(".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"]);
|
$(".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};
|
let obj = {Title: document.title, Url: url+q};
|
||||||
history.pushState(obj, obj.Title, obj.Url);
|
history.pushState(obj, obj.Title, obj.Url);
|
||||||
rebuildPaginator(data.LastPage);
|
rebuildPaginator(data.LastPage);
|
||||||
|
|
|
@ -81,23 +81,30 @@ function asyncGetScript(source) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifyOnScript(source) {
|
function notifyOnScript(source) {
|
||||||
|
source = "/static/"+source;
|
||||||
return new Promise((resolve, reject) => {
|
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];
|
let script = document.querySelectorAll('[src^="'+source+'"]')[0];
|
||||||
|
console.log("script:",script);
|
||||||
if(script===undefined) {
|
if(script===undefined) {
|
||||||
reject("no script found");
|
reject("no script found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!script.readyState) {
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onloadHandler = (e, isAbort) => {
|
const onloadHandler = (e) => {
|
||||||
if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
|
script.onload = null;
|
||||||
script.onload = null;
|
script.onreadystatechange = null;
|
||||||
script.onreadystatechange = null;
|
resolve();
|
||||||
isAbort ? reject(e) : resolve();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
script.onerror = (e) => {
|
script.onerror = (e) => {
|
||||||
|
@ -105,7 +112,6 @@ function notifyOnScript(source) {
|
||||||
};
|
};
|
||||||
script.onload = onloadHandler;
|
script.onload = onloadHandler;
|
||||||
script.onreadystatechange = 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("Unable to get script name '"+name+"'");
|
||||||
console.log("e: ", e);
|
console.log("e: ", e);
|
||||||
console.trace();
|
console.trace();
|
||||||
complete();
|
complete(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,6 +174,7 @@ function RelativeTime(date) {
|
||||||
|
|
||||||
function initPhrases() {
|
function initPhrases() {
|
||||||
console.log("in initPhrases")
|
console.log("in initPhrases")
|
||||||
|
console.log("tmlInits:",tmplInits)
|
||||||
fetchPhrases("status,topic_list,alerts,paginator,analytics")
|
fetchPhrases("status,topic_list,alerts,paginator,analytics")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,11 +213,13 @@ function fetchPhrases(plist) {
|
||||||
runInitHook("pre_iife");
|
runInitHook("pre_iife");
|
||||||
let toLoad = 2;
|
let toLoad = 2;
|
||||||
// TODO: Shunt this into loggedIn if there aren't any search and filter widgets?
|
// 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--;
|
toLoad--;
|
||||||
if(toLoad===0) initPhrases();
|
if(toLoad===0) initPhrases();
|
||||||
});
|
});
|
||||||
notifyOnScriptW("/static/template_paginator", () => {
|
notifyOnScriptW("template_paginator", () => {
|
||||||
|
if(!Template_paginator) throw("template function not found");
|
||||||
toLoad--;
|
toLoad--;
|
||||||
if(toLoad===0) initPhrases();
|
if(toLoad===0) initPhrases();
|
||||||
});
|
});
|
||||||
|
|
|
@ -63,6 +63,7 @@ func routeAPI(w http.ResponseWriter, r *http.Request, user common.User) common.R
|
||||||
if common.EnableWebsockets && count > 0 {
|
if common.EnableWebsockets && count > 0 {
|
||||||
_ = common.WsHub.PushMessage(user.ID, `{"event":"dismiss-alert","asid":`+strconv.Itoa(asid)+`}`)
|
_ = common.WsHub.PushMessage(user.ID, `{"event":"dismiss-alert","asid":`+strconv.Itoa(asid)+`}`)
|
||||||
}
|
}
|
||||||
|
w.Write(successJSONBytes)
|
||||||
// TODO: Split this into it's own function
|
// TODO: Split this into it's own function
|
||||||
case "alerts": // A feed of events tailored for a specific user
|
case "alerts": // A feed of events tailored for a specific user
|
||||||
if !user.Loggedin {
|
if !user.Loggedin {
|
||||||
|
|
|
@ -778,8 +778,7 @@ func AccountPasswordReset(w http.ResponseWriter, r *http.Request, user common.Us
|
||||||
header.AddNotice("password_reset_email_sent")
|
header.AddNotice("password_reset_email_sent")
|
||||||
}
|
}
|
||||||
header.Title = phrases.GetTitlePhrase("password_reset")
|
header.Title = phrases.GetTitlePhrase("password_reset")
|
||||||
pi := common.Page{header, tList, nil}
|
return renderTemplate("password_reset", w, r, header, common.Page{header, tList, nil})
|
||||||
return renderTemplate("password_reset", w, r, header, pi)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Ratelimit this
|
// TODO: Ratelimit this
|
||||||
|
|
|
@ -1,16 +1,6 @@
|
||||||
{{template "header.html" . }}
|
{{template "header.html" . }}
|
||||||
<div class="colstack panel_stack">
|
<div class="colstack panel_stack">
|
||||||
<nav class="colstack_left" aria-label="The control panel menu">
|
{{template "panel_group_menu.html" . }}
|
||||||
<div class="colstack_item colstack_head">
|
|
||||||
<div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_head"}}</a></div>
|
|
||||||
</div>
|
|
||||||
<div class="colstack_item rowmenu">
|
|
||||||
<div class="rowitem passive"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_general"}}</a></div>
|
|
||||||
<div class="rowitem passive"><a>{{lang "panel_group_menu_promotions"}}</a></div>
|
|
||||||
<div class="rowitem passive"><a href="/panel/groups/edit/perms/{{.ID}}">{{lang "panel_group_menu_permissions"}}</a></div>
|
|
||||||
</div>
|
|
||||||
{{template "panel_inner_menu.html" . }}
|
|
||||||
</nav>
|
|
||||||
<main class="colstack_right">
|
<main class="colstack_right">
|
||||||
<div class="colstack_item colstack_head">
|
<div class="colstack_item colstack_head">
|
||||||
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div>
|
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div>
|
||||||
|
|
|
@ -1,16 +1,6 @@
|
||||||
{{template "header.html" . }}
|
{{template "header.html" . }}
|
||||||
<div class="colstack panel_stack">
|
<div class="colstack panel_stack">
|
||||||
<nav class="colstack_left" aria-label="The control panel menu">
|
{{template "panel_group_menu.html" . }}
|
||||||
<div class="colstack_item colstack_head">
|
|
||||||
<div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_head"}}</a></div>
|
|
||||||
</div>
|
|
||||||
<div class="colstack_item rowmenu">
|
|
||||||
<div class="rowitem passive"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_general"}}</a></div>
|
|
||||||
<div class="rowitem passive"><a>{{lang "panel_group_menu_promotions"}}</a></div>
|
|
||||||
<div class="rowitem passive"><a href="/panel/groups/edit/perms/{{.ID}}">{{lang "panel_group_menu_permissions"}}</a></div>
|
|
||||||
</div>
|
|
||||||
{{template "panel_inner_menu.html" . }}
|
|
||||||
</nav>
|
|
||||||
<main class="colstack_right">
|
<main class="colstack_right">
|
||||||
<div class="colstack_item colstack_head">
|
<div class="colstack_item colstack_head">
|
||||||
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div>
|
<div class="rowitem"><h1>{{.Name}}{{lang "panel_group_head_suffix"}}</h1></div>
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<nav class="colstack_left" aria-label="{{lang "panel_menu_aria"}}">
|
||||||
|
<div class="colstack_item colstack_head">
|
||||||
|
<div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_head"}}</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="colstack_item rowmenu">
|
||||||
|
<div class="rowitem passive"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_general"}}</a></div>
|
||||||
|
<div class="rowitem passive"><a>{{lang "panel_group_menu_promotions"}}</a></div>
|
||||||
|
<div class="rowitem passive"><a href="/panel/groups/edit/perms/{{.ID}}">{{lang "panel_group_menu_permissions"}}</a></div>
|
||||||
|
</div>
|
||||||
|
{{template "panel_inner_menu.html" . }}
|
||||||
|
</nav>
|
|
@ -1 +1 @@
|
||||||
<nav class="colstack_left" aria-label="The control panel menu">{{template "panel_inner_menu.html" . }}</nav>
|
<nav class="colstack_left" aria-label="{{lang "panel_menu_aria"}}">{{template "panel_inner_menu.html" . }}</nav>
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<nav class="colstack_left" aria-label="{{lang "panel_menu_aria"}}">
|
||||||
|
<div class="colstack_item colstack_head">
|
||||||
|
<div class="rowitem back_to_site"><a href="/panel/">Back to site</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="colstack_item colstack_head">
|
||||||
|
<div class="rowitem"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_head"}}</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="colstack_item rowmenu">
|
||||||
|
<div class="rowitem passive"><a href="/panel/groups/edit/{{.ID}}">{{lang "panel_group_menu_general"}}</a></div>
|
||||||
|
<div class="rowitem passive"><a>{{lang "panel_group_menu_promotions"}}</a></div>
|
||||||
|
<div class="rowitem passive"><a href="/panel/groups/edit/perms/{{.ID}}">{{lang "panel_group_menu_permissions"}}</a></div>
|
||||||
|
</div>
|
||||||
|
{{template "panel_inner_menu.html" . }}
|
||||||
|
</nav>
|
|
@ -0,0 +1,91 @@
|
||||||
|
<div class="colstack_item colstack_head">
|
||||||
|
<div class="rowitem"><a href="/panel/">{{lang "panel_menu_head"}}</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="colstack_item rowmenu">
|
||||||
|
<div class="rowitem passive">
|
||||||
|
<a href="/panel/users/">{{lang "panel_menu_users"}}</a> <a class="menu_stats" href="#">({{.Stats.Users}})</a>
|
||||||
|
</div>
|
||||||
|
<div class="rowitem passive">
|
||||||
|
<a href="/panel/groups/">{{lang "panel_menu_groups"}}</a> <a class="menu_stats" href="#">({{.Stats.Groups}})</a>
|
||||||
|
</div>
|
||||||
|
{{if .CurrentUser.Perms.ManageForums}}<div class="rowitem passive">
|
||||||
|
<a href="/panel/forums/">{{lang "panel_menu_forums"}}</a> <a class="menu_stats" href="#">({{.Stats.Forums}})</a>
|
||||||
|
</div>{{end}}
|
||||||
|
<div class="rowitem passive">
|
||||||
|
<a href="/panel/pages/">{{lang "panel_menu_pages"}}</a> <a class="menu_stats" href="#">({{.Stats.Pages}})</a>
|
||||||
|
</div>
|
||||||
|
{{if .CurrentUser.Perms.EditSettings}}<div class="rowitem passive">
|
||||||
|
<a href="/panel/settings/">{{lang "panel_menu_settings"}}</a> <a class="menu_stats" href="#">({{.Stats.Settings}})</a>
|
||||||
|
</div>
|
||||||
|
<div class="rowitem passive">
|
||||||
|
<a href="/panel/settings/word-filters/">{{lang "panel_menu_word_filters"}}</a> <a class="menu_stats" href="#">({{.Stats.WordFilters}})</a>
|
||||||
|
</div>{{end}}
|
||||||
|
{{if .CurrentUser.Perms.ManageThemes}}
|
||||||
|
<div class="rowitem passive">
|
||||||
|
<a href="/panel/themes/">{{lang "panel_menu_themes"}}</a> <a class="menu_stats" href="#">({{.Stats.Themes}})</a>
|
||||||
|
</div>
|
||||||
|
{{if eq .Zone "themes"}}
|
||||||
|
<div class="rowitem passive submenu"><a href="/panel/themes/menus/">{{lang "panel_menu_menus"}}</a></div>
|
||||||
|
<div class="rowitem passive submenu"><a href="/panel/themes/widgets/">{{lang "panel_menu_widgets"}}</a></div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="colstack_item colstack_head">
|
||||||
|
<div class="rowitem"><a href="#">{{lang "panel_menu_events"}}</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="colstack_item rowmenu">
|
||||||
|
<div class="rowitem passive">
|
||||||
|
<a href="/panel/analytics/views/">{{lang "panel_menu_statistics"}}</a>
|
||||||
|
</div>
|
||||||
|
{{if eq .Zone "analytics"}}
|
||||||
|
<div class="rowitem passive submenu">
|
||||||
|
<a href="/panel/analytics/posts/">{{lang "panel_menu_statistics_posts"}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="rowitem passive submenu">
|
||||||
|
<a href="/panel/analytics/topics/">{{lang "panel_menu_statistics_topics"}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="rowitem passive submenu">
|
||||||
|
<a href="/panel/analytics/forums/">{{lang "panel_menu_statistics_forums"}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="rowitem passive submenu">
|
||||||
|
<a href="/panel/analytics/routes/">{{lang "panel_menu_statistics_routes"}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="rowitem passive submenu">
|
||||||
|
<a href="/panel/analytics/agents/">{{lang "panel_menu_statistics_agents"}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="rowitem passive submenu">
|
||||||
|
<a href="/panel/analytics/systems/">{{lang "panel_menu_statistics_systems"}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="rowitem passive submenu">
|
||||||
|
<a href="/panel/analytics/langs/">{{lang "panel_menu_statistics_languages"}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="rowitem passive submenu">
|
||||||
|
<a href="/panel/analytics/referrers/">{{lang "panel_menu_statistics_referrers"}}</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="rowitem passive">
|
||||||
|
<a href="/forum/{{.ReportForumID}}">{{lang "panel_menu_reports"}}</a> <a class="menu_stats" href="#">({{.Stats.Reports}})</a>
|
||||||
|
</div>
|
||||||
|
<div class="rowitem passive">
|
||||||
|
<a href="/panel/logs/mod/">{{lang "panel_menu_logs"}}</a>
|
||||||
|
</div>
|
||||||
|
{{if eq .Zone "logs"}}
|
||||||
|
<div class="rowitem passive submenu"><a href="/panel/logs/regs/">{{lang "panel_menu_logs_registrations"}}</a></div>
|
||||||
|
<div class="rowitem passive submenu"><a href="/panel/logs/mod/">{{lang "panel_menu_logs_moderators"}}</a></div>
|
||||||
|
{{if .CurrentUser.Perms.ViewAdminLogs}}<div class="rowitem passive submenu"><a>{{lang "panel_menu_logs_administrators"}}</a></div>{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="colstack_item colstack_head">
|
||||||
|
<div class="rowitem"><a href="#">{{lang "panel_menu_system"}}</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="colstack_item rowmenu">
|
||||||
|
{{if .CurrentUser.Perms.ManagePlugins}}<div class="rowitem passive">
|
||||||
|
<a href="/panel/plugins/">{{lang "panel_menu_plugins"}}</a>
|
||||||
|
</div>{{end}}
|
||||||
|
{{if .CurrentUser.IsSuperAdmin}}<div class="rowitem passive">
|
||||||
|
<a href="/panel/backups/">{{lang "panel_menu_backups"}}</a>
|
||||||
|
</div>{{end}}
|
||||||
|
{{if .CurrentUser.IsAdmin}}<div class="rowitem passive">
|
||||||
|
<a href="/panel/debug/">{{lang "panel_menu_debug"}}</a>
|
||||||
|
</div>{{end}}
|
||||||
|
</div>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<nav class="colstack_left" aria-label="{{lang "panel_menu_aria"}}">
|
||||||
|
<div class="colstack_item colstack_head">
|
||||||
|
<div class="rowitem back_to_site"><a href="/panel/">Back to site</a></div>
|
||||||
|
</div>
|
||||||
|
{{template "panel_inner_menu.html" . }}</nav>
|
|
@ -24,6 +24,9 @@
|
||||||
.menu_stats {
|
.menu_stats {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
.back_to_site {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.colstack_right {
|
.colstack_right {
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
|
|
Loading…
Reference in New Issue