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:
Azareal 2018-12-27 19:12:30 +10:00
parent 6c42df3091
commit bf2af0ae96
14 changed files with 141 additions and 50 deletions

View File

@ -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"},

View File

@ -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)
}

View File

@ -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) + "&sectionType=" + sectionType)
addImage(media.URL + "?sectionID=" + strconv.Itoa(sectionID) + "&sectionType=" + sectionType)
continue
} else if media.Type == "image" {
addImage(media.URL)

View File

@ -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",

View File

@ -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
})*/
}

View File

@ -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 }
});
});

View File

@ -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)

View File

@ -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)
}
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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}}&sectionType=forums" height="24" width="24" />{{end}}
{{if .Image}}<img src="//{{$.Header.Site.URL}}/attachs/{{.Path}}?sectionID={{.SectionID}}&sectionType=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}}

View File

@ -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">

View File

@ -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;
}