Laid the foundations for better reply attachments.
The attachment manager introduced in the previous patch is now properly localised. Repurposed AttachmentStore.CountInTopic as a more general CountIn method. Added an & entity in attachment URLs so that the characters don't get mutated into something weird. Tried to make the linebreaks a little glitchy in the inline editor, we have a better solution in a mind soon! Fixed a bug where replies used .ContentHTML instead of .Content which led to a lot of HTML getting in the way of reply edits. Fixed a bug where reply attachments used the topicID rather than the replyID for their originID entries. Fixed a bug where the topic attachment counts weren't getting incremented. Added the topic.select_button_test, topic.copy_button_test and topic.upload_button_test phrases. Added the attachCount column to the replies table. This commit requires you to run the patcher / updater.
This commit is contained in:
parent
6c42df3091
commit
bf2af0ae96
|
@ -247,6 +247,7 @@ func createTables(adapter qgen.Adapter) error {
|
|||
tblColumn{"lastUpdated", "datetime", 0, false, false, ""},
|
||||
tblColumn{"ipaddress", "varchar", 200, false, false, "0.0.0.0.0"},
|
||||
tblColumn{"likeCount", "int", 0, false, false, "0"},
|
||||
tblColumn{"attachCount", "int", 0, false, false, "0"},
|
||||
tblColumn{"words", "int", 0, false, false, "1"}, // ? - replies has a default of 1 and topics has 0? why?
|
||||
tblColumn{"actionType", "varchar", 20, false, false, "''"},
|
||||
tblColumn{"poll", "int", 0, false, false, "0"},
|
||||
|
|
|
@ -26,31 +26,31 @@ type AttachmentStore interface {
|
|||
MiniTopicGet(id int) (alist []*MiniAttachment, err error)
|
||||
Add(sectionID int, sectionTable string, originID int, originTable string, uploadedBy int, path string) (int, error)
|
||||
GlobalCount() int
|
||||
CountInTopic(tid int) int
|
||||
CountIn(originTable string, oid int) int
|
||||
CountInPath(path string) int
|
||||
Delete(aid int) error
|
||||
}
|
||||
|
||||
type DefaultAttachmentStore struct {
|
||||
get *sql.Stmt
|
||||
getByTopic *sql.Stmt
|
||||
add *sql.Stmt
|
||||
count *sql.Stmt
|
||||
countInTopic *sql.Stmt
|
||||
countInPath *sql.Stmt
|
||||
delete *sql.Stmt
|
||||
get *sql.Stmt
|
||||
getByTopic *sql.Stmt
|
||||
add *sql.Stmt
|
||||
count *sql.Stmt
|
||||
countIn *sql.Stmt
|
||||
countInPath *sql.Stmt
|
||||
delete *sql.Stmt
|
||||
}
|
||||
|
||||
func NewDefaultAttachmentStore() (*DefaultAttachmentStore, error) {
|
||||
acc := qgen.NewAcc()
|
||||
return &DefaultAttachmentStore{
|
||||
get: acc.Select("attachments").Columns("originID, sectionID, uploadedBy, path").Where("attachID = ?").Prepare(),
|
||||
getByTopic: acc.Select("attachments").Columns("attachID, sectionID, uploadedBy, path").Where("originTable = 'topics' AND originID = ?").Prepare(),
|
||||
add: acc.Insert("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Prepare(),
|
||||
count: acc.Count("attachments").Prepare(),
|
||||
countInTopic: acc.Count("attachments").Where("originTable = 'topics' and originID = ?").Prepare(),
|
||||
countInPath: acc.Count("attachments").Where("path = ?").Prepare(),
|
||||
delete: acc.Delete("attachments").Where("attachID = ?").Prepare(),
|
||||
get: acc.Select("attachments").Columns("originID, sectionID, uploadedBy, path").Where("attachID = ?").Prepare(),
|
||||
getByTopic: acc.Select("attachments").Columns("attachID, sectionID, uploadedBy, path").Where("originTable = 'topics' AND originID = ?").Prepare(),
|
||||
add: acc.Insert("attachments").Columns("sectionID, sectionTable, originID, originTable, uploadedBy, path").Fields("?,?,?,?,?,?").Prepare(),
|
||||
count: acc.Count("attachments").Prepare(),
|
||||
countIn: acc.Count("attachments").Where("originTable = ? and originID = ?").Prepare(),
|
||||
countInPath: acc.Count("attachments").Where("path = ?").Prepare(),
|
||||
delete: acc.Delete("attachments").Where("attachID = ?").Prepare(),
|
||||
}, acc.FirstError()
|
||||
}
|
||||
|
||||
|
@ -107,8 +107,8 @@ func (store *DefaultAttachmentStore) GlobalCount() (count int) {
|
|||
return count
|
||||
}
|
||||
|
||||
func (store *DefaultAttachmentStore) CountInTopic(tid int) (count int) {
|
||||
err := store.countInTopic.QueryRow(tid).Scan(&count)
|
||||
func (store *DefaultAttachmentStore) CountIn(originTable string, oid int) (count int) {
|
||||
err := store.countIn.QueryRow(originTable, oid).Scan(&count)
|
||||
if err != nil {
|
||||
LogError(err)
|
||||
}
|
||||
|
|
|
@ -587,7 +587,7 @@ func ParseMessage(msg string, sectionID int, sectionType string /*, user User*/)
|
|||
|
||||
// TODO: Reduce the amount of code duplication
|
||||
if media.Type == "attach" {
|
||||
addImage(media.URL + "?sectionID=" + strconv.Itoa(sectionID) + "§ionType=" + sectionType)
|
||||
addImage(media.URL + "?sectionID=" + strconv.Itoa(sectionID) + "&sectionType=" + sectionType)
|
||||
continue
|
||||
} else if media.Type == "image" {
|
||||
addImage(media.URL)
|
||||
|
|
|
@ -376,6 +376,10 @@
|
|||
"topic.report_button_text":"Report",
|
||||
"topic.flag_button_text":"Flag",
|
||||
|
||||
"topic.select_button_text":"Select",
|
||||
"topic.copy_button_text":"Copy",
|
||||
"topic.upload_button_text":"Upload",
|
||||
|
||||
"panel_rank_admins":"Admins",
|
||||
"panel_rank_mods":"Mods",
|
||||
"panel_rank_banned":"Banned",
|
||||
|
|
|
@ -22,6 +22,7 @@ func init() {
|
|||
addPatch(8, patch8)
|
||||
addPatch(9, patch9)
|
||||
addPatch(10, patch10)
|
||||
addPatch(11, patch11)
|
||||
}
|
||||
|
||||
func patch0(scanner *bufio.Scanner) (err error) {
|
||||
|
@ -29,7 +30,6 @@ func patch0(scanner *bufio.Scanner) (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = execStmt(qgen.Builder.DropTable("menu_items"))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -400,7 +400,6 @@ func patch10(scanner *bufio.Scanner) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// We could probably do something more efficient, but as there shouldn't be too many sites right now, we can probably cheat a little, otherwise it'll take forever to get things done
|
||||
err = acc().Select("topics").Cols("tid").EachInt(func(tid int) error {
|
||||
stid := itoa(tid)
|
||||
|
||||
|
@ -430,3 +429,41 @@ func patch10(scanner *bufio.Scanner) error {
|
|||
_, err = acc().Insert("updates").Columns("dbVersion").Fields("0").Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
func patch11(scanner *bufio.Scanner) error {
|
||||
err := execStmt(qgen.Builder.AddColumn("replies", tblColumn{"attachCount", "int", 0, false, false, "0"}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Attachments for replies got the topicID rather than the replyID for a while in error, so we want to separate these out
|
||||
_, err = acc().Update("attachments").Set("originTable = 'freplies'").Where("originTable = 'replies'").Exec()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We could probably do something more efficient, but as there shouldn't be too many sites right now, we can probably cheat a little, otherwise it'll take forever to get things done
|
||||
return acc().Select("topics").Cols("tid").EachInt(func(tid int) error {
|
||||
stid := itoa(tid)
|
||||
|
||||
count, err := acc().Count("attachments").Where("originTable = 'topics' and originID = " + stid).Total()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = acc().Update("topics").Set("attachCount = ?").Where("tid = " + stid).Exec(count)
|
||||
return err
|
||||
})
|
||||
|
||||
/*return acc().Select("replies").Cols("rid").EachInt(func(rid int) error {
|
||||
srid := itoa(rid)
|
||||
|
||||
count, err := acc().Count("attachments").Where("originTable = 'replies' and originID = " + srid).Total()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = acc().Update("replies").Set("attachCount = ?").Where("rid = " + srid).Exec(count)
|
||||
return err
|
||||
})*/
|
||||
}
|
||||
|
|
|
@ -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)+/g,"<br />"));
|
||||
$(".topic_content").html(topicContentInput.replace("\n","<br><br>"));
|
||||
let topicStatusInput = $('.topic_status_input').val();
|
||||
$(".topic_status_e:not(.open_edit)").html(topicStatusInput);
|
||||
|
||||
|
@ -410,18 +410,25 @@ function mainInit(){
|
|||
|
||||
$(".edit_item").click(function(event){
|
||||
event.preventDefault();
|
||||
let blockParent = $(this).closest('.editable_parent');
|
||||
let block = blockParent.find('.editable_block').eq(0);
|
||||
block.html("<textarea style='width: 99%;' name='edit_item'>" + block.html() + "</textarea><br /><a href='" + $(this).closest('a').attr("href") + "'><button class='submit_edit' type='submit'>Update</button></a>");
|
||||
let blockParent = this.closest('.editable_parent');
|
||||
let srcNode = blockParent.querySelector(".edit_source");
|
||||
let block = blockParent.querySelector('.editable_block');
|
||||
block.classList.add("in_edit");
|
||||
let source = "";
|
||||
if(srcNode!=null) source = srcNode.innerText;
|
||||
else source = block.innerHTML;
|
||||
// TODO: Add a client template for this
|
||||
block.innerHTML = "<textarea style='width: 99%;' name='edit_item'>" + source + "</textarea><br /><a href='" + this.closest('a').getAttribute("href") + "'><button class='submit_edit' type='submit'>Update</button></a>";
|
||||
|
||||
$(".submit_edit").click(function(event){
|
||||
event.preventDefault();
|
||||
let blockParent = $(this).closest('.editable_parent');
|
||||
let block = blockParent.find('.editable_block').eq(0);
|
||||
let newContent = block.find('textarea').eq(0).val();
|
||||
block.html(newContent);
|
||||
block.classList.remove("in_edit");
|
||||
let newContent = block.querySelector('textarea').value;
|
||||
block.innerHTML = newContent.replace("\n","<br><br>");
|
||||
if(srcNode!=null) srcNode.innerText = newContent;
|
||||
|
||||
var formAction = $(this).closest('a').attr("href");
|
||||
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 }
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,8 +8,26 @@ import (
|
|||
|
||||
"github.com/Azareal/Gosora/common"
|
||||
"github.com/Azareal/Gosora/common/counters"
|
||||
"github.com/Azareal/Gosora/query_gen"
|
||||
)
|
||||
|
||||
type ReplyStmts struct {
|
||||
updateAttachs *sql.Stmt
|
||||
}
|
||||
|
||||
var replyStmts ReplyStmts
|
||||
|
||||
// TODO: Move this statement somewhere else
|
||||
func init() {
|
||||
common.DbInits.Add(func(acc *qgen.Accumulator) error {
|
||||
replyStmts = ReplyStmts{
|
||||
// TODO: Less race-y attachment count updates
|
||||
updateAttachs: acc.Update("replies").Set("attachCount = ?").Where("rid = ?").Prepare(),
|
||||
}
|
||||
return acc.FirstError()
|
||||
})
|
||||
}
|
||||
|
||||
func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError {
|
||||
tid, err := strconv.Atoi(r.PostFormValue("tid"))
|
||||
if err != nil {
|
||||
|
@ -35,15 +53,6 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User)
|
|||
return common.NoPermissions(w, r, user)
|
||||
}
|
||||
|
||||
// Handle the file attachments
|
||||
// TODO: Stop duplicating this code
|
||||
if user.Perms.UploadFiles {
|
||||
_, rerr := uploadAttachment(w, r, user, topic.ParentID, "forums", tid, "replies")
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -55,6 +64,16 @@ func CreateReplySubmit(w http.ResponseWriter, r *http.Request, user common.User)
|
|||
if err != nil {
|
||||
return common.LocalError("Unable to load the reply", w, r, user)
|
||||
}
|
||||
|
||||
// Handle the file attachments
|
||||
// TODO: Stop duplicating this code
|
||||
if user.Perms.UploadFiles {
|
||||
_, rerr := uploadAttachment(w, r, user, topic.ParentID, "forums", rid, "replies")
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
}
|
||||
|
||||
if r.PostFormValue("has_poll") == "1" {
|
||||
var maxPollOptions = 10
|
||||
var pollInputItems = make(map[int]string)
|
||||
|
|
|
@ -599,6 +599,7 @@ func deleteAttachment(w http.ResponseWriter, r *http.Request, user common.User,
|
|||
|
||||
// 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/")
|
||||
|
@ -619,9 +620,9 @@ func uploadAttachment(w http.ResponseWriter, r *http.Request, user common.User,
|
|||
pathMap[filename] = strconv.Itoa(aid)
|
||||
}
|
||||
|
||||
switch sectionTable {
|
||||
switch originTable {
|
||||
case "topics":
|
||||
_, err = topicStmts.updateAttachs.Exec(common.Attachments.CountInTopic(oid), oid)
|
||||
_, err = topicStmts.updateAttachs.Exec(common.Attachments.CountIn(originTable,oid), oid)
|
||||
if err != nil {
|
||||
return nil, common.InternalError(err, w, r)
|
||||
}
|
||||
|
@ -629,6 +630,11 @@ func uploadAttachment(w http.ResponseWriter, r *http.Request, user common.User,
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ CREATE TABLE [replies] (
|
|||
[lastUpdated] datetime not null,
|
||||
[ipaddress] nvarchar (200) DEFAULT '0.0.0.0.0' not null,
|
||||
[likeCount] int DEFAULT 0 not null,
|
||||
[attachCount] int DEFAULT 0 not null,
|
||||
[words] int DEFAULT 1 not null,
|
||||
[actionType] nvarchar (20) DEFAULT '' not null,
|
||||
[poll] int DEFAULT 0 not null,
|
||||
|
|
|
@ -10,6 +10,7 @@ CREATE TABLE `replies` (
|
|||
`lastUpdated` datetime not null,
|
||||
`ipaddress` varchar(200) DEFAULT '0.0.0.0.0' not null,
|
||||
`likeCount` int DEFAULT 0 not null,
|
||||
`attachCount` int DEFAULT 0 not null,
|
||||
`words` int DEFAULT 1 not null,
|
||||
`actionType` varchar(20) DEFAULT '' not null,
|
||||
`poll` int DEFAULT 0 not null,
|
||||
|
|
|
@ -10,6 +10,7 @@ CREATE TABLE "replies" (
|
|||
`lastUpdated` timestamp not null,
|
||||
`ipaddress` varchar (200) DEFAULT '0.0.0.0.0' not null,
|
||||
`likeCount` int DEFAULT 0 not null,
|
||||
`attachCount` int DEFAULT 0 not null,
|
||||
`words` int DEFAULT 1 not null,
|
||||
`actionType` varchar (20) DEFAULT '' not null,
|
||||
`poll` int DEFAULT 0 not null,
|
||||
|
|
|
@ -82,17 +82,17 @@
|
|||
{{if .Topic.Attachments}}<div class="show_on_edit attach_edit_bay" tid="{{.Topic.ID}}">
|
||||
{{range .Topic.Attachments}}
|
||||
<div class="attach_item{{if .Image}} attach_image_holder{{end}}">
|
||||
{{if .Image}}<img src="//{{$.Header.Site.URL}}/attachs/{{.Path}}?sectionID={{.SectionID}}§ionType=forums" height="24" width="24" />{{end}}
|
||||
{{if .Image}}<img src="//{{$.Header.Site.URL}}/attachs/{{.Path}}?sectionID={{.SectionID}}§ionType=forums" height=24 width=24 />{{end}}
|
||||
<span class="attach_item_path" aid="{{.ID}}" fullPath="//{{$.Header.Site.URL}}/attachs/{{.Path}}">{{.Path}}</span>
|
||||
<button class="attach_item_select">Select</button>
|
||||
<button class="attach_item_copy">Copy</button>
|
||||
<button class="attach_item_select">{{lang "topic.select_button_text"}}</button>
|
||||
<button class="attach_item_copy">{{lang "topic.copy_button_text"}}</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="attach_item attach_item_buttons">
|
||||
{{if .CurrentUser.Perms.UploadFiles}}
|
||||
<input name="upload_files" id="upload_files_op" multiple type="file" style="display: none;" />
|
||||
<label for="upload_files_op" class="formbutton add_file_button">Upload</label>{{end}}
|
||||
<button class="attach_item_delete">Delete</button>
|
||||
<label for="upload_files_op" class="formbutton add_file_button">{{lang "topic.upload_button_text"}}</label>{{end}}
|
||||
<button class="attach_item_delete">{{lang "topic.delete_button_text"}}</button>
|
||||
</div>
|
||||
</div>{{end}}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<span class="action_icon" style="font-size: 18px;padding-right: 5px;" aria-hidden="true">{{.ActionIcon}}</span>
|
||||
<span itemprop="text">{{.ActionType}}</span>
|
||||
{{else}}
|
||||
{{/** TODO: We might end up with <br>s in the inline editor, fix this **/}}
|
||||
<div class="edit_source auto_hide">{{.Content}}</div>
|
||||
<div class="editable_block user_content" itemprop="text">{{.ContentHtml}}</div>
|
||||
<div class="controls button_container{{if .LikeCount}} has_likes{{end}}">
|
||||
<div class="action_button_left">
|
||||
|
|
|
@ -710,6 +710,23 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) {
|
|||
border-radius: 3px;
|
||||
padding: 16px;
|
||||
}
|
||||
.user_content.in_edit {
|
||||
padding: 0px;
|
||||
background: none;
|
||||
}
|
||||
.user_content textarea {
|
||||
resize: vertical;
|
||||
height: 150px;
|
||||
width: 100% !important;
|
||||
padding: 16px;
|
||||
}
|
||||
.user_content.in_edit a {
|
||||
display: flex;
|
||||
background-color: #444444;
|
||||
border-radius: 4px;
|
||||
margin-top: 4px; /*8 without <br>*/
|
||||
padding: 6px;
|
||||
}
|
||||
.post_item .button_container {
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
|
@ -1014,10 +1031,7 @@ input[type=checkbox]:checked + label .sel {
|
|||
}
|
||||
|
||||
@media(max-width: 500px) {
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
.topic_view_count {
|
||||
.sidebar, .topic_view_count {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue