Did a bit of refactoring on the template system.
Refactored the ReplyStore. Added the Create method to the ProfileReplyStore. Fixed a potential issue with the topics and replies generated by the installer. Refactored the accumulator. Refactored global.js Added more tests.
This commit is contained in:
parent
aab949cb2d
commit
569b424ac8
|
@ -52,7 +52,6 @@ type Stmts struct {
|
|||
addActivity *sql.Stmt
|
||||
notifyOne *sql.Stmt
|
||||
addEmail *sql.Stmt
|
||||
createProfileReply *sql.Stmt
|
||||
addSubscription *sql.Stmt
|
||||
addForumPermsToForum *sql.Stmt
|
||||
addPlugin *sql.Stmt
|
||||
|
@ -438,13 +437,6 @@ func _gen_mssql() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
log.Print("Preparing createProfileReply statement.")
|
||||
stmts.createProfileReply, err = db.Prepare("INSERT INTO [users_replies] ([uid],[content],[parsed_content],[createdAt],[createdBy],[ipaddress]) VALUES (?,?,?,GETUTCDATE(),?,?)")
|
||||
if err != nil {
|
||||
log.Print("Bad Query: ","INSERT INTO [users_replies] ([uid],[content],[parsed_content],[createdAt],[createdBy],[ipaddress]) VALUES (?,?,?,GETUTCDATE(),?,?)")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Print("Preparing addSubscription statement.")
|
||||
stmts.addSubscription, err = db.Prepare("INSERT INTO [activity_subscriptions] ([user],[targetID],[targetType],[level]) VALUES (?,?,?,2)")
|
||||
if err != nil {
|
||||
|
|
|
@ -54,7 +54,6 @@ type Stmts struct {
|
|||
addActivity *sql.Stmt
|
||||
notifyOne *sql.Stmt
|
||||
addEmail *sql.Stmt
|
||||
createProfileReply *sql.Stmt
|
||||
addSubscription *sql.Stmt
|
||||
addForumPermsToForum *sql.Stmt
|
||||
addPlugin *sql.Stmt
|
||||
|
@ -396,12 +395,6 @@ func _gen_mysql() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
log.Print("Preparing createProfileReply statement.")
|
||||
stmts.createProfileReply, err = db.Prepare("INSERT INTO `users_replies`(`uid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`ipaddress`) VALUES (?,?,?,UTC_TIMESTAMP(),?,?)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Print("Preparing addSubscription statement.")
|
||||
stmts.addSubscription, err = db.Prepare("INSERT INTO `activity_subscriptions`(`user`,`targetID`,`targetType`,`level`) VALUES (?,?,?,2)")
|
||||
if err != nil {
|
||||
|
|
|
@ -559,17 +559,13 @@ func routeProfileReplyCreate(w http.ResponseWriter, r *http.Request, user User)
|
|||
}
|
||||
|
||||
content := html.EscapeString(preparseMessage(r.PostFormValue("reply-content")))
|
||||
_, err = stmts.createProfileReply.Exec(uid, content, parseMessage(content, 0, ""), user.ID, ipaddress)
|
||||
_, err = prstore.Create(uid, content, user.ID, ipaddress)
|
||||
if err != nil {
|
||||
return InternalError(err, w, r)
|
||||
}
|
||||
|
||||
var userName string
|
||||
err = stmts.getUserName.QueryRow(uid).Scan(&userName)
|
||||
if err == ErrNoRows {
|
||||
if !users.Exists(uid) {
|
||||
return LocalError("The profile you're trying to post on doesn't exist.", w, r, user)
|
||||
} else if err != nil {
|
||||
return InternalError(err, w, r)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/user/"+strconv.Itoa(uid), http.StatusSeeOther)
|
||||
|
|
56
misc_test.go
56
misc_test.go
|
@ -550,6 +550,8 @@ func topicStoreTest(t *testing.T) {
|
|||
t.Error("The number of topics should be bigger than zero")
|
||||
t.Error("count", count)
|
||||
}
|
||||
|
||||
// TODO: Test topic creation and retrieving that created topic plus reload and inspecting the cache
|
||||
}
|
||||
|
||||
func TestForumStore(t *testing.T) {
|
||||
|
@ -737,16 +739,28 @@ func TestReplyStore(t *testing.T) {
|
|||
|
||||
reply, err := rstore.Get(1)
|
||||
expectNilErr(t, err)
|
||||
expect(t, reply.ID == 1, fmt.Sprintf("RID #1 has the wrong ID. It should be 1 not %d", reply.ID))
|
||||
expect(t, reply.ParentID == 1, fmt.Sprintf("The parent topic of RID #1 should be 1 not %d", reply.ParentID))
|
||||
expect(t, reply.CreatedBy == 1, fmt.Sprintf("The creator of RID #1 should be 1 not %d", reply.CreatedBy))
|
||||
expect(t, reply.Content == "A reply!", fmt.Sprintf("The contents of RID #1 should be 'A reply!' not %s", reply.Content))
|
||||
expect(t, reply.IPAddress == "::1", fmt.Sprintf("The IPAddress of RID#1 should be '::1' not %s", reply.IPAddress))
|
||||
|
||||
if reply.ID != 1 {
|
||||
t.Errorf("RID #1 has the wrong ID. It should be 1 not %d", reply.ID)
|
||||
}
|
||||
if reply.ParentID != 1 {
|
||||
t.Errorf("The parent topic of RID #1 should be 1 not %d", reply.ParentID)
|
||||
}
|
||||
if reply.CreatedBy != 1 {
|
||||
t.Errorf("The creator of RID #1 should be 1 not %d", reply.CreatedBy)
|
||||
}
|
||||
_, err = rstore.Get(2)
|
||||
recordMustNotExist(t, err, "RID #2 shouldn't exist")
|
||||
|
||||
// TODO: Test Create and Get
|
||||
//Create(tid int, content string, ipaddress string, fid int, uid int) (id int, err error)
|
||||
rid, err := rstore.Create(1, "Fofofo", "::1", 2, 1)
|
||||
expectNilErr(t, err)
|
||||
expect(t, rid == 2, fmt.Sprintf("The next reply ID should be 2 not %d", rid))
|
||||
|
||||
reply, err = rstore.Get(2)
|
||||
expectNilErr(t, err)
|
||||
expect(t, reply.ID == 2, fmt.Sprintf("RID #2 has the wrong ID. It should be 2 not %d", reply.ID))
|
||||
expect(t, reply.ParentID == 1, fmt.Sprintf("The parent topic of RID #2 should be 1 not %d", reply.ParentID))
|
||||
expect(t, reply.CreatedBy == 1, fmt.Sprintf("The creator of RID #2 should be 1 not %d", reply.CreatedBy))
|
||||
expect(t, reply.Content == "Fofofo", fmt.Sprintf("The contents of RID #2 should be 'Fofofo' not %s", reply.Content))
|
||||
expect(t, reply.IPAddress == "::1", fmt.Sprintf("The IPAddress of RID #2 should be '::1' not %s", reply.IPAddress))
|
||||
}
|
||||
|
||||
func TestProfileReplyStore(t *testing.T) {
|
||||
|
@ -758,10 +772,30 @@ func TestProfileReplyStore(t *testing.T) {
|
|||
}
|
||||
|
||||
_, err := prstore.Get(-1)
|
||||
recordMustNotExist(t, err, "RID #-1 shouldn't exist")
|
||||
recordMustNotExist(t, err, "PRID #-1 shouldn't exist")
|
||||
|
||||
_, err = prstore.Get(0)
|
||||
recordMustNotExist(t, err, "RID #0 shouldn't exist")
|
||||
recordMustNotExist(t, err, "PRID #0 shouldn't exist")
|
||||
|
||||
_, err = prstore.Get(1)
|
||||
recordMustNotExist(t, err, "PRID #0 shouldn't exist")
|
||||
|
||||
var profileID = 1
|
||||
prid, err := prstore.Create(profileID, "Haha", 1, "::1")
|
||||
expect(t, err == nil, "Unable to create a profile reply")
|
||||
expect(t, prid == 1, "The first profile reply should have an ID of 1")
|
||||
|
||||
profileReply, err := prstore.Get(1)
|
||||
expect(t, err == nil, "PRID #1 should exist")
|
||||
expect(t, profileReply.ID == 1, fmt.Sprintf("The profile reply should have an ID of 1 not %d", profileReply.ID))
|
||||
expect(t, profileReply.ParentID == 1, fmt.Sprintf("The parent ID of the profile reply should be 1 not %d", profileReply.ParentID))
|
||||
expect(t, profileReply.Content == "Haha", fmt.Sprintf("The profile reply's contents should be 'Haha' not '%s'", profileReply.Content))
|
||||
expect(t, profileReply.CreatedBy == 1, fmt.Sprintf("The profile reply's creator should be 1 not %d", profileReply.CreatedBy))
|
||||
expect(t, profileReply.IPAddress == "::1", fmt.Sprintf("The profile reply's IP Address should be '::1' not '%s'", profileReply.IPAddress))
|
||||
|
||||
//Get(id int) (*Reply, error)
|
||||
//Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error)
|
||||
|
||||
}
|
||||
|
||||
func TestSlugs(t *testing.T) {
|
||||
|
|
|
@ -174,7 +174,7 @@ func TestBBCodeRender(t *testing.T) {
|
|||
msg = "[rand]18446744073709551615[/rand]" // Unsigned 64-bit MAX
|
||||
t.Log("Testing string '" + msg + "'")
|
||||
res = bbcodeFullParse(msg)
|
||||
conv, err = strconv.Atoi(res)
|
||||
_, err = strconv.Atoi(res)
|
||||
if err != nil && res != "<span style='color: red;'>[Invalid Number]</span>[rand]18446744073709551615[/rand]" {
|
||||
t.Error("Bad output:", "'"+res+"'")
|
||||
t.Error("Expected a number between 0 and 18446744073709551615")
|
||||
|
@ -182,7 +182,7 @@ func TestBBCodeRender(t *testing.T) {
|
|||
msg = "[rand]170141183460469231731687303715884105727[/rand]" // Signed 128-bit MAX
|
||||
t.Log("Testing string '" + msg + "'")
|
||||
res = bbcodeFullParse(msg)
|
||||
conv, err = strconv.Atoi(res)
|
||||
_, err = strconv.Atoi(res)
|
||||
if err != nil && res != "<span style='color: red;'>[Invalid Number]</span>[rand]170141183460469231731687303715884105727[/rand]" {
|
||||
t.Error("Bad output:", "'"+res+"'")
|
||||
t.Error("Expected a number between 0 and 170141183460469231731687303715884105727")
|
||||
|
|
|
@ -10,22 +10,22 @@ var prstore ProfileReplyStore
|
|||
|
||||
type ProfileReplyStore interface {
|
||||
Get(id int) (*Reply, error)
|
||||
Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error)
|
||||
}
|
||||
|
||||
// TODO: Refactor this to stop using the global stmt store
|
||||
// TODO: Add more methods to this like Create()
|
||||
type SQLProfileReplyStore struct {
|
||||
get *sql.Stmt
|
||||
get *sql.Stmt
|
||||
create *sql.Stmt
|
||||
}
|
||||
|
||||
func NewSQLProfileReplyStore() (*SQLProfileReplyStore, error) {
|
||||
getUserReplyStmt, err := qgen.Builder.SimpleSelect("users_replies", "uid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress", "rid = ?", "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
acc := qgen.Builder.Accumulator()
|
||||
return &SQLProfileReplyStore{
|
||||
get: getUserReplyStmt,
|
||||
}, nil
|
||||
get: acc.SimpleSelect("users_replies", "uid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress", "rid = ?", "", ""),
|
||||
create: acc.SimpleInsert("users_replies", "uid, content, parsed_content, createdAt, createdBy, ipaddress", "?,?,?,UTC_TIMESTAMP(),?,?"),
|
||||
}, acc.FirstError()
|
||||
}
|
||||
|
||||
func (store *SQLProfileReplyStore) Get(id int) (*Reply, error) {
|
||||
|
@ -33,3 +33,17 @@ func (store *SQLProfileReplyStore) Get(id int) (*Reply, error) {
|
|||
err := store.get.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress)
|
||||
return &reply, err
|
||||
}
|
||||
|
||||
func (store *SQLProfileReplyStore) Create(profileID int, content string, createdBy int, ipaddress string) (id int, err error) {
|
||||
res, err := store.create.Exec(profileID, content, parseMessage(content, 0, ""), createdBy, ipaddress)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
lastID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Should we reload the user?
|
||||
return int(lastID), err
|
||||
}
|
||||
|
|
268
public/global.js
268
public/global.js
|
@ -34,72 +34,72 @@ function bindToAlerts() {
|
|||
}
|
||||
|
||||
// TODO: Add the ability for users to dismiss alerts
|
||||
function loadAlerts(menu_alerts)
|
||||
function loadAlerts(menuAlerts)
|
||||
{
|
||||
var alertListNode = menu_alerts.getElementsByClassName("alertList")[0];
|
||||
var alertCounterNode = menu_alerts.getElementsByClassName("alert_counter")[0];
|
||||
var alertListNode = menuAlerts.getElementsByClassName("alertList")[0];
|
||||
var alertCounterNode = menuAlerts.getElementsByClassName("alert_counter")[0];
|
||||
alertCounterNode.textContent = "0";
|
||||
$.ajax({
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
url:'/api/?action=get&module=alerts',
|
||||
success: function(data) {
|
||||
if("errmsg" in data) {
|
||||
alertListNode.innerHTML = "<div class='alertItem'>"+data.errmsg+"</div>";
|
||||
return;
|
||||
}
|
||||
|
||||
var alist = "";
|
||||
for(var i in data.msgs) {
|
||||
var msg = data.msgs[i];
|
||||
var mmsg = msg.msg;
|
||||
|
||||
if("sub" in msg) {
|
||||
for(var i = 0; i < msg.sub.length; i++) {
|
||||
mmsg = mmsg.replace("\{"+i+"\}", msg.sub[i]);
|
||||
//console.log("Sub #" + i + ":",msg.sub[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if("avatar" in msg) {
|
||||
alist += "<div class='alertItem withAvatar' style='background-image:url(\""+msg.avatar+"\");'><a class='text' data-asid='"+msg.asid+"' href=\""+msg.path+"\">"+mmsg+"</a></div>";
|
||||
alertList.push("<div class='alertItem withAvatar' style='background-image:url(\""+msg.avatar+"\");'><a class='text' data-asid='"+msg.asid+"' href=\""+msg.path+"\">"+mmsg+"</a></div>");
|
||||
} else {
|
||||
alist += "<div class='alertItem'><a href=\""+msg.path+"\" class='text'>"+mmsg+"</a></div>";
|
||||
alertList.push("<div class='alertItem'><a href=\""+msg.path+"\" class='text'>"+mmsg+"</a></div>");
|
||||
}
|
||||
//console.log(msg);
|
||||
//console.log(mmsg);
|
||||
}
|
||||
|
||||
if(alist == "") alist = "<div class='alertItem'>You don't have any alerts</div>";
|
||||
alertListNode.innerHTML = alist;
|
||||
|
||||
if(data.msgCount != 0 && data.msgCount != undefined) {
|
||||
alertCounterNode.textContent = data.msgCount;
|
||||
menu_alerts.classList.add("has_alerts");
|
||||
} else {
|
||||
menu_alerts.classList.remove("has_alerts");
|
||||
}
|
||||
alertCount = data.msgCount;
|
||||
|
||||
bindToAlerts();
|
||||
},
|
||||
error: function(magic,theStatus,error) {
|
||||
var errtxt
|
||||
try {
|
||||
var data = JSON.parse(magic.responseText);
|
||||
if("errmsg" in data) errtxt = data.errmsg;
|
||||
else errtxt = "Unable to get the alerts";
|
||||
} catch(err) {
|
||||
errtxt = "Unable to get the alerts";
|
||||
console.log(magic.responseText);
|
||||
console.log(err);
|
||||
}
|
||||
console.log("error: ",error);
|
||||
alertListNode.innerHTML = "<div class='alertItem'>"+errtxt+"</div>";
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
url:'/api/?action=get&module=alerts',
|
||||
success: function(data) {
|
||||
if("errmsg" in data) {
|
||||
alertListNode.innerHTML = "<div class='alertItem'>"+data.errmsg+"</div>";
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
var alist = "";
|
||||
for(var i in data.msgs) {
|
||||
var msg = data.msgs[i];
|
||||
var mmsg = msg.msg;
|
||||
|
||||
if("sub" in msg) {
|
||||
for(var i = 0; i < msg.sub.length; i++) {
|
||||
mmsg = mmsg.replace("\{"+i+"\}", msg.sub[i]);
|
||||
//console.log("Sub #" + i + ":",msg.sub[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if("avatar" in msg) {
|
||||
alist += "<div class='alertItem withAvatar' style='background-image:url(\""+msg.avatar+"\");'><a class='text' data-asid='"+msg.asid+"' href=\""+msg.path+"\">"+mmsg+"</a></div>";
|
||||
alertList.push("<div class='alertItem withAvatar' style='background-image:url(\""+msg.avatar+"\");'><a class='text' data-asid='"+msg.asid+"' href=\""+msg.path+"\">"+mmsg+"</a></div>");
|
||||
} else {
|
||||
alist += "<div class='alertItem'><a href=\""+msg.path+"\" class='text'>"+mmsg+"</a></div>";
|
||||
alertList.push("<div class='alertItem'><a href=\""+msg.path+"\" class='text'>"+mmsg+"</a></div>");
|
||||
}
|
||||
//console.log(msg);
|
||||
//console.log(mmsg);
|
||||
}
|
||||
|
||||
if(alist == "") alist = "<div class='alertItem'>You don't have any alerts</div>";
|
||||
alertListNode.innerHTML = alist;
|
||||
|
||||
if(data.msgCount != 0 && data.msgCount != undefined) {
|
||||
alertCounterNode.textContent = data.msgCount;
|
||||
menuAlerts.classList.add("has_alerts");
|
||||
} else {
|
||||
menuAlerts.classList.remove("has_alerts");
|
||||
}
|
||||
alertCount = data.msgCount;
|
||||
|
||||
bindToAlerts();
|
||||
},
|
||||
error: function(magic,theStatus,error) {
|
||||
let errtxt
|
||||
try {
|
||||
var data = JSON.parse(magic.responseText);
|
||||
if("errmsg" in data) errtxt = data.errmsg;
|
||||
else errtxt = "Unable to get the alerts";
|
||||
} catch(err) {
|
||||
errtxt = "Unable to get the alerts";
|
||||
console.log(magic.responseText);
|
||||
console.log(err);
|
||||
}
|
||||
console.log("error", error);
|
||||
alertListNode.innerHTML = "<div class='alertItem'>"+errtxt+"</div>";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function SplitN(data,ch,n) {
|
||||
|
@ -121,84 +121,86 @@ function SplitN(data,ch,n) {
|
|||
return out;
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
if(window["WebSocket"]) {
|
||||
if(window.location.protocol == "https:")
|
||||
conn = new WebSocket("wss://" + document.location.host + "/ws/");
|
||||
else conn = new WebSocket("ws://" + document.location.host + "/ws/");
|
||||
function runWebSockets() {
|
||||
if(window.location.protocol == "https:")
|
||||
conn = new WebSocket("wss://" + document.location.host + "/ws/");
|
||||
else conn = new WebSocket("ws://" + document.location.host + "/ws/");
|
||||
|
||||
conn.onopen = function() {
|
||||
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
|
||||
Notification.requestPermission();
|
||||
}
|
||||
conn.onclose = function() {
|
||||
conn = false;
|
||||
console.log("The WebSockets connection was closed");
|
||||
}
|
||||
conn.onmessage = function(event) {
|
||||
//console.log("WS_Message:", event.data);
|
||||
if(event.data[0] == "{") {
|
||||
try {
|
||||
var data = JSON.parse(event.data);
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
if ("msg" in data) {
|
||||
var msg = data.msg
|
||||
if("sub" in data)
|
||||
for(var i = 0; i < data.sub.length; i++)
|
||||
msg = msg.replace("\{"+i+"\}", data.sub[i]);
|
||||
|
||||
if("avatar" in data) alertList.push("<div class='alertItem withAvatar' style='background-image:url(\""+data.avatar+"\");'><a class='text' data-asid='"+data.asid+"' href=\""+data.path+"\">"+msg+"</a></div>");
|
||||
else alertList.push("<div class='alertItem'><a href=\""+data.path+"\" class='text'>"+msg+"</a></div>");
|
||||
if(alertList.length > 8) alertList.shift();
|
||||
//console.log("post alertList",alertList);
|
||||
alertCount++;
|
||||
|
||||
var alist = ""
|
||||
for (var i = 0; i < alertList.length; i++) alist += alertList[i];
|
||||
|
||||
//console.log(alist);
|
||||
// TODO: Add support for other alert feeds like PM Alerts
|
||||
var general_alerts = document.getElementById("general_alerts");
|
||||
var alertListNode = general_alerts.getElementsByClassName("alertList")[0];
|
||||
var alertCounterNode = general_alerts.getElementsByClassName("alert_counter")[0];
|
||||
alertListNode.innerHTML = alist;
|
||||
alertCounterNode.textContent = alertCount;
|
||||
|
||||
// TODO: Add some sort of notification queue to avoid flooding the end-user with notices?
|
||||
// TODO: Use the site name instead of "Something Happened"
|
||||
if(Notification.permission === "granted") {
|
||||
var n = new Notification("Something Happened",{
|
||||
body: msg,
|
||||
icon: data.avatar,
|
||||
});
|
||||
setTimeout(n.close.bind(n), 8000);
|
||||
}
|
||||
|
||||
bindToAlerts();
|
||||
}
|
||||
conn.onopen = function() {
|
||||
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
|
||||
Notification.requestPermission();
|
||||
}
|
||||
conn.onclose = function() {
|
||||
conn = false;
|
||||
console.log("The WebSockets connection was closed");
|
||||
}
|
||||
conn.onmessage = function(event) {
|
||||
//console.log("WS_Message:", event.data);
|
||||
if(event.data[0] == "{") {
|
||||
try {
|
||||
var data = JSON.parse(event.data);
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
var messages = event.data.split('\r');
|
||||
for(var i = 0; i < messages.length; i++) {
|
||||
//console.log("Message: ",messages[i]);
|
||||
if(messages[i].startsWith("set ")) {
|
||||
//msgblocks = messages[i].split(' ',3);
|
||||
let msgblocks = SplitN(messages[i]," ",3);
|
||||
if(msgblocks.length < 3) continue;
|
||||
document.querySelector(msgblocks[1]).innerHTML = msgblocks[2];
|
||||
} else if(messages[i].startsWith("set-class ")) {
|
||||
let msgblocks = SplitN(messages[i]," ",3);
|
||||
if(msgblocks.length < 3) continue;
|
||||
document.querySelector(msgblocks[1]).className = msgblocks[2];
|
||||
if ("msg" in data) {
|
||||
var msg = data.msg
|
||||
if("sub" in data)
|
||||
for(var i = 0; i < data.sub.length; i++)
|
||||
msg = msg.replace("\{"+i+"\}", data.sub[i]);
|
||||
|
||||
if("avatar" in data) alertList.push("<div class='alertItem withAvatar' style='background-image:url(\""+data.avatar+"\");'><a class='text' data-asid='"+data.asid+"' href=\""+data.path+"\">"+msg+"</a></div>");
|
||||
else alertList.push("<div class='alertItem'><a href=\""+data.path+"\" class='text'>"+msg+"</a></div>");
|
||||
if(alertList.length > 8) alertList.shift();
|
||||
//console.log("post alertList",alertList);
|
||||
alertCount++;
|
||||
|
||||
var alist = ""
|
||||
for (var i = 0; i < alertList.length; i++) alist += alertList[i];
|
||||
|
||||
//console.log(alist);
|
||||
// TODO: Add support for other alert feeds like PM Alerts
|
||||
var generalAlerts = document.getElementById("general_alerts");
|
||||
var alertListNode = generalAlerts.getElementsByClassName("alertList")[0];
|
||||
var alertCounterNode = generalAlerts.getElementsByClassName("alert_counter")[0];
|
||||
alertListNode.innerHTML = alist;
|
||||
alertCounterNode.textContent = alertCount;
|
||||
|
||||
// TODO: Add some sort of notification queue to avoid flooding the end-user with notices?
|
||||
// TODO: Use the site name instead of "Something Happened"
|
||||
if(Notification.permission === "granted") {
|
||||
var n = new Notification("Something Happened",{
|
||||
body: msg,
|
||||
icon: data.avatar,
|
||||
});
|
||||
setTimeout(n.close.bind(n), 8000);
|
||||
}
|
||||
|
||||
bindToAlerts();
|
||||
}
|
||||
}
|
||||
|
||||
var messages = event.data.split('\r');
|
||||
for(var i = 0; i < messages.length; i++) {
|
||||
//console.log("Message: ",messages[i]);
|
||||
if(messages[i].startsWith("set ")) {
|
||||
//msgblocks = messages[i].split(' ',3);
|
||||
let msgblocks = SplitN(messages[i]," ",3);
|
||||
if(msgblocks.length < 3) continue;
|
||||
document.querySelector(msgblocks[1]).innerHTML = msgblocks[2];
|
||||
} else if(messages[i].startsWith("set-class ")) {
|
||||
let msgblocks = SplitN(messages[i]," ",3);
|
||||
if(msgblocks.length < 3) continue;
|
||||
document.querySelector(msgblocks[1]).className = msgblocks[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
if(window["WebSocket"]) runWebSockets();
|
||||
else conn = false;
|
||||
|
||||
$(".open_edit").click(function(event){
|
||||
|
|
|
@ -115,9 +115,7 @@ func (build *accBuilder) Purge(table string) *sql.Stmt {
|
|||
return build.prepare(build.adapter.Purge("_builder", table))
|
||||
}
|
||||
|
||||
// These ones support transactions
|
||||
func (build *accBuilder) SimpleSelectTx(tx *sql.Tx, table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.SimpleSelect("_builder", table, columns, where, orderby, limit)
|
||||
func (build *accBuilder) prepareTx(tx *sql.Tx, res string, err error) (stmt *sql.Stmt) {
|
||||
if err != nil {
|
||||
build.recordError(err)
|
||||
return nil
|
||||
|
@ -127,70 +125,40 @@ func (build *accBuilder) SimpleSelectTx(tx *sql.Tx, table string, columns string
|
|||
return stmt
|
||||
}
|
||||
|
||||
// These ones support transactions
|
||||
func (build *accBuilder) SimpleSelectTx(tx *sql.Tx, table string, columns string, where string, orderby string, limit string) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.SimpleSelect("_builder", table, columns, where, orderby, limit)
|
||||
return build.prepareTx(tx, res, err)
|
||||
}
|
||||
|
||||
func (build *accBuilder) SimpleCountTx(tx *sql.Tx, table string, where string, limit string) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.SimpleCount("_builder", table, where, limit)
|
||||
if err != nil {
|
||||
build.recordError(err)
|
||||
return nil
|
||||
}
|
||||
stmt, err = tx.Prepare(res)
|
||||
build.recordError(err)
|
||||
return stmt
|
||||
return build.prepareTx(tx, res, err)
|
||||
}
|
||||
|
||||
func (build *accBuilder) SimpleLeftJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.SimpleLeftJoin("_builder", table1, table2, columns, joiners, where, orderby, limit)
|
||||
if err != nil {
|
||||
build.recordError(err)
|
||||
return nil
|
||||
}
|
||||
stmt, err = tx.Prepare(res)
|
||||
build.recordError(err)
|
||||
return stmt
|
||||
return build.prepareTx(tx, res, err)
|
||||
}
|
||||
|
||||
func (build *accBuilder) SimpleInnerJoinTx(tx *sql.Tx, table1 string, table2 string, columns string, joiners string, where string, orderby string, limit string) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.SimpleInnerJoin("_builder", table1, table2, columns, joiners, where, orderby, limit)
|
||||
if err != nil {
|
||||
build.recordError(err)
|
||||
return nil
|
||||
}
|
||||
stmt, err = tx.Prepare(res)
|
||||
build.recordError(err)
|
||||
return stmt
|
||||
return build.prepareTx(tx, res, err)
|
||||
}
|
||||
|
||||
func (build *accBuilder) CreateTableTx(tx *sql.Tx, table string, charset string, collation string, columns []DB_Table_Column, keys []DB_Table_Key) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.CreateTable("_builder", table, charset, collation, columns, keys)
|
||||
if err != nil {
|
||||
build.recordError(err)
|
||||
return nil
|
||||
}
|
||||
stmt, err = tx.Prepare(res)
|
||||
build.recordError(err)
|
||||
return stmt
|
||||
return build.prepareTx(tx, res, err)
|
||||
}
|
||||
|
||||
func (build *accBuilder) SimpleInsertTx(tx *sql.Tx, table string, columns string, fields string) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.SimpleInsert("_builder", table, columns, fields)
|
||||
if err != nil {
|
||||
build.recordError(err)
|
||||
return nil
|
||||
}
|
||||
stmt, err = tx.Prepare(res)
|
||||
build.recordError(err)
|
||||
return stmt
|
||||
return build.prepareTx(tx, res, err)
|
||||
}
|
||||
|
||||
func (build *accBuilder) SimpleInsertSelectTx(tx *sql.Tx, ins DB_Insert, sel DB_Select) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.SimpleInsertSelect("_builder", ins, sel)
|
||||
if err != nil {
|
||||
build.recordError(err)
|
||||
return nil
|
||||
}
|
||||
stmt, err = tx.Prepare(res)
|
||||
build.recordError(err)
|
||||
return stmt
|
||||
return build.prepareTx(tx, res, err)
|
||||
}
|
||||
|
||||
func (build *accBuilder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DB_Insert, sel DB_Join) (stmt *sql.Stmt) {
|
||||
|
@ -206,45 +174,21 @@ func (build *accBuilder) SimpleInsertLeftJoinTx(tx *sql.Tx, ins DB_Insert, sel D
|
|||
|
||||
func (build *accBuilder) SimpleInsertInnerJoinTx(tx *sql.Tx, ins DB_Insert, sel DB_Join) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.SimpleInsertInnerJoin("_builder", ins, sel)
|
||||
if err != nil {
|
||||
build.recordError(err)
|
||||
return nil
|
||||
}
|
||||
stmt, err = tx.Prepare(res)
|
||||
build.recordError(err)
|
||||
return stmt
|
||||
return build.prepareTx(tx, res, err)
|
||||
}
|
||||
|
||||
func (build *accBuilder) SimpleUpdateTx(tx *sql.Tx, table string, set string, where string) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.SimpleUpdate("_builder", table, set, where)
|
||||
if err != nil {
|
||||
build.recordError(err)
|
||||
return nil
|
||||
}
|
||||
stmt, err = tx.Prepare(res)
|
||||
build.recordError(err)
|
||||
return stmt
|
||||
return build.prepareTx(tx, res, err)
|
||||
}
|
||||
|
||||
func (build *accBuilder) SimpleDeleteTx(tx *sql.Tx, table string, where string) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.SimpleDelete("_builder", table, where)
|
||||
if err != nil {
|
||||
build.recordError(err)
|
||||
return nil
|
||||
}
|
||||
stmt, err = tx.Prepare(res)
|
||||
build.recordError(err)
|
||||
return stmt
|
||||
return build.prepareTx(tx, res, err)
|
||||
}
|
||||
|
||||
// I don't know why you need this, but here it is x.x
|
||||
func (build *accBuilder) PurgeTx(tx *sql.Tx, table string) (stmt *sql.Stmt) {
|
||||
res, err := build.adapter.Purge("_builder", table)
|
||||
if err != nil {
|
||||
build.recordError(err)
|
||||
return nil
|
||||
}
|
||||
stmt, err = tx.Prepare(res)
|
||||
build.recordError(err)
|
||||
return stmt
|
||||
return build.prepareTx(tx, res, err)
|
||||
}
|
||||
|
|
|
@ -212,9 +212,9 @@ func seedTables(adapter qgen.DB_Adapter) error {
|
|||
|
||||
//
|
||||
|
||||
qgen.Install.SimpleInsert("topics", "title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, createdBy, parentID", "'Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2")
|
||||
qgen.Install.SimpleInsert("topics", "title, content, parsed_content, createdAt, lastReplyAt, lastReplyBy, createdBy, parentID, ipaddress", "'Test Topic','A topic automatically generated by the software.','A topic automatically generated by the software.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1'")
|
||||
|
||||
qgen.Install.SimpleInsert("replies", "tid, content, parsed_content, createdAt, createdBy, lastUpdated, lastEdit, lastEditBy", "1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0")
|
||||
qgen.Install.SimpleInsert("replies", "tid, content, parsed_content, createdAt, createdBy, lastUpdated, lastEdit, lastEditBy, ipaddress", "1,'A reply!','A reply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1'")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -328,8 +328,6 @@ func writeInserts(adapter qgen.DB_Adapter) error {
|
|||
|
||||
adapter.SimpleInsert("addEmail", "emails", "email, uid, validated, token", "?,?,?,?")
|
||||
|
||||
adapter.SimpleInsert("createProfileReply", "users_replies", "uid, content, parsed_content, createdAt, createdBy, ipaddress", "?,?,?,UTC_TIMESTAMP(),?,?")
|
||||
|
||||
adapter.SimpleInsert("addSubscription", "activity_subscriptions", "user, targetID, targetType, level", "?,?,?,2")
|
||||
|
||||
adapter.SimpleInsert("addForumPermsToForum", "forums_permissions", "gid,fid,preset,permissions", "?,?,?,?")
|
||||
|
|
|
@ -16,18 +16,11 @@ type SQLReplyStore struct {
|
|||
}
|
||||
|
||||
func NewSQLReplyStore() (*SQLReplyStore, error) {
|
||||
getReplyStmt, err := qgen.Builder.SimpleSelect("replies", "tid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress, likeCount", "rid = ?", "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createReplyStmt, err := qgen.Builder.SimpleInsert("replies", "tid, content, parsed_content, createdAt, lastUpdated, ipaddress, words, createdBy", "?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
acc := qgen.Builder.Accumulator()
|
||||
return &SQLReplyStore{
|
||||
get: getReplyStmt,
|
||||
create: createReplyStmt,
|
||||
}, nil
|
||||
get: acc.SimpleSelect("replies", "tid, content, createdBy, createdAt, lastEdit, lastEditBy, ipaddress, likeCount", "rid = ?", "", ""),
|
||||
create: acc.SimpleInsert("replies", "tid, content, parsed_content, createdAt, lastUpdated, ipaddress, words, createdBy", "?,?,?,UTC_TIMESTAMP(),UTC_TIMESTAMP(),?,?,?"),
|
||||
}, acc.FirstError()
|
||||
}
|
||||
|
||||
func (store *SQLReplyStore) Get(id int) (*Reply, error) {
|
||||
|
|
|
@ -5,16 +5,48 @@ package main
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"regexp"
|
||||
"text/template/parse"
|
||||
)
|
||||
|
||||
var tagFinder *regexp.Regexp
|
||||
var limeFuncMap = map[string]interface{}{
|
||||
"and": "&&",
|
||||
"not": "!",
|
||||
"or": "||",
|
||||
"eq": true,
|
||||
"ge": true,
|
||||
"gt": true,
|
||||
"le": true,
|
||||
"lt": true,
|
||||
"ne": true,
|
||||
"add": true,
|
||||
"subtract": true,
|
||||
"multiply": true,
|
||||
"divide": true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
tagFinder = regexp.MustCompile(`(?s)\{\{(.*)\}\}`)
|
||||
}
|
||||
|
||||
func mangoParse(tmpl string) error {
|
||||
tree := parse.New(name, funcMap)
|
||||
var treeSet = make(map[string]*parse.Tree)
|
||||
tree, err = tree.Parse(content, "{{", "}}", treeSet, limeFuncMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
treeLength := len(tree.Root.Nodes)
|
||||
log.Print("treeLength", treeLength)
|
||||
return nil
|
||||
}
|
||||
|
||||
func icecreamSoup(tmpl string) error {
|
||||
if config.MinifyTemplates {
|
||||
tmpl = minify(tmpl)
|
||||
}
|
||||
tagIndices := tagFinder.FindAllStringIndex(tmpl, -1)
|
||||
if tagIndices != nil && len(tagIndices) > 0 {
|
||||
|
||||
|
|
|
@ -25,5 +25,5 @@ INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (3,2,'{"View
|
|||
INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (4,2,'{"ViewTopic":true}');
|
||||
INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (5,2,'{"ViewTopic":true}');
|
||||
INSERT INTO [forums_permissions] ([gid],[fid],[permissions]) VALUES (6,2,'{"ViewTopic":true}');
|
||||
INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[lastReplyBy],[createdBy],[parentID]) VALUES ('TestTopic','Atopicautomaticallygeneratedbythesoftware.','Atopicautomaticallygeneratedbythesoftware.',GETUTCDATE(),GETUTCDATE(),1,1,2);
|
||||
INSERT INTO [replies] ([tid],[content],[parsed_content],[createdAt],[createdBy],[lastUpdated],[lastEdit],[lastEditBy]) VALUES (1,'Areply!','Areply!',GETUTCDATE(),1,GETUTCDATE(),0,0);
|
||||
INSERT INTO [topics] ([title],[content],[parsed_content],[createdAt],[lastReplyAt],[lastReplyBy],[createdBy],[parentID],[ipaddress]) VALUES ('TestTopic','Atopicautomaticallygeneratedbythesoftware.','Atopicautomaticallygeneratedbythesoftware.',GETUTCDATE(),GETUTCDATE(),1,1,2,'::1');
|
||||
INSERT INTO [replies] ([tid],[content],[parsed_content],[createdAt],[createdBy],[lastUpdated],[lastEdit],[lastEditBy],[ipaddress]) VALUES (1,'Areply!','Areply!',GETUTCDATE(),1,GETUTCDATE(),0,0,'::1');
|
||||
|
|
|
@ -25,5 +25,5 @@ INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (3,2,'{"ViewT
|
|||
INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (4,2,'{"ViewTopic":true}');
|
||||
INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (5,2,'{"ViewTopic":true}');
|
||||
INSERT INTO `forums_permissions`(`gid`,`fid`,`permissions`) VALUES (6,2,'{"ViewTopic":true}');
|
||||
INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`createdBy`,`parentID`) VALUES ('TestTopic','Atopicautomaticallygeneratedbythesoftware.','Atopicautomaticallygeneratedbythesoftware.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2);
|
||||
INSERT INTO `replies`(`tid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`lastUpdated`,`lastEdit`,`lastEditBy`) VALUES (1,'Areply!','Areply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0);
|
||||
INSERT INTO `topics`(`title`,`content`,`parsed_content`,`createdAt`,`lastReplyAt`,`lastReplyBy`,`createdBy`,`parentID`,`ipaddress`) VALUES ('TestTopic','Atopicautomaticallygeneratedbythesoftware.','Atopicautomaticallygeneratedbythesoftware.',UTC_TIMESTAMP(),UTC_TIMESTAMP(),1,1,2,'::1');
|
||||
INSERT INTO `replies`(`tid`,`content`,`parsed_content`,`createdAt`,`createdBy`,`lastUpdated`,`lastEdit`,`lastEditBy`,`ipaddress`) VALUES (1,'Areply!','Areply!',UTC_TIMESTAMP(),1,UTC_TIMESTAMP(),0,0,'::1');
|
||||
|
|
|
@ -498,6 +498,8 @@ var topic_alt_104 = []byte(`
|
|||
`)
|
||||
var profile_0 = []byte(`
|
||||
|
||||
<div id="profile_container">
|
||||
|
||||
<div id="profile_left_lane" class="colstack_left">
|
||||
<!--<header class="colstack_item colstack_head rowhead">
|
||||
<div class="rowitem"><h1>Profile</h1></div>
|
||||
|
@ -639,6 +641,8 @@ var profile_42 = []byte(`' type="hidden" />
|
|||
var profile_43 = []byte(`
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
`)
|
||||
var profile_44 = []byte(`
|
||||
<script type="text/javascript">
|
||||
|
|
55
templates.go
55
templates.go
|
@ -121,24 +121,7 @@ func (c *CTemplateSet) compileTemplate(name string, dir string, expects string,
|
|||
c.FragmentCursor = make(map[string]int)
|
||||
c.FragmentCursor[fname] = 0
|
||||
|
||||
subtree := c.tlist[fname]
|
||||
if dev.TemplateDebug {
|
||||
fmt.Println(subtree.Root)
|
||||
}
|
||||
|
||||
treeLength := len(subtree.Root.Nodes)
|
||||
for index, node := range subtree.Root.Nodes {
|
||||
if dev.TemplateDebug {
|
||||
fmt.Println("Node: " + node.String())
|
||||
}
|
||||
|
||||
c.previousNode = c.currentNode
|
||||
c.currentNode = node.Type()
|
||||
if treeLength != (index + 1) {
|
||||
c.nextNode = subtree.Root.Nodes[index+1].Type()
|
||||
}
|
||||
out += c.compileSwitch(varholder, holdreflect, fname, node)
|
||||
}
|
||||
out += c.rootIterate(c.tlist[fname], varholder, holdreflect, fname)
|
||||
|
||||
var importList string
|
||||
if c.doImports {
|
||||
|
@ -178,6 +161,26 @@ w.Write([]byte(`, " + ", -1)
|
|||
return fout, nil
|
||||
}
|
||||
|
||||
func (c *CTemplateSet) rootIterate(tree *parse.Tree, varholder string, holdreflect reflect.Value, fname string) (out string) {
|
||||
if dev.TemplateDebug {
|
||||
fmt.Println(tree.Root)
|
||||
}
|
||||
treeLength := len(tree.Root.Nodes)
|
||||
for index, node := range tree.Root.Nodes {
|
||||
if dev.TemplateDebug {
|
||||
fmt.Println("Node: ", node.String())
|
||||
}
|
||||
|
||||
c.previousNode = c.currentNode
|
||||
c.currentNode = node.Type()
|
||||
if treeLength != (index + 1) {
|
||||
c.nextNode = tree.Root.Nodes[index+1].Type()
|
||||
}
|
||||
out += c.compileSwitch(varholder, holdreflect, fname, node)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (c *CTemplateSet) compileSwitch(varholder string, holdreflect reflect.Value, templateName string, node interface{}) (out string) {
|
||||
if dev.TemplateDebug {
|
||||
fmt.Println("in compileSwitch")
|
||||
|
@ -449,7 +452,6 @@ func (c *CTemplateSet) compileVarswitch(varholder string, holdreflect reflect.Va
|
|||
fmt.Println("Chain Node:", n.Node)
|
||||
fmt.Println("Chain Node Args:", node.Args)
|
||||
}
|
||||
break
|
||||
case *parse.IdentifierNode:
|
||||
if dev.TemplateDebug {
|
||||
fmt.Println("Identifier Node:", node)
|
||||
|
@ -957,7 +959,6 @@ func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflec
|
|||
case *parse.DotNode:
|
||||
varholder = pvarholder
|
||||
holdreflect = pholdreflect
|
||||
break
|
||||
case *parse.NilNode:
|
||||
panic("Nil is not a command x.x")
|
||||
default:
|
||||
|
@ -995,19 +996,7 @@ func (c *CTemplateSet) compileSubtemplate(pvarholder string, pholdreflect reflec
|
|||
c.localVars[fname]["."] = VarItemReflect{".", varholder, holdreflect}
|
||||
c.FragmentCursor[fname] = 0
|
||||
|
||||
treeLength := len(subtree.Root.Nodes)
|
||||
for index, node := range subtree.Root.Nodes {
|
||||
if dev.TemplateDebug {
|
||||
fmt.Println("Node:", node.String())
|
||||
}
|
||||
|
||||
c.previousNode = c.currentNode
|
||||
c.currentNode = node.Type()
|
||||
if treeLength != (index + 1) {
|
||||
c.nextNode = subtree.Root.Nodes[index+1].Type()
|
||||
}
|
||||
out += c.compileSwitch(varholder, holdreflect, fname, node)
|
||||
}
|
||||
out += c.rootIterate(subtree, varholder, holdreflect, fname)
|
||||
return out
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{{template "header.html" . }}
|
||||
|
||||
<div id="profile_container">
|
||||
|
||||
<div id="profile_left_lane" class="colstack_left">
|
||||
<!--<header class="colstack_item colstack_head rowhead">
|
||||
<div class="rowitem"><h1>Profile</h1></div>
|
||||
|
@ -98,6 +100,8 @@
|
|||
{{end}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{/** Quick subpage switcher **/}}
|
||||
{{/** TODO: Stop inlining this **/}}
|
||||
<script type="text/javascript">
|
||||
|
|
Loading…
Reference in New Issue