new modtools ux

- don't allow users to select things they shouldn't be able to
- hide mod options the user doesn't have access to
- highlight options which can be selected
- stop counting topics twice when they're clicked on multiple times

fix modtools not opening in some cases
fix command orders in batch files
fix global.js's getExt()
reduce boilerplate
This commit is contained in:
Azareal 2020-07-19 13:26:56 +10:00
parent 6260dbced7
commit be609b47ce
24 changed files with 445 additions and 254 deletions

View File

@ -202,20 +202,25 @@ type TopicListSort struct {
Ascending bool Ascending bool
} }
type QuickTools struct {
CanDelete bool
CanLock bool
CanMove bool
}
type TopicListPage struct { type TopicListPage struct {
*Header *Header
TopicList []*TopicsRow TopicList []TopicsRowMut
ForumList []Forum ForumList []Forum
DefaultForum int DefaultForum int
Sort TopicListSort Sort TopicListSort
CanLock bool QuickTools
CanMove bool
Paginator Paginator
} }
type ForumPage struct { type ForumPage struct {
*Header *Header
ItemList []*TopicsRow ItemList []TopicsRowMut
Forum *Forum Forum *Forum
CanLock bool CanLock bool
CanMove bool CanMove bool

View File

@ -226,9 +226,9 @@ func compileCommons(c *tmpl.CTemplateSet, head, head2 *Header, forumList []Forum
return head2 return head2
}*/ }*/
var topicsList []*TopicsRow var topicsList []TopicsRowMut
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 1, 0, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil}) topicsList = append(topicsList, TopicsRowMut{&TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 1, 0, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil}, false})
topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, false, false, Paginator{[]int{1}, 1, 1}} topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, QuickTools{false, false, false}, Paginator{[]int{1}, 1, 1}}
o.Add("topics", "c.TopicListPage", topicListPage) o.Add("topics", "c.TopicListPage", topicListPage)
o.Add("topics_mini", "c.TopicListPage", topicListPage) o.Add("topics_mini", "c.TopicListPage", topicListPage)
@ -309,9 +309,9 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
ppage := ProfilePage{htitle("User 526"), replyList, *user, 0, 0, false, false, false, false} // TODO: Use the score from user to generate the currentScore and nextScore ppage := ProfilePage{htitle("User 526"), replyList, *user, 0, 0, false, false, false, false} // TODO: Use the score from user to generate the currentScore and nextScore
t.Add("profile", "c.ProfilePage", ppage) t.Add("profile", "c.ProfilePage", ppage)
var topicsList []*TopicsRow var topicsList []TopicsRowMut
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "127.0.0.1", 1, 0, 1, 1, 0, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil}) topicsList = append(topicsList, TopicsRowMut{&TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "127.0.0.1", 1, 0, 1, 1, 0, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil}, false})
topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, false, false, Paginator{[]int{1}, 1, 1}} topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, QuickTools{false, false, false}, Paginator{[]int{1}, 1, 1}}
forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0) forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
forumPage := ForumPage{htitle("General Forum"), topicsList, forumItem, false, false, Paginator{[]int{1}, 1, 1}} forumPage := ForumPage{htitle("General Forum"), topicsList, forumItem, false, false, Paginator{[]int{1}, 1, 1}}
@ -538,8 +538,8 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri
t := TItemHold(make(map[string]TItem)) t := TItemHold(make(map[string]TItem))
topicsRow := &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 0, 1, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil} topicsRow := TopicsRowMut{&TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 0, 1, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil}, false}
t.AddStd("topics_topic", "c.TopicsRow", topicsRow) t.AddStd("topics_topic", "c.TopicsRowMut", topicsRow)
poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{ poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
PollOption{0, "Nothing"}, PollOption{0, "Nothing"},

View File

@ -94,6 +94,11 @@ type TopicUser struct {
Deletable bool Deletable bool
} }
type TopicsRowMut struct {
*TopicsRow
CanMod bool
}
// TODO: Embed TopicUser to simplify this structure and it's related logic? // TODO: Embed TopicUser to simplify this structure and it's related logic?
type TopicsRow struct { type TopicsRow struct {
ID int ID int
@ -151,11 +156,17 @@ type WsTopicsRow struct {
LastUser *WsJSONUser LastUser *WsJSONUser
ForumName string ForumName string
ForumLink string ForumLink string
CanMod bool
} }
// TODO: Can we get the client side to render the relative times instead? // TODO: Can we get the client side to render the relative times instead?
func (r *TopicsRow) WebSockets() *WsTopicsRow { func (r *TopicsRow) WebSockets() *WsTopicsRow {
return &WsTopicsRow{r.ID, r.Link, r.Title, r.CreatedBy, r.IsClosed, r.Sticky, r.CreatedAt, r.LastReplyAt, RelativeTime(r.LastReplyAt), r.LastReplyBy, r.LastReplyID, r.ParentID, r.ViewCount, r.PostCount, r.LikeCount, r.AttachCount, r.ClassName, r.Creator.WebSockets(), r.LastUser.WebSockets(), r.ForumName, r.ForumLink} return &WsTopicsRow{r.ID, r.Link, r.Title, r.CreatedBy, r.IsClosed, r.Sticky, r.CreatedAt, r.LastReplyAt, RelativeTime(r.LastReplyAt), r.LastReplyBy, r.LastReplyID, r.ParentID, r.ViewCount, r.PostCount, r.LikeCount, r.AttachCount, r.ClassName, r.Creator.WebSockets(), r.LastUser.WebSockets(), r.ForumName, r.ForumLink, false}
}
// TODO: Can we get the client side to render the relative times instead?
func (r *TopicsRow) WebSockets2(canMod bool) *WsTopicsRow {
return &WsTopicsRow{r.ID, r.Link, r.Title, r.CreatedBy, r.IsClosed, r.Sticky, r.CreatedAt, r.LastReplyAt, RelativeTime(r.LastReplyAt), r.LastReplyBy, r.LastReplyID, r.ParentID, r.ViewCount, r.PostCount, r.LikeCount, r.AttachCount, r.ClassName, r.Creator.WebSockets(), r.LastUser.WebSockets(), r.ForumName, r.ForumLink, canMod}
} }
// TODO: Stop relying on so many struct types? // TODO: Stop relying on so many struct types?

View File

@ -151,6 +151,7 @@ func wsTopicListTick(h *WsHubImpl) error {
} }
canSeeRenders := make(map[string][]byte) canSeeRenders := make(map[string][]byte)
canSeeLists := make(map[string][]*WsTopicsRow)
for name, canSee := range canSeeMap { for name, canSee := range canSeeMap {
topicList, forumList, _, err := TopicList.GetListByCanSee(canSee, 1, 0, nil) topicList, forumList, _, err := TopicList.GetListByCanSee(canSee, 1, 0, nil)
if err != nil { if err != nil {
@ -180,6 +181,7 @@ func wsTopicListTick(h *WsHubImpl) error {
for i, topicRow := range topicList { for i, topicRow := range topicList {
wsTopicList[i] = topicRow.WebSockets() wsTopicList[i] = topicRow.WebSockets()
} }
canSeeLists[name] = wsTopicList
outBytes, err := json.Marshal(&WsTopicList{wsTopicList, 0, tickStart.Unix()}) outBytes, err := json.Marshal(&WsTopicList{wsTopicList, 0, tickStart.Unix()})
if err != nil { if err != nil {
@ -191,17 +193,94 @@ func wsTopicListTick(h *WsHubImpl) error {
// TODO: Use MessagePack for additional speed? // TODO: Use MessagePack for additional speed?
//fmt.Println("writing to the clients") //fmt.Println("writing to the clients")
for _, wsUser := range currentWatchers { for _, wsUser := range currentWatchers {
group := groups[wsUser.User.Group] u := wsUser.User
group := groups[u.Group]
canSee := make([]byte, len(group.CanSee)) canSee := make([]byte, len(group.CanSee))
for i, item := range group.CanSee { for i, item := range group.CanSee {
canSee[i] = byte(item) canSee[i] = byte(item)
} }
sCanSee := string(canSee)
l := canSeeLists[sCanSee]
// TODO: Optimise this away for guests?
anyMod, anyLock, anyMove, allMod := false, false, false, true
var modSet map[int]int
if u.IsSuperAdmin {
anyMod = true
anyLock = true
anyMove = true
} else {
modSet = make(map[int]int, len(l))
for i, t := range l {
// TODO: Abstract this?
fp, err := FPStore.Get(t.ParentID, u.Group)
if err == ErrNoRows {
fp = BlankForumPerms()
} else if err != nil {
return err
}
var ccanMod, ccanLock, ccanMove bool
if fp.Overrides {
ccanLock = fp.CloseTopic
ccanMove = fp.MoveTopic
ccanMod = t.CreatedBy == u.ID || fp.DeleteTopic || ccanLock || ccanMove
} else {
ccanLock = u.Perms.CloseTopic
ccanMove = u.Perms.MoveTopic
ccanMod = t.CreatedBy == u.ID || u.Perms.DeleteTopic || ccanLock || ccanMove
}
if ccanLock {
anyLock = true
}
if ccanMove {
anyMove = true
}
if ccanMod {
anyMod = true
} else {
allMod = false
}
var v int
if ccanMod {
v = 1
}
modSet[i] = v
}
}
//fmt.Println("writing to user #", wsUser.User.ID) //fmt.Println("writing to user #", wsUser.User.ID)
outBytes := canSeeRenders[string(canSee)] outBytes := canSeeRenders[sCanSee]
//fmt.Println("outBytes: ", string(outBytes)) //fmt.Println("outBytes: ", string(outBytes))
err := wsUser.WriteToPageBytes(outBytes, "/topics/") //fmt.Println("outBytes[:len(outBytes)-1]: ", string(outBytes[:len(outBytes)-1]))
if err == ErrNoneOnPage { //e := wsUser.WriteToPageBytes(outBytes, "/topics/")
//e := wsUser.WriteToPageBytesMulti([][]byte{outBytes[:len(outBytes)-1], []byte(`,"mod":1}`)}, "/topics/")
var e error
if !anyMod {
e = wsUser.WriteToPageBytes(outBytes, "/topics/")
} else {
var lm []byte
if anyLock && anyMove {
lm = []byte(`,"lock":1,"move":1}`)
} else if anyLock {
lm = []byte(`,"lock":1}`)
} else if anyMove {
lm = []byte(`,"move":1}`)
} else {
lm = []byte("}")
}
if allMod {
e = wsUser.WriteToPageBytesMulti([][]byte{outBytes[:len(outBytes)-1], []byte(`,"mod":1`), lm}, "/topics/")
} else {
// TODO: Temporary and inefficient
mBytes, err := json.Marshal(modSet)
if err != nil {
return err
}
e = wsUser.WriteToPageBytesMulti([][]byte{outBytes[:len(outBytes)-1], []byte(`,"mod":`), mBytes, lm}, "/topics/")
}
}
if e == ErrNoneOnPage {
//fmt.Printf("werr for #%d: %s\n", wsUser.User.ID, err) //fmt.Printf("werr for #%d: %s\n", wsUser.User.ID, err)
wsUser.FinalizePage("/topics/", func() { wsUser.FinalizePage("/topics/", func() {
topicListMutex.Lock() topicListMutex.Lock()

View File

@ -221,8 +221,8 @@ function runWebSockets(resume=false) {
if(window.location.protocol == "https:") s = "s"; if(window.location.protocol == "https:") s = "s";
conn = new WebSocket("ws"+s+"://" + document.location.host + "/ws/"); conn = new WebSocket("ws"+s+"://" + document.location.host + "/ws/");
conn.onerror = err => { conn.onerror = e => {
console.log(err); console.log(e);
} }
// TODO: Sync alerts, topic list, etc. // TODO: Sync alerts, topic list, etc.
@ -297,11 +297,26 @@ function runWebSockets(resume=false) {
} else if("Topics" in data) { } else if("Topics" in data) {
console.log("topic in data"); console.log("topic in data");
console.log("data",data); console.log("data",data);
// TODO: Handle desyncs more gracefully?
// TODO: Send less unneccessary data?
let topic = data.Topics[0]; let topic = data.Topics[0];
if(topic===undefined){ if(topic===undefined){
console.log("empty topic list"); console.log("empty topic list");
return; return;
} }
if("mod" in data) {
topic.CanMod = data.mod==1 || data.mod[0]==1;
if(data.lock==1) {
$(".val_lock").each(function(){
this.classList.remove("auto_hide");
});
}
if(data.move==1) {
$(".val_move").each(function(){
this.classList.remove("auto_hide");
});
}
}
// TODO: Fix the data race where the function hasn't been loaded yet // TODO: Fix the data race where the function hasn't been loaded yet
let renTopic = Tmpl_topics_topic(topic); let renTopic = Tmpl_topics_topic(topic);
$(".topic_row[data-tid='"+topic.ID+"']").addClass("ajax_topic_dupe"); $(".topic_row[data-tid='"+topic.ID+"']").addClass("ajax_topic_dupe");
@ -313,7 +328,7 @@ function runWebSockets(resume=false) {
moreTopicCount++; moreTopicCount++;
let blocks = document.getElementsByClassName("more_topic_block_initial"); let blocks = document.getElementsByClassName("more_topic_block_initial");
for(let i=0; i < blocks.length; i++) { for(let i=0; i<blocks.length; i++) {
let block = blocks[i]; let block = blocks[i];
block.classList.remove("more_topic_block_initial"); block.classList.remove("more_topic_block_initial");
block.classList.add("more_topic_block_active"); block.classList.add("more_topic_block_active");
@ -347,7 +362,7 @@ function runWebSockets(resume=false) {
// TODO: Surely, there's a prettier and more elegant way of doing this? // TODO: Surely, there's a prettier and more elegant way of doing this?
function getExt(name) { function getExt(name) {
if(!name.indexOf('.' > -1)) throw("This file doesn't have an extension"); if(!name.indexOf('.') > -1) throw("This file doesn't have an extension");
return name.split('.').pop(); return name.split('.').pop();
} }
@ -439,9 +454,9 @@ function mainInit(){
ev.preventDefault(); ev.preventDefault();
let blocks = document.getElementsByClassName("more_topic_block_active"); let blocks = document.getElementsByClassName("more_topic_block_active");
for(let i=0; i<blocks.length; i++) { for(let i=0; i<blocks.length; i++) {
let block = blocks[i]; let bl = blocks[i];
block.classList.remove("more_topic_block_active"); bl.classList.remove("more_topic_block_active");
block.classList.add("more_topic_block_initial"); bl.classList.add("more_topic_block_initial");
} }
$(".ajax_topic_dupe").fadeOut("slow", function(){ $(".ajax_topic_dupe").fadeOut("slow", function(){
$(this).remove(); $(this).remove();
@ -646,6 +661,7 @@ function mainInit(){
}); });
bindPage(); bindPage();
runInitHook("after_init_bind_page");
$(".edit_field").click(function(ev) { $(".edit_field").click(function(ev) {
ev.preventDefault(); ev.preventDefault();
@ -927,6 +943,7 @@ function bindPage() {
}); });
bindTopic(); bindTopic();
runHook("end_bind_page")
} }
function unbindPage() { function unbindPage() {

View File

@ -151,16 +151,16 @@ var imageExts = ["png","jpg","jpe","jpeg","jif","jfi","jfif","svg","bmp","gif","
addInitHook("start_init", () => { addInitHook("start_init", () => {
addHook("end_bind_topic", () => { addHook("end_bind_topic", () => {
let changeListener = (files,handler) => {
if(files!=null) {
files.removeEventListener("change", handler, false);
files.addEventListener("change", handler, false);
}
};
let uploadFiles = document.getElementById("upload_files"); let uploadFiles = document.getElementById("upload_files");
if(uploadFiles!=null) { changeListener(uploadFiles,uploadAttachHandler);
uploadFiles.removeEventListener("change", uploadAttachHandler, false);
uploadFiles.addEventListener("change", uploadAttachHandler, false);
}
let uploadFilesOp = document.getElementById("upload_files_op"); let uploadFilesOp = document.getElementById("upload_files_op");
if(uploadFilesOp!=null) { changeListener(uploadFilesOp,uploadAttachHandler2);
uploadFilesOp.removeEventListener("change", uploadAttachHandler2, false);
uploadFilesOp.addEventListener("change", uploadAttachHandler2, false);
}
bindAttachManager(); bindAttachManager();
function bindAttachItems() { function bindAttachItems() {
@ -212,73 +212,6 @@ var imageExts = ["png","jpg","jpe","jpeg","jif","jfi","jfif","svg","bmp","gif","
bindAttachItems(); bindAttachItems();
bindAttachManager(); bindAttachManager();
}); });
$(".moderate_link").unbind("click");
$(".mod_floater_submit").unbind("click");
$(".moderate_link").click(ev => {
ev.preventDefault();
$(".pre_opt").removeClass("auto_hide");
$(".moderate_link").addClass("moderate_open");
$("#topicsItemList,#forumItemList").addClass("topics_moderate");
$(".topic_row").each(function(){
$(this).click(function(){
selectedTopics.push(parseInt($(this).attr("data-tid"),10));
if(selectedTopics.length==1) {
var msg = phraseBox["topic_list"]["topic_list.what_to_do_single"];
} else {
var msg = "What do you want to do with these "+selectedTopics.length+" topics?";
}
$(".mod_floater_head span").html(msg);
$(this).addClass("topic_selected");
$(".mod_floater").removeClass("auto_hide");
});
});
let bulkActionSender = (action,selectedTopics,fragBit) => {
$.ajax({
url: "/topic/"+action+"/submit/"+fragBit+"?s="+me.User.S,
type: "POST",
data: JSON.stringify(selectedTopics),
contentType: "application/json",
error: ajaxError,
success: () => {
window.location.reload();
}
});
};
// TODO: Should we unbind this here to avoid binding multiple listeners to this accidentally?
$(".mod_floater_submit").click(function(ev){
ev.preventDefault();
let selectNode = this.form.querySelector(".mod_floater_options");
let optionNode = selectNode.options[selectNode.selectedIndex];
let action = optionNode.getAttribute("value");
// Handle these specially
switch(action) {
case "move":
console.log("move action");
let modTopicMover = $("#mod_topic_mover");
$("#mod_topic_mover").removeClass("auto_hide");
$("#mod_topic_mover .pane_row").click(function(){
modTopicMover.find(".pane_row").removeClass("pane_selected");
let fid = this.getAttribute("data-fid");
if(fid==null) return;
this.classList.add("pane_selected");
console.log("fid",fid);
forumToMoveTo = fid;
$("#mover_submit").unbind("click");
$("#mover_submit").click(ev => {
ev.preventDefault();
bulkActionSender("move",selectedTopics,forumToMoveTo);
});
});
return;
}
bulkActionSender(action,selectedTopics,"");
});
});
function addPollInput() { function addPollInput() {
console.log("clicked on pollinputinput"); console.log("clicked on pollinputinput");
@ -306,4 +239,74 @@ var imageExts = ["png","jpg","jpe","jpeg","jif","jfi","jfif","svg","bmp","gif","
}); });
}); });
}); });
//addInitHook("after_init_bind_page", () => {
addHook("end_bind_page", () => {
$(".moderate_link").unbind("click");
$(".mod_floater_submit").unbind("click");
$(".moderate_link").click(ev => {
ev.preventDefault();
$(".pre_opt").removeClass("auto_hide");
$(".moderate_link").addClass("moderate_open");
$("#topicsItemList,#forumItemList").addClass("topics_moderate");
$(".topic_row").each(function(){
$(this).click(function(){
if(!this.classList.contains("can_mod") || this.classList.contains("topic_selected")) return;
selectedTopics.push(parseInt($(this).attr("data-tid"),10));
if(selectedTopics.length==1) {
var msg = phraseBox["topic_list"]["topic_list.what_to_do_single"];
} else {
var msg = "What do you want to do with these "+selectedTopics.length+" topics?";
}
$(".mod_floater_head span").html(msg);
$(this).addClass("topic_selected");
$(".mod_floater").removeClass("auto_hide");
});
});
let bulkActionSender = (action,selectedTopics,fragBit) => {
$.ajax({
url: "/topic/"+action+"/submit/"+fragBit+"?s="+me.User.S,
type: "POST",
data: JSON.stringify(selectedTopics),
contentType: "application/json",
error: ajaxError,
success: () => {
window.location.reload();
}
});
};
// TODO: Should we unbind this here to avoid binding multiple listeners to this accidentally?
$(".mod_floater_submit").click(function(ev){
ev.preventDefault();
let selectNode = this.form.querySelector(".mod_floater_options");
let optionNode = selectNode.options[selectNode.selectedIndex];
let action = optionNode.getAttribute("value");
// Handle these specially
switch(action) {
case "move":
console.log("move action");
let modTopicMover = $("#mod_topic_mover");
$("#mod_topic_mover").removeClass("auto_hide");
$("#mod_topic_mover .pane_row").click(function(){
modTopicMover.find(".pane_row").removeClass("pane_selected");
let fid = this.getAttribute("data-fid");
if(fid==null) return;
this.classList.add("pane_selected");
console.log("fid",fid);
forumToMoveTo = fid;
$("#mover_submit").unbind("click");
$("#mover_submit").click(ev => {
ev.preventDefault();
bulkActionSender("move",selectedTopics,forumToMoveTo);
});
});
return;
}
bulkActionSender(action,selectedTopics,"");
});
});
});
})() })()

View File

@ -54,8 +54,14 @@ func ViewForum(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header, s
return nil return nil
} }
topicList2 := make([]c.TopicsRowMut, len(topicList))
canMod := u.Perms.CloseTopic || u.Perms.MoveTopic
for i, t := range topicList {
topicList2[i] = c.TopicsRowMut{t, t.CreatedBy == u.ID || canMod}
}
//pageList := c.Paginate(page, lastPage, 5) //pageList := c.Paginate(page, lastPage, 5)
pi := c.ForumPage{h, topicList, forum, u.Perms.CloseTopic, u.Perms.MoveTopic, pagi} pi := c.ForumPage{h, topicList2, forum, u.Perms.CloseTopic, u.Perms.MoveTopic, pagi}
tmpl := forum.Tmpl tmpl := forum.Tmpl
if tmpl == "" { if tmpl == "" {
ferr = renderTemplate("forum", w, r, h, pi) ferr = renderTemplate("forum", w, r, h, pi)

View File

@ -8,12 +8,12 @@ import (
"github.com/Azareal/Gosora/common/phrases" "github.com/Azareal/Gosora/common/phrases"
) )
func ForumList(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header) c.RouteError { func ForumList(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError {
/*skip, rerr := h.Hooks.VhookSkippable("route_forum_list_start", w, r, user, h) /*skip, rerr := h.Hooks.VhookSkippable("route_forum_list_start", w, r, u, h)
if skip || rerr != nil { if skip || rerr != nil {
return rerr return rerr
}*/ }*/
skip, rerr := c.H_route_forum_list_start_hook(h.Hooks, w, r, user, h) skip, rerr := c.H_route_forum_list_start_hook(h.Hooks, w, r, u, h)
if skip || rerr != nil { if skip || rerr != nil {
return rerr return rerr
} }
@ -24,18 +24,18 @@ func ForumList(w http.ResponseWriter, r *http.Request, user *c.User, h *c.Header
var err error var err error
var canSee []int var canSee []int
if user.IsSuperAdmin { if u.IsSuperAdmin {
canSee, err = c.Forums.GetAllVisibleIDs() canSee, err = c.Forums.GetAllVisibleIDs()
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
} else { } else {
group, err := c.Groups.Get(user.Group) g, err := c.Groups.Get(u.Group)
if err != nil { if err != nil {
log.Printf("Group #%d doesn't exist despite being used by c.User #%d", user.Group, user.ID) log.Printf("Group #%d doesn't exist despite being used by c.User #%d", u.Group, u.ID)
return c.LocalError("Something weird happened", w, r, user) return c.LocalError("Something weird happened", w, r, u)
} }
canSee = group.CanSee canSee = g.CanSee
} }
var forumList []c.Forum var forumList []c.Forum

View File

@ -11,13 +11,13 @@ import (
p "github.com/Azareal/Gosora/common/phrases" p "github.com/Azareal/Gosora/common/phrases"
) )
func Forums(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError { func Forums(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "forums", "forums") basePage, ferr := buildBasePage(w, r, u, "forums", "forums")
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageForums { if !u.Perms.ManageForums {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
basePage.Header.AddScript("Sortable-1.4.0/Sortable.min.js") basePage.Header.AddScript("Sortable-1.4.0/Sortable.min.js")
basePage.Header.AddScriptAsync("panel_forums.js") basePage.Header.AddScriptAsync("panel_forums.js")
@ -52,26 +52,26 @@ func Forums(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_forums", &pi}) return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_forums", &pi})
} }
func ForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError { func ForumsCreateSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageForums { if !u.Perms.ManageForums {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
fname := r.PostFormValue("name") name := r.PostFormValue("name")
fdesc := r.PostFormValue("desc") desc := r.PostFormValue("desc")
fpreset := c.StripInvalidPreset(r.PostFormValue("preset")) preset := c.StripInvalidPreset(r.PostFormValue("preset"))
factive := r.PostFormValue("active") factive := r.PostFormValue("active")
active := (factive == "on" || factive == "1") active := (factive == "on" || factive == "1")
fid, err := c.Forums.Create(fname, fdesc, active, fpreset) fid, err := c.Forums.Create(name, desc, active, preset)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("create", fid, "forum", user.GetIP(), user.ID) err = c.AdminLogs.Create("create", fid, "forum", u.GetIP(), u.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -81,22 +81,22 @@ func ForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.
} }
// TODO: Revamp this // TODO: Revamp this
func ForumsDelete(w http.ResponseWriter, r *http.Request, user *c.User, sfid string) c.RouteError { func ForumsDelete(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "delete_forum", "forums") basePage, ferr := buildBasePage(w, r, u, "delete_forum", "forums")
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageForums { if !u.Perms.ManageForums {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
fid, err := strconv.Atoi(sfid) fid, err := strconv.Atoi(sfid)
if err != nil { if err != nil {
return c.LocalError("The provided Forum ID is not a valid number.", w, r, user) return c.LocalError("The provided Forum ID is not a valid number.", w, r, u)
} }
forum, err := c.Forums.Get(fid) forum, err := c.Forums.Get(fid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("The forum you're trying to delete doesn't exist.", w, r, user) return c.LocalError("The forum you're trying to delete doesn't exist.", w, r, u)
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -105,32 +105,32 @@ func ForumsDelete(w http.ResponseWriter, r *http.Request, user *c.User, sfid str
youSure := c.AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirmMsg} youSure := c.AreYouSure{"/panel/forums/delete/submit/" + strconv.Itoa(fid), confirmMsg}
pi := c.PanelPage{basePage, tList, youSure} pi := c.PanelPage{basePage, tList, youSure}
if c.RunPreRenderHook("pre_render_panel_delete_forum", w, r, user, &pi) { if c.RunPreRenderHook("pre_render_panel_delete_forum", w, r, u, &pi) {
return nil return nil
} }
return renderTemplate("panel_are_you_sure", w, r, basePage.Header, &pi) return renderTemplate("panel_are_you_sure", w, r, basePage.Header, &pi)
} }
func ForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sfid string) c.RouteError { func ForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageForums { if !u.Perms.ManageForums {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
fid, err := strconv.Atoi(sfid) fid, err := strconv.Atoi(sfid)
if err != nil { if err != nil {
return c.LocalError("The provided Forum ID is not a valid number.", w, r, user) return c.LocalError("The provided Forum ID is not a valid number.", w, r, u)
} }
err = c.Forums.Delete(fid) err = c.Forums.Delete(fid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("The forum you're trying to delete doesn't exist.", w, r, user) return c.LocalError("The forum you're trying to delete doesn't exist.", w, r, u)
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
err = c.AdminLogs.Create("delete", fid, "forum", user.GetIP(), user.ID) err = c.AdminLogs.Create("delete", fid, "forum", u.GetIP(), u.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -139,14 +139,14 @@ func ForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sf
return nil return nil
} }
func ForumsOrderSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError { func ForumsOrderSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
if !user.Perms.ManageForums { if !u.Perms.ManageForums {
return c.NoPermissionsJSQ(w, r, user, js) return c.NoPermissionsJSQ(w, r, u, js)
} }
sitems := strings.TrimSuffix(strings.TrimPrefix(r.PostFormValue("items"), "{"), "}") sitems := strings.TrimSuffix(strings.TrimPrefix(r.PostFormValue("items"), "{"), "}")
//fmt.Printf("sitems: %+v\n", sitems) //fmt.Printf("sitems: %+v\n", sitems)
@ -155,13 +155,13 @@ func ForumsOrderSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.R
for index, sfid := range strings.Split(sitems, ",") { for index, sfid := range strings.Split(sitems, ",") {
fid, err := strconv.Atoi(sfid) fid, err := strconv.Atoi(sfid)
if err != nil { if err != nil {
return c.LocalErrorJSQ("Invalid integer in forum list", w, r, user, js) return c.LocalErrorJSQ("Invalid integer in forum list", w, r, u, js)
} }
updateMap[fid] = index updateMap[fid] = index
} }
c.Forums.UpdateOrder(updateMap) c.Forums.UpdateOrder(updateMap)
err := c.AdminLogs.Create("reorder", 0, "forum", user.GetIP(), user.ID) err := c.AdminLogs.Create("reorder", 0, "forum", u.GetIP(), u.ID)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
@ -169,13 +169,13 @@ func ForumsOrderSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.R
return successRedirect("/panel/forums/", w, r, js) return successRedirect("/panel/forums/", w, r, js)
} }
func ForumsEdit(w http.ResponseWriter, r *http.Request, user *c.User, sfid string) c.RouteError { func ForumsEdit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "edit_forum", "forums") basePage, ferr := buildBasePage(w, r, u, "edit_forum", "forums")
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageForums { if !u.Perms.ManageForums {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
fid, err := strconv.Atoi(sfid) fid, err := strconv.Atoi(sfid)
@ -186,7 +186,7 @@ func ForumsEdit(w http.ResponseWriter, r *http.Request, user *c.User, sfid strin
forum, err := c.Forums.Get(fid) forum, err := c.Forums.Get(fid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, user) return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, u)
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -222,30 +222,30 @@ func ForumsEdit(w http.ResponseWriter, r *http.Request, user *c.User, sfid strin
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_forum_edit", &pi}) return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_forum_edit", &pi})
} }
func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sfid string) c.RouteError { func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageForums { if !u.Perms.ManageForums {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
fid, err := strconv.Atoi(sfid) fid, err := strconv.Atoi(sfid)
if err != nil { if err != nil {
return c.LocalErrorJSQ("The provided Forum ID is not a valid number.", w, r, user, js) return c.LocalErrorJSQ("The provided Forum ID is not a valid number.", w, r, u, js)
} }
forum, err := c.Forums.Get(fid) forum, err := c.Forums.Get(fid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalErrorJSQ("The forum you're trying to edit doesn't exist.", w, r, user, js) return c.LocalErrorJSQ("The forum you're trying to edit doesn't exist.", w, r, u, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
fname := r.PostFormValue("forum_name") name := r.PostFormValue("forum_name")
fdesc := r.PostFormValue("forum_desc") desc := r.PostFormValue("forum_desc")
fpreset := c.StripInvalidPreset(r.PostFormValue("forum_preset")) preset := c.StripInvalidPreset(r.PostFormValue("forum_preset"))
factive := r.PostFormValue("forum_active") factive := r.PostFormValue("forum_active")
active := false active := false
@ -255,11 +255,11 @@ func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sfid
active = true active = true
} }
err = forum.Update(fname, fdesc, active, fpreset) err = forum.Update(name, desc, active, preset)
if err != nil { if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
err = c.AdminLogs.Create("edit", fid, "forum", user.GetIP(), user.ID) err = c.AdminLogs.Create("edit", fid, "forum", u.GetIP(), u.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -268,29 +268,28 @@ func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sfid
return successRedirect("/panel/forums/", w, r, js) return successRedirect("/panel/forums/", w, r, js)
} }
func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sfid string) c.RouteError { func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageForums { if !u.Perms.ManageForums {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
fid, err := strconv.Atoi(sfid) fid, err := strconv.Atoi(sfid)
if err != nil { if err != nil {
return c.LocalErrorJSQ("The provided Forum ID is not a valid number.", w, r, user, js) return c.LocalErrorJSQ("The provided Forum ID is not a valid number.", w, r, u, js)
} }
gid, err := strconv.Atoi(r.PostFormValue("gid")) gid, err := strconv.Atoi(r.PostFormValue("gid"))
if err != nil { if err != nil {
return c.LocalErrorJSQ("Invalid Group ID", w, r, user, js) return c.LocalErrorJSQ("Invalid Group ID", w, r, u, js)
} }
forum, err := c.Forums.Get(fid) forum, err := c.Forums.Get(fid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalErrorJSQ("This forum doesn't exist", w, r, user, js) return c.LocalErrorJSQ("This forum doesn't exist", w, r, u, js)
} else if err != nil { } else if err != nil {
return c.InternalErrorJSQ(err, w, r, js) return c.InternalErrorJSQ(err, w, r, js)
} }
@ -298,9 +297,9 @@ func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, user *c.User,
permPreset := c.StripInvalidGroupForumPreset(r.PostFormValue("perm_preset")) permPreset := c.StripInvalidGroupForumPreset(r.PostFormValue("perm_preset"))
err = forum.SetPreset(permPreset, gid) err = forum.SetPreset(permPreset, gid)
if err != nil { if err != nil {
return c.LocalErrorJSQ(err.Error(), w, r, user, js) return c.LocalErrorJSQ(err.Error(), w, r, u, js)
} }
err = c.AdminLogs.Create("edit", fid, "forum", user.GetIP(), user.ID) err = c.AdminLogs.Create("edit", fid, "forum", u.GetIP(), u.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -328,23 +327,23 @@ func forumPermsExtractDash(paramList string) (fid, gid int, err error) {
return fid, gid, err return fid, gid, err
} }
func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, user *c.User, paramList string) c.RouteError { func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, u *c.User, paramList string) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "edit_forum", "forums") basePage, ferr := buildBasePage(w, r, u, "edit_forum", "forums")
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageForums { if !u.Perms.ManageForums {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
fid, gid, err := forumPermsExtractDash(paramList) fid, gid, err := forumPermsExtractDash(paramList)
if err != nil { if err != nil {
return c.LocalError(err.Error(), w, r, user) return c.LocalError(err.Error(), w, r, u)
} }
f, err := c.Forums.Get(fid) f, err := c.Forums.Get(fid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, user) return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, u)
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -386,24 +385,24 @@ func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, user *c.User
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_forum_edit_perms", &pi}) return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "", "", "panel_forum_edit_perms", &pi})
} }
func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, user *c.User, paramList string) c.RouteError { func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, u *c.User, paramList string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user) _, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil { if ferr != nil {
return ferr return ferr
} }
if !user.Perms.ManageForums { if !u.Perms.ManageForums {
return c.NoPermissions(w, r, user) return c.NoPermissions(w, r, u)
} }
js := r.PostFormValue("js") == "1" js := r.PostFormValue("js") == "1"
fid, gid, err := forumPermsExtractDash(paramList) fid, gid, err := forumPermsExtractDash(paramList)
if err != nil { if err != nil {
return c.LocalError(err.Error(), w, r, user) return c.LocalError(err.Error(), w, r, u)
} }
forum, err := c.Forums.Get(fid) forum, err := c.Forums.Get(fid)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, user) return c.LocalError("The forum you're trying to edit doesn't exist.", w, r, u)
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -435,9 +434,9 @@ func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, user *
err = forum.SetPerms(&fp, "custom", gid) err = forum.SetPerms(&fp, "custom", gid)
if err != nil { if err != nil {
return c.LocalErrorJSQ(err.Error(), w, r, user, js) return c.LocalErrorJSQ(err.Error(), w, r, u, js)
} }
err = c.AdminLogs.Create("edit", fid, "forum", user.GetIP(), user.ID) err = c.AdminLogs.Create("edit", fid, "forum", u.GetIP(), u.ID)
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }

View File

@ -19,6 +19,21 @@ func wsTopicList(topicList []*c.TopicsRow, lastPage int) *c.WsTopicList {
return &c.WsTopicList{wsTopicList, lastPage, 0} return &c.WsTopicList{wsTopicList, lastPage, 0}
} }
func wsTopicList2(topicList []*c.TopicsRow, u *c.User, fps map[int]c.QuickTools, lastPage int) *c.WsTopicList {
wsTopicList := make([]*c.WsTopicsRow, len(topicList))
for i, t := range topicList {
var canMod bool
if fps == nil {
canMod = true
} else {
quickTools := fps[t.ParentID]
canMod = t.CreatedBy == u.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove
}
wsTopicList[i] = t.WebSockets2(canMod)
}
return &c.WsTopicList{wsTopicList, lastPage, 0}
}
func TopicList(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError { func TopicList(w http.ResponseWriter, r *http.Request, u *c.User, h *c.Header) c.RouteError {
/*skip, rerr := h.Hooks.VhookSkippable("route_topic_list_start", w, r, u, h) /*skip, rerr := h.Hooks.VhookSkippable("route_topic_list_start", w, r, u, h)
if skip || rerr != nil { if skip || rerr != nil {
@ -79,7 +94,7 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
var topicList []*c.TopicsRow var topicList []*c.TopicsRow
var forumList []c.Forum var forumList []c.Forum
var pagi c.Paginator var pagi c.Paginator
var canLock, ccanLock, canMove, ccanMove bool var canDelete, ccanDelete, canLock, ccanLock, canMove, ccanMove bool
q := r.FormValue("q") q := r.FormValue("q")
if q != "" && c.RepliesSearch != nil { if q != "" && c.RepliesSearch != nil {
var canSee []int var canSee []int
@ -152,34 +167,45 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
// TODO: De-dupe this logic in common/topic_list.go? // TODO: De-dupe this logic in common/topic_list.go?
//var sb strings.Builder //var sb strings.Builder
fps := make(map[int]c.QuickTools)
for _, t := range topicList { for _, t := range topicList {
//c.BuildTopicURLSb(&sb, c.NameToSlug(t.Title), t.ID) //c.BuildTopicURLSb(&sb, c.NameToSlug(t.Title), t.ID)
//t.Link = sb.String() //t.Link = sb.String()
//sb.Reset() //sb.Reset()
t.Link = c.BuildTopicURL(c.NameToSlug(t.Title), t.ID) t.Link = c.BuildTopicURL(c.NameToSlug(t.Title), t.ID)
// TODO: Pass forum to something like t.Forum and use that instead of these two properties? Could be more flexible. // TODO: Pass forum to something like t.Forum and use that instead of these two properties? Could be more flexible.
forum := c.Forums.DirtyGet(t.ParentID) f := c.Forums.DirtyGet(t.ParentID)
t.ForumName = forum.Name t.ForumName = f.Name
t.ForumLink = forum.Link t.ForumLink = f.Link
fp, err := c.FPStore.Get(forum.ID, user.Group) _, ok := fps[f.ID]
if err == c.ErrNoRows { if !ok {
fp = c.BlankForumPerms() // TODO: Abstract this?
} else if err != nil { fp, err := c.FPStore.Get(f.ID, user.Group)
return c.InternalError(err, w, r) if err == c.ErrNoRows {
} fp = c.BlankForumPerms()
if fp.Overrides && !user.IsSuperAdmin { } else if err != nil {
ccanLock = fp.CloseTopic return c.InternalError(err, w, r)
ccanMove = fp.MoveTopic }
} else { if fp.Overrides && !user.IsSuperAdmin {
ccanLock = user.Perms.CloseTopic ccanDelete = fp.DeleteTopic
ccanMove = user.Perms.MoveTopic ccanLock = fp.CloseTopic
} ccanMove = fp.MoveTopic
if ccanLock { } else {
canLock = true ccanDelete = user.Perms.DeleteTopic
} ccanLock = user.Perms.CloseTopic
if ccanMove { ccanMove = user.Perms.MoveTopic
canMove = true }
if ccanDelete {
canDelete = true
}
if ccanLock {
canLock = true
}
if ccanMove {
canMove = true
}
fps[f.ID] = c.QuickTools{ccanDelete, ccanLock, ccanMove}
} }
// TODO: Create a specialised function with a bit less overhead for getting the last page for a post count // TODO: Create a specialised function with a bit less overhead for getting the last page for a post count
@ -192,7 +218,7 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
// TODO: Reduce the amount of boilerplate here // TODO: Reduce the amount of boilerplate here
if r.FormValue("js") == "1" { if r.FormValue("js") == "1" {
outBytes, err := wsTopicList(topicList, pagi.LastPage).MarshalJSON() outBytes, err := wsTopicList2(topicList, user, fps, pagi.LastPage).MarshalJSON()
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -200,37 +226,57 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
return nil return nil
} }
topicList2 := make([]c.TopicsRowMut, len(topicList))
for i, t := range topicList {
var canMod bool
if fps == nil {
canMod = true
} else {
quickTools := fps[t.ParentID]
canMod = t.CreatedBy == user.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove
}
topicList2[i] = c.TopicsRowMut{t, canMod}
}
h.Title = phrases.GetTitlePhrase("topics_search") h.Title = phrases.GetTitlePhrase("topics_search")
pi := c.TopicListPage{h, topicList, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, canLock, canMove, pagi} pi := c.TopicListPage{h, topicList2, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, c.QuickTools{canDelete, canLock, canMove}, pagi}
return renderTemplate("topics", w, r, h, pi) return renderTemplate("topics", w, r, h, pi)
} }
// TODO: Pass a struct back rather than passing back so many variables // TODO: Pass a struct back rather than passing back so many variables
var fps map[int]c.QuickTools
if user.IsSuperAdmin { if user.IsSuperAdmin {
topicList, forumList, pagi, err = c.TopicList.GetList(page, tsorder, fids) topicList, forumList, pagi, err = c.TopicList.GetList(page, tsorder, fids)
canLock, canMove = true, true canLock, canMove = true, true
} else { } else {
topicList, forumList, pagi, err = c.TopicList.GetListByGroup(group, page, tsorder, fids) topicList, forumList, pagi, err = c.TopicList.GetListByGroup(group, page, tsorder, fids)
for _, forum := range forumList { fps = make(map[int]c.QuickTools)
fp, err := c.FPStore.Get(forum.ID, user.Group) for _, f := range forumList {
fp, err := c.FPStore.Get(f.ID, user.Group)
if err == c.ErrNoRows { if err == c.ErrNoRows {
fp = c.BlankForumPerms() fp = c.BlankForumPerms()
} else if err != nil { } else if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
if fp.Overrides { if fp.Overrides {
ccanDelete = fp.DeleteTopic
ccanLock = fp.CloseTopic ccanLock = fp.CloseTopic
ccanMove = fp.MoveTopic ccanMove = fp.MoveTopic
} else { } else {
ccanDelete = user.Perms.DeleteTopic
ccanLock = user.Perms.CloseTopic ccanLock = user.Perms.CloseTopic
ccanMove = user.Perms.MoveTopic ccanMove = user.Perms.MoveTopic
} }
if ccanDelete {
canDelete = true
}
if ccanLock { if ccanLock {
canLock = true canLock = true
} }
if ccanMove { if ccanMove {
canMove = true canMove = true
} }
fps[f.ID] = c.QuickTools{ccanDelete, ccanLock, ccanMove}
} }
} }
if err != nil { if err != nil {
@ -239,7 +285,7 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
// TODO: Reduce the amount of boilerplate here // TODO: Reduce the amount of boilerplate here
if r.FormValue("js") == "1" { if r.FormValue("js") == "1" {
outBytes, err := wsTopicList(topicList, pagi.LastPage).MarshalJSON() outBytes, err := wsTopicList2(topicList, user, fps, pagi.LastPage).MarshalJSON()
if err != nil { if err != nil {
return c.InternalError(err, w, r) return c.InternalError(err, w, r)
} }
@ -247,7 +293,19 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
return nil return nil
} }
pi := c.TopicListPage{h, topicList, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, canLock, canMove, pagi} topicList2 := make([]c.TopicsRowMut, len(topicList))
for i, t := range topicList {
var canMod bool
if fps == nil {
canMod = true
} else {
quickTools := fps[t.ParentID]
canMod = t.CreatedBy == user.ID || quickTools.CanDelete || quickTools.CanLock || quickTools.CanMove
}
topicList2[i] = c.TopicsRowMut{t, canMod}
}
pi := c.TopicListPage{h, topicList2, forumList, c.Config.DefaultForum, c.TopicListSort{torder, false}, c.QuickTools{canDelete, canLock, canMove}, pagi}
if r.FormValue("i") == "1" { if r.FormValue("i") == "1" {
return renderTemplate("topics_mini", w, r, h, pi) return renderTemplate("topics_mini", w, r, h, pi)
} }

View File

@ -41,6 +41,9 @@ if %errorlevel% neq 0 (
exit /b %errorlevel% exit /b %errorlevel%
) )
echo Generating the JSON handlers
easyjson -pkg common
echo Building the hook generator echo Building the hook generator
go build -tags hookgen -ldflags="-s -w" "./cmd/hook_gen" go build -tags hookgen -ldflags="-s -w" "./cmd/hook_gen"
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
@ -54,9 +57,6 @@ if %errorlevel% neq 0 (
exit /b %errorlevel% exit /b %errorlevel%
) )
echo Generating the JSON handlers
easyjson -pkg common
echo Building the query generator echo Building the query generator
go build -ldflags="-s -w" "./cmd/query_gen" go build -ldflags="-s -w" "./cmd/query_gen"
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (

View File

@ -41,6 +41,9 @@ if %errorlevel% neq 0 (
exit /b %errorlevel% exit /b %errorlevel%
) )
echo Generating the JSON handlers
easyjson -pkg common
echo Building the hook generator echo Building the hook generator
go build -tags hookgen -ldflags="-s -w" "./cmd/hook_gen" go build -tags hookgen -ldflags="-s -w" "./cmd/hook_gen"
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
@ -54,9 +57,6 @@ if %errorlevel% neq 0 (
exit /b %errorlevel% exit /b %errorlevel%
) )
echo Generating the JSON handlers
easyjson -pkg common
echo Building the query generator echo Building the query generator
go build -ldflags="-s -w" "./cmd/query_gen" go build -ldflags="-s -w" "./cmd/query_gen"
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (

View File

@ -41,6 +41,9 @@ if %errorlevel% neq 0 (
exit /b %errorlevel% exit /b %errorlevel%
) )
echo Generating the JSON handlers
easyjson -pkg common
echo Building the hook generator echo Building the hook generator
go build -tags hookgen -ldflags="-s -w" "./cmd/hook_gen" go build -tags hookgen -ldflags="-s -w" "./cmd/hook_gen"
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
@ -54,9 +57,6 @@ if %errorlevel% neq 0 (
exit /b %errorlevel% exit /b %errorlevel%
) )
echo Generating the JSON handlers
easyjson -pkg common
echo Building the query generator echo Building the query generator
go build -ldflags="-s -w" "./cmd/query_gen" go build -ldflags="-s -w" "./cmd/query_gen"
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (

View File

@ -41,6 +41,9 @@ if %errorlevel% neq 0 (
exit /b %errorlevel% exit /b %errorlevel%
) )
echo Generating the JSON handlers
easyjson -pkg common
echo Building the hook generator echo Building the hook generator
go build -tags hookgen -ldflags="-s -w" "./cmd/hook_gen" go build -tags hookgen -ldflags="-s -w" "./cmd/hook_gen"
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
@ -54,9 +57,6 @@ if %errorlevel% neq 0 (
exit /b %errorlevel% exit /b %errorlevel%
) )
echo Generating the JSON handlers
easyjson -pkg common
echo Building the query generator echo Building the query generator
go build -ldflags="-s -w" "./cmd/query_gen" go build -ldflags="-s -w" "./cmd/query_gen"
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (

View File

@ -47,7 +47,7 @@
{{end}} {{end}}
{{end}} {{end}}
<div id="forum_topic_list"class="rowblock topic_list single_forum"aria-label="{{lang "forum_list_aria"}}"> <div id="forum_topic_list"class="rowblock topic_list single_forum"aria-label="{{lang "forum_list_aria"}}">
{{range .ItemList}}<div class="topic_row{{if .Sticky}} topic_sticky{{else if .IsClosed}} topic_closed{{end}}"data-tid="{{.ID}}"> {{range .ItemList}}<div class="topic_row{{if .Sticky}} topic_sticky{{else if .IsClosed}} topic_closed{{end}}{{if .CanMod}} can_mod{{end}}"data-tid="{{.ID}}">
<div class="rowitem topic_left passive datarow"> <div class="rowitem topic_left passive datarow">
<span class="selector"></span> <span class="selector"></span>
<a href="{{.Creator.Link}}"><img src="{{.Creator.MicroAvatar}}"height=64 alt="Avatar"title="{{.Creator.Name}}'s Avatar"aria-hidden="true"></a> <a href="{{.Creator.Link}}"><img src="{{.Creator.MicroAvatar}}"height=64 alt="Avatar"title="{{.Creator.Name}}'s Avatar"aria-hidden="true"></a>

View File

@ -1,13 +1,13 @@
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_pages_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_pages_head"}}</h1></div>
</div> </div>
<div id="panel_pages" class="colstack_item rowlist"> <div id="panel_pages"class="colstack_item rowlist">
{{range .ItemList}} {{range .ItemList}}
<div class="rowitem panel_compactrow"> <div class="rowitem panel_compactrow">
<a href="/panel/pages/edit/{{.ID}}" class="panel_upshift">{{.Title}}</a>&nbsp;<a href="/pages/{{.Name}}">[{{.Name}}]</a> <a href="/panel/pages/edit/{{.ID}}"class="panel_upshift">{{.Title}}</a>&nbsp;<a href="/pages/{{.Name}}">[{{.Name}}]</a>
<span class="panel_buttons"> <span class="panel_buttons">
<a href="/panel/pages/edit/{{.ID}}" class="panel_tag panel_right_button edit_button" aria-label="{{lang "panel_pages_edit_button_aria"}}"></a> <a href="/panel/pages/edit/{{.ID}}"class="panel_tag panel_right_button edit_button"aria-label="{{lang "panel_pages_edit_button_aria"}}"></a>
<a href="/panel/pages/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}" class="panel_tag panel_right_button delete_button" aria-label="{{lang "panel_pages_delete_button_aria"}}"></a> <a href="/panel/pages/delete/submit/{{.ID}}?s={{$.CurrentUser.Session}}"class="panel_tag panel_right_button delete_button"aria-label="{{lang "panel_pages_delete_button_aria"}}"></a>
</span> </span>
</div> </div>
{{else}} {{else}}
@ -21,22 +21,22 @@
<div class="rowitem"><h1>{{lang "panel_pages_create_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_pages_create_head"}}</h1></div>
</div> </div>
<div class="colstack_item the_form"> <div class="colstack_item the_form">
<form action="/panel/pages/create/submit/?s={{.CurrentUser.Session}}" method="post"> <form action="/panel/pages/create/submit/?s={{.CurrentUser.Session}}"method="post">
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_pages_create_name"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_pages_create_name"}}</a></div>
<div class="formitem"><input name="name" type="text" placeholder="{{lang "panel_pages_create_name_placeholder"}}"></div> <div class="formitem"><input name="name"type="text"placeholder="{{lang "panel_pages_create_name_placeholder"}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_pages_create_title"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_pages_create_title"}}</a></div>
<div class="formitem"><input name="title" type="text" placeholder="{{lang "panel_pages_create_title_placeholder"}}"></div> <div class="formitem"><input name="title"type="text"placeholder="{{lang "panel_pages_create_title_placeholder"}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem"> <div class="formitem">
<textarea name="body" placeholder="{{lang "panel_pages_create_body_placeholder"}}"></textarea> <textarea name="body"placeholder="{{lang "panel_pages_create_body_placeholder"}}"></textarea>
</div> </div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem"><button name="panel-button" class="formbutton form_middle_button">{{lang "panel_pages_create_button"}}</button></div> <div class="formitem"><button name="panel-button"class="formbutton form_middle_button">{{lang "panel_pages_create_button"}}</button></div>
</div> </div>
</form> </form>
</div> </div>

View File

@ -3,20 +3,20 @@
<div class="colstack_item colstack_head"> <div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_edit_head"}}</h1></div> <div class="rowitem"><h1>{{lang "panel_themes_menus_edit_head"}}</h1></div>
</div> </div>
<form action="/panel/themes/menus/item/edit/submit/{{.Item.ID}}?s={{.CurrentUser.Session}}" method="post"> <form action="/panel/themes/menus/item/edit/submit/{{.Item.ID}}?s={{.CurrentUser.Session}}"method="post">
<div id="panel_themes_menu_item_edit" class="colstack_item the_form"> <div id="panel_themes_menu_item_edit"class="colstack_item the_form">
{{/** TODO: Let an admin move a menu item from one menu to another? **/}} {{/** TODO: Let an admin move a menu item from one menu to another? **/}}
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_name"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_themes_menus_name"}}</a></div>
<div class="formitem"><input name="item-name" type="text" value="{{.Item.Name}}"></div> <div class="formitem"><input name="item-name"type="text"value="{{.Item.Name}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_htmlid"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_themes_menus_htmlid"}}</a></div>
<div class="formitem"><input name="item-htmlid" type="text" value="{{.Item.HTMLID}}"></div> <div class="formitem"><input name="item-htmlid"type="text"value="{{.Item.HTMLID}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_cssclass"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_themes_menus_cssclass"}}</a></div>
<div class="formitem"><input name="item-cssclass" type="text" value="{{.Item.CSSClass}}"></div> <div class="formitem"><input name="item-cssclass"type="text"value="{{.Item.CSSClass}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_position"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_themes_menus_position"}}</a></div>
@ -29,19 +29,19 @@
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_path"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_themes_menus_path"}}</a></div>
<div class="formitem"><input name="item-path" type="text" value="{{.Item.Path}}"></div> <div class="formitem"><input name="item-path"type="text"value="{{.Item.Path}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_aria"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_themes_menus_aria"}}</a></div>
<div class="formitem"><input name="item-aria" type="text" value="{{.Item.Aria}}"></div> <div class="formitem"><input name="item-aria"type="text"value="{{.Item.Aria}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_tooltip"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_themes_menus_tooltip"}}</a></div>
<div class="formitem"><input name="item-tooltip" type="text" value="{{.Item.Tooltip}}"></div> <div class="formitem"><input name="item-tooltip"type="text"value="{{.Item.Tooltip}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_tmplname"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_themes_menus_tmplname"}}</a></div>
<div class="formitem"><input name="item-tmplname" type="text" value="{{.Item.TmplName}}"></div> <div class="formitem"><input name="item-tmplname"type="text"value="{{.Item.TmplName}}"></div>
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_permissions"}}</a></div> <div class="formitem formlabel"><a>{{lang "panel_themes_menus_permissions"}}</a></div>
@ -55,7 +55,7 @@
</div> </div>
<div class="formrow"> <div class="formrow">
<div class="formitem"> <div class="formitem">
<button name="panel-button" class="formbutton">{{lang "panel_themes_menus_edit_update_button"}}</button> <button name="panel-button"class="formbutton">{{lang "panel_themes_menus_edit_update_button"}}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -30,7 +30,7 @@
</div> </div>
{{if .CurrentUser.Loggedin}} {{if .CurrentUser.Loggedin}}
{{template "topics_mod_floater.html" .}} {{template "topics_mod_floater.html" . }}
{{if .ForumList}} {{if .ForumList}}
{{/** TODO: Have a seperate forum list for moving topics? Maybe an AJAX forum search compatible with plugin_guilds? **/}} {{/** TODO: Have a seperate forum list for moving topics? Maybe an AJAX forum search compatible with plugin_guilds? **/}}
@ -52,7 +52,7 @@
</form> </form>
</div> </div>
<div class="rowblock topic_create_form quick_create_form auto_hide"aria-label="{{lang "quick_topic.aria"}}"> <div class="rowblock topic_create_form quick_create_form auto_hide"aria-label="{{lang "quick_topic.aria"}}">
<form name="topic_create_form_form"id="quick_post_form"enctype="multipart/form-data"action="/topic/create/submit/?s={{.CurrentUser.Session}}" method="post"></form> <form name="topic_create_form_form"id="quick_post_form"enctype="multipart/form-data"action="/topic/create/submit/?s={{.CurrentUser.Session}}"method="post"></form>
<img class="little_row_avatar"src="{{.CurrentUser.MicroAvatar}}"height=64 alt="{{lang "quick_topic.avatar_alt"}}"title="{{lang "quick_topic.avatar_tooltip"}}"> <img class="little_row_avatar"src="{{.CurrentUser.MicroAvatar}}"height=64 alt="{{lang "quick_topic.avatar_alt"}}"title="{{lang "quick_topic.avatar_tooltip"}}">
<div class="main_form"> <div class="main_form">
<div class="topic_meta"> <div class="topic_meta">

View File

@ -6,9 +6,9 @@
</div> </div>
<div class="mod_floater_body"> <div class="mod_floater_body">
<select class="mod_floater_options"> <select class="mod_floater_options">
<option value="delete">{{lang "topic_list.moderate_delete"}}</option> <option class="val_delete" value="delete">{{lang "topic_list.moderate_delete"}}</option>
{{if .CanLock}}<option value="lock">{{lang "topic_list.moderate_lock"}}</option>{{end}} <option class="val_lock{{if not .CanLock}} auto_hide{{end}}" value="lock">{{lang "topic_list.moderate_lock"}}</option>
{{if .CanMove}}<option value="move">{{lang "topic_list.moderate_move"}}</option>{{end}} <option class="val_move{{if not .CanMove}} auto_hide{{end}}" value="move">{{lang "topic_list.moderate_move"}}</option>
</select> </select>
<button class="mod_floater_submit">{{lang "topic_list.moderate_run"}}</button> <button class="mod_floater_submit">{{lang "topic_list.moderate_run"}}</button>
</div> </div>

View File

@ -1,4 +1,4 @@
<div class="topic_row{{if .Sticky}} topic_sticky{{else if .IsClosed}} topic_closed{{end}}" data-tid="{{.ID}}"> <div class="topic_row{{if .Sticky}} topic_sticky{{else if .IsClosed}} topic_closed{{end}}{{if .CanMod}} can_mod{{end}}" data-tid="{{.ID}}">
<div class="rowitem topic_left passive datarow"> <div class="rowitem topic_left passive datarow">
<span class="selector"></span> <span class="selector"></span>
<a href="{{.Creator.Link}}"><img src="{{.Creator.MicroAvatar}}"height=64 alt="Avatar"title="{{.Creator.Name}}'s Avatar"aria-hidden="true"></a> <a href="{{.Creator.Link}}"><img src="{{.Creator.MicroAvatar}}"height=64 alt="Avatar"title="{{.Creator.Name}}'s Avatar"aria-hidden="true"></a>

View File

@ -907,7 +907,11 @@ textarea {
.topic_sticky .topic_left, .topic_sticky .topic_right { .topic_sticky .topic_left, .topic_sticky .topic_right {
border-bottom: 2px solid hsl(51, 60%, 70%); border-bottom: 2px solid hsl(51, 60%, 70%);
} }
.topics_moderate .topic_row:hover .topic_left, .topics_moderate .topic_row:hover .topic_right { .topics_moderate .topic_row:not(.can_mod) .topic_left,
.topics_moderate .topic_row:not(.can_mod) .topic_right {
background-color: #EEEEEE;
}
.topics_moderate .can_mod:hover .topic_left, .topics_moderate .can_mod:hover .topic_right {
background-color: hsl(81, 60%, 97%); background-color: hsl(81, 60%, 97%);
} }
.topic_selected .topic_left, .topic_selected .topic_right { .topic_selected .topic_left, .topic_selected .topic_right {

View File

@ -1,4 +1,4 @@
<div class="topic_row{{if .Sticky}} topic_sticky{{else if .IsClosed}} topic_closed{{end}}"data-tid={{.ID}}> <div class="topic_row{{if .Sticky}} topic_sticky{{else if .IsClosed}} topic_closed{{end}}{{if .CanMod}} can_mod{{end}}"data-tid={{.ID}}>
<div class="rowitem topic_left passive datarow"> <div class="rowitem topic_left passive datarow">
<a href="{{.Creator.Link}}"><img src="{{.Creator.MicroAvatar}}"height=64 alt="Avatar"title="{{.Creator.Name}}'s Avatar"aria-hidden="true"></a> <a href="{{.Creator.Link}}"><img src="{{.Creator.MicroAvatar}}"height=64 alt="Avatar"title="{{.Creator.Name}}'s Avatar"aria-hidden="true"></a>
<span class="topic_inner_left"> <span class="topic_inner_left">

View File

@ -228,7 +228,10 @@ li a {
.sidebar .rowblock:not(.topic_list):not(.rowhead):not(.opthead) .rowitem, .sidebar .search { .sidebar .rowblock:not(.topic_list):not(.rowhead):not(.opthead) .rowitem, .sidebar .search {
margin-left: 12px; margin-left: 12px;
} }
.topics_moderate .topic_row:hover { .topics_moderate .can_mod {
background-color: #4d4d4d;
}
.topics_moderate .can_mod:hover {
background-color: rgb(78, 78, 98); background-color: rgb(78, 78, 98);
} }
.widget_search:first-child { .widget_search:first-child {
@ -552,7 +555,7 @@ h2 {
.topic_closed { .topic_closed {
background-color: #4b4b4b; background-color: #4b4b4b;
} }
.topic_selected { .topic_row.topic_selected {
background-color: rgb(68, 68, 88); background-color: rgb(68, 68, 88);
} }
.new_item .topic_left { .new_item .topic_left {

View File

@ -855,12 +855,18 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
.topic_list .topic_row { .topic_list .topic_row {
display: flex; display: flex;
} }
.topics_moderate .topic_row:hover .rowitem { .topics_moderate .topic_row:not(.can_mod) .rowitem {
background-color: hsla(0, 0%, 27%, 1); background-color: hsla(0, 0%, 22%, 1);
} }
.topic_selected .rowitem { .topics_moderate .can_mod .rowitem {
background-color: hsla(0, 0%, 25%, 1);
}
.topics_moderate .can_mod:hover .rowitem {
background-color: hsla(0, 0%, 29%, 1); background-color: hsla(0, 0%, 29%, 1);
} }
.topic_row.topic_selected .rowitem {
background-color: hsla(0, 0%, 31%, 1);
}
/* Temporary hack, so that I don't break the topic lists of the other themes */ /* Temporary hack, so that I don't break the topic lists of the other themes */
.topic_list .topic_inner_right { .topic_list .topic_inner_right {
display: none; display: none;