You can now dismiss alerts.

Refactored the API system.
Fixed a bug in the default topic where the forum didn't have a time set.
The JS is now run in strict mode.
Fixed a bug in the titles where they broke onto multiple lines.
Fixed a bug in create topic where the datetime wasn't getting set for the forum.
Fixed a bug in create reply where the local cache wasn't getting updated.
Fixed a bug in create reply where the wrong forum was getting updated.

Phased out jQuery in a few bits of JS to make it easier to understand.
This commit is contained in:
Azareal 2017-08-18 13:16:56 +01:00
parent ca9c755a47
commit 166d96e3b2
14 changed files with 108 additions and 68 deletions

View File

@ -2,8 +2,10 @@ package main
import "log"
import "strings"
import "strconv"
import "errors"
// These notes are for me, don't worry about it too much ^_^
/*
"You received a friend invite from {user}"
"{x}{mentioned you on}{user}{'s profile}"
@ -18,7 +20,7 @@ import "errors"
"{x}{created a new topic}{topic}"
*/
func build_alert(event string, elementType string, actor_id int, targetUser_id int, elementID int, user User /* The current user */) (string, error) {
func build_alert(asid int, event string, elementType string, actor_id int, targetUser_id int, elementID int, user User /* The current user */) (string, error) {
var targetUser *User
actor, err := users.CascadeGet(actor_id)
@ -35,7 +37,7 @@ func build_alert(event string, elementType string, actor_id int, targetUser_id i
}*/
if event == "friend_invite" {
return `{"msg":"You received a friend invite from {0}","sub":["` + actor.Name + `"],"path":"`+actor.Link+`","avatar":"`+strings.Replace(actor.Avatar,"/","\\/",-1)+`"}`, nil
return `{"msg":"You received a friend invite from {0}","sub":["` + actor.Name + `"],"path":"`+actor.Link+`","avatar":"`+strings.Replace(actor.Avatar,"/","\\/",-1)+`","asid":"`+strconv.Itoa(asid)+`"}`, nil
}
var act, post_act, url, area string
@ -109,7 +111,7 @@ func build_alert(event string, elementType string, actor_id int, targetUser_id i
case "reply": act = "replied to"
}
return `{"msg":"{0} ` + start_frag + act + post_act + ` {1}` + end_frag + `","sub":["` + actor.Name + `","` + area + `"],"path":"` + url + `","avatar":"` + actor.Avatar + `"}`, nil
return `{"msg":"{0} ` + start_frag + act + post_act + ` {1}` + end_frag + `","sub":["` + actor.Name + `","` + area + `"],"path":"` + url + `","avatar":"` + actor.Avatar + `","asid":"`+strconv.Itoa(asid)+`"}`, nil
}
func notify_watchers(asid int64) {
@ -144,5 +146,5 @@ func notify_watchers(asid int64) {
return
}
_ = ws_hub.push_alerts(uids, event, elementType, actor_id, targetUser_id, elementID)
_ = ws_hub.push_alerts(uids, int(asid), event, elementType, actor_id, targetUser_id, elementID)
}

View File

@ -118,6 +118,11 @@ func LoginRequired(w http.ResponseWriter, r *http.Request, user User) {
fmt.Fprintln(w,b.String())
}
func PreErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
}
func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, is_js string) {
w.WriteHeader(500)
if is_js == "0" {

View File

@ -109,6 +109,7 @@ var delete_reply_stmt *sql.Stmt
var delete_topic_stmt *sql.Stmt
var delete_profile_reply_stmt *sql.Stmt
var delete_forum_perms_by_forum_stmt *sql.Stmt
var delete_activity_stream_match_stmt *sql.Stmt
var report_exists_stmt *sql.Stmt
var group_count_stmt *sql.Stmt
var modlog_count_stmt *sql.Stmt
@ -734,6 +735,12 @@ func _gen_mysql() (err error) {
return err
}
log.Print("Preparing delete_activity_stream_match statement.")
delete_activity_stream_match_stmt, err = db.Prepare("DELETE FROM `activity_stream_matches` WHERE `watcher` = ? AND `asid` = ?")
if err != nil {
return err
}
log.Print("Preparing report_exists statement.")
report_exists_stmt, err = db.Prepare("SELECT COUNT(*) AS `count` FROM `topics` WHERE `data` = ? AND `data` != '' AND `parentID` = 1")
if err != nil {

View File

@ -238,7 +238,7 @@ INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`) VALUES ('Awaiting
INSERT INTO users_groups(`name`,`permissions`,`plugin_perms`,`tag`) VALUES ('Not Loggedin','{"ViewTopic":true}','{}','Guest');
INSERT INTO forums(`name`,`active`) VALUES ('Reports',0);
INSERT INTO forums(`name`,`lastTopicTime`) VALUES ('General',UTC_TIMESTAMP());
INSERT INTO forums(`name`,`lastTopicTime`,`lastTopicID`,`lastReplyer`,`lastReplyerID`,`lastTopic`) VALUES ('General',UTC_TIMESTAMP(),1,"Admin",1,'Test Topic');
INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (1,1,'{"ViewTopic":true,"CreateReply":true,"CreateTopic":true,"PinTopic":true,"CloseTopic":true}');
INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (2,1,'{"ViewTopic":true,"CreateReply":true,"CloseTopic":true}');
INSERT INTO forums_permissions(`gid`,`fid`,`permissions`) VALUES (3,1,'{}');

View File

@ -28,11 +28,11 @@ func (hub *WS_Hub) push_message(_ int, _ string) error {
return ws_nouser
}
func(hub *WS_Hub) push_alert(_ int, _ string, _ string, _ int, _ int, _ int) error {
func(hub *WS_Hub) push_alert(_ int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return ws_nouser
}
func(hub *WS_Hub) push_alerts(_ []int, _ string, _ string, _ int, _ int, _ int) error {
func(hub *WS_Hub) push_alerts(_ []int, _ int, _ string, _ string, _ int, _ int, _ int) error {
return ws_nouser
}

View File

@ -1,3 +1,4 @@
'use strict';
var form_vars = {};
var alertList = [];
var alertCount = 0;
@ -11,6 +12,14 @@ function post_link(event)
$.ajax({ url: form_action, type: "POST", dataType: "json", data: {js: "1"} });
}
function bind_to_alerts() {
$(".alertItem.withAvatar a").click(function(event) {
event.stopPropagation();
$.ajax({ url: "/api/?action=set&module=dismiss-alert", type: "POST", dataType: "json", data: { asid: $(this).attr("data-asid") } });
});
}
// TO-DO: Add the ability for users to dismiss alerts
function load_alerts(menu_alerts)
{
var alertListNode = menu_alerts.getElementsByClassName("alertList")[0];
@ -19,7 +28,7 @@ function load_alerts(menu_alerts)
$.ajax({
type: 'get',
dataType: 'json',
url:'/api/?action=get&module=alerts&format=json',
url:'/api/?action=get&module=alerts',
success: function(data) {
if("errmsg" in data) {
alertListNode.innerHTML = "<div class='alertItem'>"+data.errmsg+"</div>";
@ -41,8 +50,8 @@ function load_alerts(menu_alerts)
}
if("avatar" in msg) {
alist += "<div class='alertItem withAvatar' style='background-image:url(\""+msg.avatar+"\");'><a class='text' href=\""+msg.path+"\">"+mmsg+"</a></div>";
alertList.push("<div class='alertItem withAvatar' style='background-image:url(\""+msg.avatar+"\");'><a class='text' href=\""+msg.path+"\">"+mmsg+"</a></div>");
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>");
anyAvatar = true
} else {
alist += "<div class='alertItem'><a href=\""+msg.path+"\" class='text'>"+mmsg+"</a></div>";
@ -65,6 +74,8 @@ function load_alerts(menu_alerts)
menu_alerts.classList.remove("has_alerts");
}
alertCount = data.msgCount;
bind_to_alerts();
},
error: function(magic,theStatus,error) {
try {
@ -123,26 +134,26 @@ $(document).ready(function(){
if ("msg" in data) {
var msg = data.msg
if("sub" in data) {
for(var i = 0; i < data.sub.length; i++) {
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' href=\""+data.path+"\">"+msg+"</a></div>");
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];
}
for (var i = 0; i < alertList.length; i++) alist += alertList[i];
//console.log(alist);
$("#general_alerts").find(".alertList").html(alist); // Add support for other alert feeds like PM Alerts
$("#general_alerts").find(".alert_counter").text(alertCount);
// TO-DO: 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;
// TO-DO: Add some sort of notification queue to avoid flooding the end-user with notices?
// TO-DO: Use the site name instead of "Something Happened"
@ -153,6 +164,8 @@ $(document).ready(function(){
});
setTimeout(n.close.bind(n), 8000);
}
bind_to_alerts();
}
}
@ -337,6 +350,7 @@ $(document).ready(function(){
});
});
// This one's for Tempra Conflux
$(".ip_item").each(function(){
var ip = this.textContent;
if(ip.length > 10){

View File

@ -391,6 +391,10 @@ func write_deletes(adapter qgen.DB_Adapter) error {
adapter.SimpleDelete("delete_forum_perms_by_forum","forums_permissions","fid = ?")
adapter.SimpleDelete("delete_activity_stream_match","activity_stream_matches","watcher = ? AND asid = ?")
//adapter.SimpleDelete("delete_activity_stream_matches_by_watcher","activity_stream_matches","watcher = ?")
return nil
}

View File

@ -841,7 +841,7 @@ func route_topic_create_submit(w http.ResponseWriter, r *http.Request, user User
return
}
err = fstore.UpdateLastTopic(topic_name,int(lastId),user.Name,user.ID,"",fid)
err = fstore.UpdateLastTopic(topic_name,int(lastId),user.Name,user.ID,time.Now().Format("2006-01-02 15:04:05"),fid)
if err != nil && err != ErrNoRows {
InternalError(err,w)
}
@ -896,8 +896,8 @@ func route_create_reply(w http.ResponseWriter, r *http.Request, user User) {
InternalError(err,w)
return
}
_, err = update_forum_cache_stmt.Exec(topic.Title,tid,user.Name,user.ID,1)
if err != nil {
err = fstore.UpdateLastTopic(topic.Title,tid,user.Name,user.ID,time.Now().Format("2006-01-02 15:04:05"),topic.ParentID)
if err != nil && err != ErrNoRows {
InternalError(err,w)
return
}
@ -1027,7 +1027,7 @@ func route_like_topic(w http.ResponseWriter, r *http.Request, user User) {
}
// Live alerts, if the poster is online and WebSockets is enabled
_ = ws_hub.push_alert(topic.CreatedBy,"like","topic",user.ID,topic.CreatedBy,tid)
_ = ws_hub.push_alert(topic.CreatedBy,int(lastId),"like","topic",user.ID,topic.CreatedBy,tid)
// Reload the topic...
err = topics.Load(tid)
@ -1137,7 +1137,7 @@ func route_reply_like_submit(w http.ResponseWriter, r *http.Request, user User)
}
// Live alerts, if the poster is online and WebSockets is enabled
_ = ws_hub.push_alert(reply.CreatedBy,"like","post",user.ID,reply.CreatedBy,rid)
_ = ws_hub.push_alert(reply.CreatedBy,int(lastId),"like","post",user.ID,reply.CreatedBy,rid)
http.Redirect(w,r,"/topic/" + strconv.Itoa(reply.ParentID),http.StatusSeeOther)
}
@ -1896,37 +1896,37 @@ func route_register_submit(w http.ResponseWriter, r *http.Request, user User) {
http.Redirect(w,r,"/",http.StatusSeeOther)
}
// TO-DO: We don't need support XML here to support sitemaps, we could handle those elsewhere
var phrase_login_alerts []byte = []byte(`{"msgs":[{"msg":"Login to see your alerts","path":"/accounts/login"}]}`)
func route_api(w http.ResponseWriter, r *http.Request, user User) {
w.Header().Set("Content-Type","application/json")
err := r.ParseForm()
format := r.FormValue("format")
// TO-DO: Change is_js from a string to a boolean value
var is_js string
if format == "json" {
is_js = "1"
} else { // html
is_js = "0"
}
if err != nil {
PreErrorJSQ("Bad Form",w,r,is_js)
PreErrorJS("Bad Form",w,r)
return
}
action := r.FormValue("action")
if action != "get" && action != "set" {
PreErrorJSQ("Invalid Action",w,r,is_js)
PreErrorJS("Invalid Action",w,r)
return
}
module := r.FormValue("module")
switch(module) {
case "alerts": // A feed of events tailored for a specific user
if format != "json" {
PreError("You can only fetch alerts in the JSON format!",w,r)
case "dismiss-alert":
asid, err := strconv.Atoi(r.FormValue("asid"))
if err != nil {
PreErrorJS("Invalid asid",w,r)
return
}
w.Header().Set("Content-Type","application/json")
_, err = delete_activity_stream_match_stmt.Exec(user.ID,asid)
if err != nil {
InternalError(err,w)
return
}
case "alerts": // A feed of events tailored for a specific user
if !user.Loggedin {
w.Write(phrase_login_alerts)
return
@ -1938,10 +1938,10 @@ func route_api(w http.ResponseWriter, r *http.Request, user User) {
err = get_activity_count_by_watcher_stmt.QueryRow(user.ID).Scan(&msgCount)
if err == ErrNoRows {
PreError("Couldn't find the parent topic",w,r)
PreErrorJS("Couldn't find the parent topic",w,r)
return
} else if err != nil {
InternalError(err,w)
InternalErrorJS(err,w,r)
return
}
@ -1958,7 +1958,7 @@ func route_api(w http.ResponseWriter, r *http.Request, user User) {
InternalErrorJS(err,w,r)
return
}
res, err := build_alert(event, elementType, actor_id, targetUser_id, elementID, user)
res, err := build_alert(asid, event, elementType, actor_id, targetUser_id, elementID, user)
if err != nil {
LocalErrorJS(err.Error(),w,r)
return
@ -1988,6 +1988,6 @@ func route_api(w http.ResponseWriter, r *http.Request, user User) {
return
}*/
default:
PreErrorJSQ("Invalid Module",w,r,is_js)
PreErrorJS("Invalid Module",w,r)
}
}

View File

@ -25,11 +25,9 @@ var header_8 []byte = []byte(`";</script>
<meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" />
</head>
<body>
<style>
`)
<style>`)
var header_9 []byte = []byte(`.supermod_only { display: none !important; }`)
var header_10 []byte = []byte(`
</style>
var header_10 []byte = []byte(`</style>
<div class="container">
`)
var menu_0 []byte = []byte(`<div class="nav">
@ -618,8 +616,8 @@ var forum_6 []byte = []byte(`?page=`)
var forum_7 []byte = []byte(`">&gt;</a></div>`)
var forum_8 []byte = []byte(`
<div class="rowblock rowhead">
<div class="rowitem `)
<div id="forum_head_block" class="rowblock rowhead">
<div class="rowitem forum_title`)
var forum_9 []byte = []byte(` has_opt`)
var forum_10 []byte = []byte(`"><a>`)
var forum_11 []byte = []byte(`</a>

View File

@ -7,7 +7,8 @@
<div class="rowitem passive"><a href="/user/edit/username/">Change Username</a></div>
<div class="rowitem passive"><a href="/user/edit/critical/">Change Password</a></div>
<div class="rowitem passive"><a href="/user/edit/email/">Change Email</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a href="/user/notifications">Notifications</a></div>
{{/** TO-DO: Add an alerts page with pagination to go through alerts which either don't fit in the alerts drop-down or which have already been dismissed. Bear in mind though that dismissed alerts older than two weeks might be purged to save space and to speed up the database **/}}
<div class="rowitem passive"><a>Coming Soon</a></div>
<div class="rowitem passive"><a>Coming Soon</a></div>
</div>

View File

@ -4,8 +4,8 @@
{{if ne .LastPage .Page}}<link rel="prerender" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}" />
<div id="nextFloat" class="next_button"><a class="next_link" href="/forum/{{.Forum.ID}}?page={{add .Page 1}}">&gt;</a></div>{{end}}
<div class="rowblock rowhead">
<div class="rowitem {{if ne .CurrentUser.ID 0}}has_opt{{end}}"><a>{{.Title}}</a>
<div id="forum_head_block" class="rowblock rowhead">
<div class="rowitem forum_title{{if ne .CurrentUser.ID 0}} has_opt{{end}}"><a>{{.Title}}</a>
</div>
{{if ne .CurrentUser.ID 0}}
{{if .CurrentUser.Perms.CreateTopic}}

View File

@ -15,9 +15,7 @@
<meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" />
</head>
<body>
<style>
{{if not .CurrentUser.Is_Super_Mod}}.supermod_only { display: none !important; }{{end}}
</style>
<style>{{if not .CurrentUser.Is_Super_Mod}}.supermod_only { display: none !important; }{{end}}</style>
<div class="container">
{{template "menu.html" .}}
<div id="back"><div id="main" {{if .Header.Widgets.RightSidebar}}class="shrink_main"{{end}}>

View File

@ -129,6 +129,7 @@ a {
.opthead, .rowhead, .colstack_head {
padding-bottom: 0px;
padding-top: 3px !important;
white-space: nowrap;
}
.rowblock:not(.opthead):not(.colstack_head):not(.rowhead) .rowitem {
@ -212,12 +213,24 @@ a {
.like_label:before {
content: "+1";
}
.edit_label:before { content: "Edit"; }
.trash_label:before { content: "Delete"; }
.pin_label:before { content: "Pin"; }
.unpin_label:before { content: "Unpin"; }
.flag_label:before { content: "Flag"; }
.level_label:before { content: "Level"; }
.edit_label:before {
content: "Edit";
}
.trash_label:before {
content: "Delete";
}
.pin_label:before {
content: "Pin";
}
.unpin_label:before {
content: "Unpin";
}
.flag_label:before {
content: "Flag";
}
.level_label:before {
content: "Level";
}
.like_count_label:before {
content: "likes";
@ -252,11 +265,9 @@ a {
.formrow.real_first_child, .formrow:first-child {
margin-top: 8px;
}
.formrow.real_first_child .formitem, .formrow:first-child .formitem {
padding-top: 12px;
}
.formrow:last-child .formitem {
padding-bottom: 12px;
}

View File

@ -87,7 +87,7 @@ func (hub *WS_Hub) push_message(targetUser int, msg string) error {
return nil
}
func(hub *WS_Hub) push_alert(targetUser int, event string, elementType string, actor_id int, targetUser_id int, elementID int) error {
func(hub *WS_Hub) push_alert(targetUser int, asid int, event string, elementType string, actor_id int, targetUser_id int, elementID int) error {
//log.Print("In push_alert")
hub.users.RLock()
ws_user, ok := hub.online_users[targetUser]
@ -97,7 +97,7 @@ func(hub *WS_Hub) push_alert(targetUser int, event string, elementType string, a
}
//log.Print("Building alert")
alert, err := build_alert(event, elementType, actor_id, targetUser_id, elementID, *ws_user.User)
alert, err := build_alert(asid, event, elementType, actor_id, targetUser_id, elementID, *ws_user.User)
if err != nil {
return err
}
@ -113,7 +113,7 @@ func(hub *WS_Hub) push_alert(targetUser int, event string, elementType string, a
return nil
}
func(hub *WS_Hub) push_alerts(users []int, event string, elementType string, actor_id int, targetUser_id int, elementID int) error {
func(hub *WS_Hub) push_alerts(users []int, asid int, event string, elementType string, actor_id int, targetUser_id int, elementID int) error {
//log.Print("In push_alerts")
var ws_users []*WS_User
hub.users.RLock()
@ -133,7 +133,7 @@ func(hub *WS_Hub) push_alerts(users []int, event string, elementType string, act
}
//log.Print("Building alert")
alert, err := build_alert(event, elementType, actor_id, targetUser_id, elementID, *ws_user.User)
alert, err := build_alert(asid, event, elementType, actor_id, targetUser_id, elementID, *ws_user.User)
if err != nil {
errs = append(errs,err)
}