diff --git a/public/global.js b/public/global.js
index 7d86b796..c7eeb8b3 100644
--- a/public/global.js
+++ b/public/global.js
@@ -380,7 +380,7 @@ function mainInit(){
$(".topic_name").html(topicNameInput);
$(".topic_name").attr(topicNameInput);
let topicContentInput = $('.topic_content_input').val();
- $(".topic_content").html(topicContentInput.replace("\n","
"));
+ $(".topic_content").html(quickParse(topicContentInput));
let topicStatusInput = $('.topic_status_input').val();
$(".topic_status_e:not(.open_edit)").html(topicStatusInput);
@@ -408,6 +408,20 @@ function mainInit(){
$(this).closest('.deletable_block').remove();
});
+ // Miniature implementation of the parser to avoid sending as much data back and forth
+ function quickParse(msg) {
+ msg = msg.replace(":)", "😀")
+ msg = msg.replace(":(", "😞")
+ msg = msg.replace(":D", "😃")
+ msg = msg.replace(":P", "😛")
+ msg = msg.replace(":O", "😲")
+ msg = msg.replace(":p", "😛")
+ msg = msg.replace(":o", "😲")
+ msg = msg.replace(";)", "😉")
+ msg = msg.replace("\n","
")
+ return msg
+ }
+
$(".edit_item").click(function(event){
event.preventDefault();
let blockParent = this.closest('.editable_parent');
@@ -418,18 +432,26 @@ function mainInit(){
if(srcNode!=null) source = srcNode.innerText;
else source = block.innerHTML;
// TODO: Add a client template for this
- block.innerHTML = "
";
+ block.innerHTML = "
";
$(".submit_edit").click(function(event){
event.preventDefault();
block.classList.remove("in_edit");
let newContent = block.querySelector('textarea').value;
- block.innerHTML = newContent.replace("\n","
");
+ block.innerHTML = quickParse(newContent);
if(srcNode!=null) srcNode.innerText = newContent;
let formAction = this.closest('a').getAttribute("href");
// TODO: Bounce the parsed post back and set innerHTML to it?
- $.ajax({ url: formAction, type: "POST", error: ajaxError, dataType: "json", data: { isJs: "1", edit_item: newContent }
+ $.ajax({
+ url: formAction,
+ type: "POST",
+ dataType: "json",
+ data: { js: "1", edit_item: newContent },
+ error: ajaxError,
+ success: (data,status,xhr) => {
+ if("Content" in data) block.innerHTML = data["Content"];
+ }
});
});
});
diff --git a/routes/attachments.go b/routes/attachments.go
index 2bd2966c..5e308c0d 100644
--- a/routes/attachments.go
+++ b/routes/attachments.go
@@ -3,6 +3,7 @@ package routes
import (
"database/sql"
"net/http"
+ "os"
"path/filepath"
"strconv"
"strings"
@@ -84,3 +85,75 @@ func ShowAttachment(w http.ResponseWriter, r *http.Request, user common.User, fi
http.ServeFile(w, r, "./attachs/"+filename)
return nil
}
+
+// TODO: Add a table for the files and lock the file row when performing tasks related to the file
+func deleteAttachment(w http.ResponseWriter, r *http.Request, user common.User, aid int, js bool) common.RouteError {
+ attach, err := common.Attachments.Get(aid)
+ if err == sql.ErrNoRows {
+ return common.NotFoundJSQ(w, r, nil, js)
+ } else if err != nil {
+ return common.InternalErrorJSQ(err, w, r, js)
+ }
+
+ err = common.Attachments.Delete(aid)
+ if err != nil {
+ return common.InternalErrorJSQ(err, w, r, js)
+ }
+
+ count := common.Attachments.CountInPath(attach.Path)
+ if err != nil {
+ return common.InternalErrorJSQ(err, w, r, js)
+ }
+ if count == 0 {
+ err := os.Remove("./attachs/" + attach.Path)
+ if err != nil {
+ return common.InternalErrorJSQ(err, w, r, js)
+ }
+ }
+
+ return nil
+}
+
+// TODO: Stop duplicating this code
+// TODO: Use a transaction here
+// TODO: Move this function to neutral ground
+func uploadAttachment(w http.ResponseWriter, r *http.Request, user common.User, sid int, sectionTable string, oid int, originTable string) (pathMap map[string]string, rerr common.RouteError) {
+ pathMap = make(map[string]string)
+ files, rerr := uploadFilesWithHash(w, r, user, "./attachs/")
+ if rerr != nil {
+ return nil, rerr
+ }
+
+ for _, filename := range files {
+ aid, err := common.Attachments.Add(sid, sectionTable, oid, originTable, user.ID, filename)
+ if err != nil {
+ return nil, common.InternalError(err, w, r)
+ }
+
+ _, ok := pathMap[filename]
+ if ok {
+ pathMap[filename] += "," + strconv.Itoa(aid)
+ } else {
+ pathMap[filename] = strconv.Itoa(aid)
+ }
+
+ switch originTable {
+ case "topics":
+ _, err = topicStmts.updateAttachs.Exec(common.Attachments.CountIn(originTable, oid), oid)
+ if err != nil {
+ return nil, common.InternalError(err, w, r)
+ }
+ err = common.Topics.Reload(oid)
+ if err != nil {
+ return nil, common.InternalError(err, w, r)
+ }
+ case "replies":
+ _, err = replyStmts.updateAttachs.Exec(common.Attachments.CountIn(originTable, oid), oid)
+ if err != nil {
+ return nil, common.InternalError(err, w, r)
+ }
+ }
+ }
+
+ return pathMap, nil
+}
diff --git a/routes/reply.go b/routes/reply.go
index 42a8a9ba..46ff744c 100644
--- a/routes/reply.go
+++ b/routes/reply.go
@@ -2,6 +2,7 @@ package routes
import (
"database/sql"
+ "encoding/json"
"net/http"
"strconv"
"strings"
@@ -28,17 +29,24 @@ func init() {
})
}
+type JsonReply struct {
+ Content string
+}
+
func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
+ // TODO: Use this
+ js := r.FormValue("js") == "1"
+
tid, err := strconv.Atoi(r.PostFormValue("tid"))
if err != nil {
- return common.PreError("Failed to convert the Topic ID", w, r)
+ return common.PreErrorJSQ("Failed to convert the Topic ID", w, r, js)
}
topic, err := common.Topics.Get(tid)
if err == sql.ErrNoRows {
- return common.PreError("Couldn't find the parent topic", w, r)
+ return common.PreErrorJSQ("Couldn't find the parent topic", w, r, js)
} else if err != nil {
- return common.InternalError(err, w, r)
+ return common.InternalErrorJSQ(err, w, r, js)
}
// TODO: Add hooks to make use of headerLite
@@ -47,22 +55,22 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User)
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.CreateReply {
- return common.NoPermissions(w, r, user)
+ return common.NoPermissionsJSQ(w, r, user, js)
}
if topic.IsClosed && !user.Perms.CloseTopic {
- return common.NoPermissions(w, r, user)
+ return common.NoPermissionsJSQ(w, r, user, js)
}
content := common.PreparseMessage(r.PostFormValue("reply-content"))
// TODO: Fully parse the post and put that in the parsed column
rid, err := common.Rstore.Create(topic, content, user.LastIP, user.ID)
if err != nil {
- return common.InternalError(err, w, r)
+ return common.InternalErrorJSQ(err, w, r, js)
}
reply, err := common.Rstore.Get(rid)
if err != nil {
- return common.LocalError("Unable to load the reply", w, r, user)
+ return common.LocalErrorJSQ("Unable to load the reply", w, r, user, js)
}
// Handle the file attachments
@@ -84,13 +92,13 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User)
if strings.HasPrefix(key, "pollinputitem[") {
halves := strings.Split(key, "[")
if len(halves) != 2 {
- return common.LocalError("Malformed pollinputitem", w, r, user)
+ return common.LocalErrorJSQ("Malformed pollinputitem", w, r, user, js)
}
halves[1] = strings.TrimSuffix(halves[1], "]")
index, err := strconv.Atoi(halves[1])
if err != nil {
- return common.LocalError("Malformed pollinputitem", w, r, user)
+ return common.LocalErrorJSQ("Malformed pollinputitem", w, r, user, js)
}
// If there are duplicates, then something has gone horribly wrong, so let's ignore them, this'll likely happen during an attack
@@ -115,25 +123,35 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User)
pollType := 0 // Basic single choice
_, err := common.Polls.Create(reply, pollType, seqPollInputItems)
if err != nil {
- return common.LocalError("Failed to add poll to reply", w, r, user) // TODO: Might need to be an internal error as it could leave phantom polls?
+ return common.LocalErrorJSQ("Failed to add poll to reply", w, r, user, js) // TODO: Might need to be an internal error as it could leave phantom polls?
}
}
err = common.Forums.UpdateLastTopic(tid, user.ID, topic.ParentID)
if err != nil && err != sql.ErrNoRows {
- return common.InternalError(err, w, r)
+ return common.InternalErrorJSQ(err, w, r, js)
}
common.AddActivityAndNotifyAll(user.ID, topic.CreatedBy, "reply", "topic", tid)
if err != nil {
- return common.InternalError(err, w, r)
+ return common.InternalErrorJSQ(err, w, r, js)
}
- http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
wcount := common.WordCount(content)
err = user.IncreasePostStats(wcount, false)
if err != nil {
- return common.InternalError(err, w, r)
+ return common.InternalErrorJSQ(err, w, r, js)
+ }
+
+ if js {
+ outBytes, err := json.Marshal(JsonReply{common.ParseMessage(reply.Content, topic.ParentID, "forums")})
+ if err != nil {
+ return common.InternalErrorJSQ(err, w, r, js)
+ }
+ w.Write(outBytes)
+ } else {
+ // TODO: Send the user to the specific post on the page
+ http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
}
counters.PostCounter.Bump()
@@ -143,25 +161,25 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User)
// TODO: Disable stat updates in posts handled by plugin_guilds
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, srid string) common.RouteError {
- isJs := (r.PostFormValue("js") == "1")
+ js := (r.PostFormValue("js") == "1")
rid, err := strconv.Atoi(srid)
if err != nil {
- return common.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, isJs)
+ return common.PreErrorJSQ("The provided Reply ID is not a valid number.", w, r, js)
}
reply, err := common.Rstore.Get(rid)
if err == sql.ErrNoRows {
- return common.PreErrorJSQ("The target reply doesn't exist.", w, r, isJs)
+ return common.PreErrorJSQ("The target reply doesn't exist.", w, r, js)
} else if err != nil {
- return common.InternalErrorJSQ(err, w, r, isJs)
+ return common.InternalErrorJSQ(err, w, r, js)
}
topic, err := reply.Topic()
if err == sql.ErrNoRows {
- return common.PreErrorJSQ("The parent topic doesn't exist.", w, r, isJs)
+ return common.PreErrorJSQ("The parent topic doesn't exist.", w, r, js)
} else if err != nil {
- return common.InternalErrorJSQ(err, w, r, isJs)
+ return common.InternalErrorJSQ(err, w, r, js)
}
// TODO: Add hooks to make use of headerLite
@@ -170,24 +188,37 @@ func ReplyEditSubmit(w http.ResponseWriter, r *http.Request, user common.User, s
return ferr
}
if !user.Perms.ViewTopic || !user.Perms.EditReply {
- return common.NoPermissionsJSQ(w, r, user, isJs)
+ return common.NoPermissionsJSQ(w, r, user, js)
}
if topic.IsClosed && !user.Perms.CloseTopic {
- return common.NoPermissionsJSQ(w, r, user, isJs)
+ return common.NoPermissionsJSQ(w, r, user, js)
}
err = reply.SetPost(r.PostFormValue("edit_item"))
if err == sql.ErrNoRows {
- return common.PreErrorJSQ("The parent topic doesn't exist.", w, r, isJs)
+ return common.PreErrorJSQ("The parent topic doesn't exist.", w, r, js)
} else if err != nil {
- return common.InternalErrorJSQ(err, w, r, isJs)
+ return common.InternalErrorJSQ(err, w, r, js)
}
- if !isJs {
+ // TODO: Avoid the load to get this faster?
+ reply, err = common.Rstore.Get(rid)
+ if err == sql.ErrNoRows {
+ return common.PreErrorJSQ("The updated reply doesn't exist.", w, r, js)
+ } else if err != nil {
+ return common.InternalErrorJSQ(err, w, r, js)
+ }
+
+ if !js {
http.Redirect(w, r, "/topic/"+strconv.Itoa(topic.ID)+"#reply-"+strconv.Itoa(rid), http.StatusSeeOther)
} else {
- w.Write(successJSONBytes)
+ outBytes, err := json.Marshal(JsonReply{common.ParseMessage(reply.Content, topic.ParentID, "forums")})
+ if err != nil {
+ return common.InternalErrorJSQ(err, w, r, js)
+ }
+ w.Write(outBytes)
}
+
return nil
}
diff --git a/routes/topic.go b/routes/topic.go
index 920bcf3b..6525a1f8 100644
--- a/routes/topic.go
+++ b/routes/topic.go
@@ -569,78 +569,6 @@ func uploadFilesWithHash(w http.ResponseWriter, r *http.Request, user common.Use
return filenames, nil
}
-// TODO: Add a table for the files and lock the file row when performing tasks related to the file
-func deleteAttachment(w http.ResponseWriter, r *http.Request, user common.User, aid int, js bool) common.RouteError {
- attach, err := common.Attachments.Get(aid)
- if err == sql.ErrNoRows {
- return common.NotFoundJSQ(w, r, nil, js)
- } else if err != nil {
- return common.InternalErrorJSQ(err, w, r, js)
- }
-
- err = common.Attachments.Delete(aid)
- if err != nil {
- return common.InternalErrorJSQ(err, w, r, js)
- }
-
- count := common.Attachments.CountInPath(attach.Path)
- if err != nil {
- return common.InternalErrorJSQ(err, w, r, js)
- }
- if count == 0 {
- err := os.Remove("./attachs/" + attach.Path)
- if err != nil {
- return common.InternalErrorJSQ(err, w, r, js)
- }
- }
-
- return nil
-}
-
-// TODO: Stop duplicating this code
-// TODO: Use a transaction here
-// TODO: Move this function to neutral ground
-func uploadAttachment(w http.ResponseWriter, r *http.Request, user common.User, sid int, sectionTable string, oid int, originTable string) (pathMap map[string]string, rerr common.RouteError) {
- pathMap = make(map[string]string)
- files, rerr := uploadFilesWithHash(w, r, user, "./attachs/")
- if rerr != nil {
- return nil, rerr
- }
-
- for _, filename := range files {
- aid, err := common.Attachments.Add(sid, sectionTable, oid, originTable, user.ID, filename)
- if err != nil {
- return nil, common.InternalError(err, w, r)
- }
-
- _, ok := pathMap[filename]
- if ok {
- pathMap[filename] += "," + strconv.Itoa(aid)
- } else {
- pathMap[filename] = strconv.Itoa(aid)
- }
-
- switch originTable {
- case "topics":
- _, err = topicStmts.updateAttachs.Exec(common.Attachments.CountIn(originTable,oid), oid)
- if err != nil {
- return nil, common.InternalError(err, w, r)
- }
- err = common.Topics.Reload(oid)
- if err != nil {
- return nil, common.InternalError(err, w, r)
- }
- case "replies":
- _, err = replyStmts.updateAttachs.Exec(common.Attachments.CountIn(originTable,oid), oid)
- if err != nil {
- return nil, common.InternalError(err, w, r)
- }
- }
- }
-
- return pathMap, nil
-}
-
// TODO: Update the stats after edits so that we don't under or over decrement stats during deletes
// TODO: Disable stat updates in posts handled by plugin_guilds
func EditTopicSubmit(w http.ResponseWriter, r *http.Request, user common.User, stid string) common.RouteError {