From 4813403fbb9b2eb3c9e4f3ed0338a5f63b535440 Mon Sep 17 00:00:00 2001 From: Azareal Date: Fri, 28 Dec 2018 12:08:35 +1000 Subject: [PATCH] Posts should now be properly rendered after an inline edit. Moved uploadAttachment and deleteAttachment from route/topic.go to route/attachments.go Fixes #47 --- public/global.js | 30 +++++++++++++--- routes/attachments.go | 73 +++++++++++++++++++++++++++++++++++++ routes/reply.go | 83 +++++++++++++++++++++++++++++-------------- routes/topic.go | 72 ------------------------------------- 4 files changed, 156 insertions(+), 102 deletions(-) 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 {