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
}
type QuickTools struct {
CanDelete bool
CanLock bool
CanMove bool
}
type TopicListPage struct {
*Header
TopicList []*TopicsRow
TopicList []TopicsRowMut
ForumList []Forum
DefaultForum int
Sort TopicListSort
CanLock bool
CanMove bool
QuickTools
Paginator
}
type ForumPage struct {
*Header
ItemList []*TopicsRow
ItemList []TopicsRowMut
Forum *Forum
CanLock bool
CanMove bool

View File

@ -226,9 +226,9 @@ func compileCommons(c *tmpl.CTemplateSet, head, head2 *Header, forumList []Forum
return head2
}*/
var topicsList []*TopicsRow
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})
topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, false, false, Paginator{[]int{1}, 1, 1}}
var topicsList []TopicsRowMut
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}, QuickTools{false, false, false}, Paginator{[]int{1}, 1, 1}}
o.Add("topics", "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
t.Add("profile", "c.ProfilePage", ppage)
var topicsList []*TopicsRow
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})
topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, false, false, Paginator{[]int{1}, 1, 1}}
var topicsList []TopicsRowMut
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}, 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)
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))
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}
t.AddStd("topics_topic", "c.TopicsRow", topicsRow)
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.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{
PollOption{0, "Nothing"},

View File

@ -94,6 +94,11 @@ type TopicUser struct {
Deletable bool
}
type TopicsRowMut struct {
*TopicsRow
CanMod bool
}
// TODO: Embed TopicUser to simplify this structure and it's related logic?
type TopicsRow struct {
ID int
@ -151,11 +156,17 @@ type WsTopicsRow struct {
LastUser *WsJSONUser
ForumName string
ForumLink string
CanMod bool
}
// TODO: Can we get the client side to render the relative times instead?
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?

View File

@ -151,6 +151,7 @@ func wsTopicListTick(h *WsHubImpl) error {
}
canSeeRenders := make(map[string][]byte)
canSeeLists := make(map[string][]*WsTopicsRow)
for name, canSee := range canSeeMap {
topicList, forumList, _, err := TopicList.GetListByCanSee(canSee, 1, 0, nil)
if err != nil {
@ -180,6 +181,7 @@ func wsTopicListTick(h *WsHubImpl) error {
for i, topicRow := range topicList {
wsTopicList[i] = topicRow.WebSockets()
}
canSeeLists[name] = wsTopicList
outBytes, err := json.Marshal(&WsTopicList{wsTopicList, 0, tickStart.Unix()})
if err != nil {
@ -191,17 +193,94 @@ func wsTopicListTick(h *WsHubImpl) error {
// TODO: Use MessagePack for additional speed?
//fmt.Println("writing to the clients")
for _, wsUser := range currentWatchers {
group := groups[wsUser.User.Group]
u := wsUser.User
group := groups[u.Group]
canSee := make([]byte, len(group.CanSee))
for i, item := range group.CanSee {
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)
outBytes := canSeeRenders[string(canSee)]
outBytes := canSeeRenders[sCanSee]
//fmt.Println("outBytes: ", string(outBytes))
err := wsUser.WriteToPageBytes(outBytes, "/topics/")
if err == ErrNoneOnPage {
//fmt.Println("outBytes[:len(outBytes)-1]: ", string(outBytes[:len(outBytes)-1]))
//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)
wsUser.FinalizePage("/topics/", func() {
topicListMutex.Lock()

View File

@ -221,8 +221,8 @@ function runWebSockets(resume=false) {
if(window.location.protocol == "https:") s = "s";
conn = new WebSocket("ws"+s+"://" + document.location.host + "/ws/");
conn.onerror = err => {
console.log(err);
conn.onerror = e => {
console.log(e);
}
// TODO: Sync alerts, topic list, etc.
@ -297,11 +297,26 @@ function runWebSockets(resume=false) {
} else if("Topics" in data) {
console.log("topic in data");
console.log("data",data);
// TODO: Handle desyncs more gracefully?
// TODO: Send less unneccessary data?
let topic = data.Topics[0];
if(topic===undefined){
console.log("empty topic list");
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
let renTopic = Tmpl_topics_topic(topic);
$(".topic_row[data-tid='"+topic.ID+"']").addClass("ajax_topic_dupe");
@ -313,7 +328,7 @@ function runWebSockets(resume=false) {
moreTopicCount++;
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];
block.classList.remove("more_topic_block_initial");
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?
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();
}
@ -439,9 +454,9 @@ function mainInit(){
ev.preventDefault();
let blocks = document.getElementsByClassName("more_topic_block_active");
for(let i=0; i<blocks.length; i++) {
let block = blocks[i];
block.classList.remove("more_topic_block_active");
block.classList.add("more_topic_block_initial");
let bl = blocks[i];
bl.classList.remove("more_topic_block_active");
bl.classList.add("more_topic_block_initial");
}
$(".ajax_topic_dupe").fadeOut("slow", function(){
$(this).remove();
@ -646,6 +661,7 @@ function mainInit(){
});
bindPage();
runInitHook("after_init_bind_page");
$(".edit_field").click(function(ev) {
ev.preventDefault();
@ -927,6 +943,7 @@ function bindPage() {
});
bindTopic();
runHook("end_bind_page")
}
function unbindPage() {

View File

@ -151,16 +151,16 @@ var imageExts = ["png","jpg","jpe","jpeg","jif","jfi","jfif","svg","bmp","gif","
addInitHook("start_init", () => {
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");
if(uploadFiles!=null) {
uploadFiles.removeEventListener("change", uploadAttachHandler, false);
uploadFiles.addEventListener("change", uploadAttachHandler, false);
}
changeListener(uploadFiles,uploadAttachHandler);
let uploadFilesOp = document.getElementById("upload_files_op");
if(uploadFilesOp!=null) {
uploadFilesOp.removeEventListener("change", uploadAttachHandler2, false);
uploadFilesOp.addEventListener("change", uploadAttachHandler2, false);
}
changeListener(uploadFilesOp,uploadAttachHandler2);
bindAttachManager();
function bindAttachItems() {
@ -212,73 +212,6 @@ var imageExts = ["png","jpg","jpe","jpeg","jif","jfi","jfif","svg","bmp","gif","
bindAttachItems();
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() {
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
}
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)
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
if tmpl == "" {
ferr = renderTemplate("forum", w, r, h, pi)

View File

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

View File

@ -11,13 +11,13 @@ import (
p "github.com/Azareal/Gosora/common/phrases"
)
func Forums(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "forums", "forums")
func Forums(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, u, "forums", "forums")
if ferr != nil {
return ferr
}
if !user.Perms.ManageForums {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageForums {
return c.NoPermissions(w, r, u)
}
basePage.Header.AddScript("Sortable-1.4.0/Sortable.min.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})
}
func ForumsCreateSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ForumsCreateSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
if !user.Perms.ManageForums {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageForums {
return c.NoPermissions(w, r, u)
}
fname := r.PostFormValue("name")
fdesc := r.PostFormValue("desc")
fpreset := c.StripInvalidPreset(r.PostFormValue("preset"))
name := r.PostFormValue("name")
desc := r.PostFormValue("desc")
preset := c.StripInvalidPreset(r.PostFormValue("preset"))
factive := r.PostFormValue("active")
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 {
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 {
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
func ForumsDelete(w http.ResponseWriter, r *http.Request, user *c.User, sfid string) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "delete_forum", "forums")
func ForumsDelete(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
basePage, ferr := buildBasePage(w, r, u, "delete_forum", "forums")
if ferr != nil {
return ferr
}
if !user.Perms.ManageForums {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageForums {
return c.NoPermissions(w, r, u)
}
fid, err := strconv.Atoi(sfid)
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)
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 {
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}
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 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 {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
if !user.Perms.ManageForums {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageForums {
return c.NoPermissions(w, r, u)
}
fid, err := strconv.Atoi(sfid)
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)
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 {
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 {
return c.InternalError(err, w, r)
}
@ -139,14 +139,14 @@ func ForumsDeleteSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sf
return nil
}
func ForumsOrderSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ForumsOrderSubmit(w http.ResponseWriter, r *http.Request, u *c.User) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
js := r.PostFormValue("js") == "1"
if !user.Perms.ManageForums {
return c.NoPermissionsJSQ(w, r, user, js)
if !u.Perms.ManageForums {
return c.NoPermissionsJSQ(w, r, u, js)
}
sitems := strings.TrimSuffix(strings.TrimPrefix(r.PostFormValue("items"), "{"), "}")
//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, ",") {
fid, err := strconv.Atoi(sfid)
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
}
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 {
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)
}
func ForumsEdit(w http.ResponseWriter, r *http.Request, user *c.User, sfid string) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "edit_forum", "forums")
func ForumsEdit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
basePage, ferr := buildBasePage(w, r, u, "edit_forum", "forums")
if ferr != nil {
return ferr
}
if !user.Perms.ManageForums {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageForums {
return c.NoPermissions(w, r, u)
}
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)
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 {
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})
}
func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sfid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
if !user.Perms.ManageForums {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageForums {
return c.NoPermissions(w, r, u)
}
js := r.PostFormValue("js") == "1"
fid, err := strconv.Atoi(sfid)
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)
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 {
return c.InternalErrorJSQ(err, w, r, js)
}
fname := r.PostFormValue("forum_name")
fdesc := r.PostFormValue("forum_desc")
fpreset := c.StripInvalidPreset(r.PostFormValue("forum_preset"))
name := r.PostFormValue("forum_name")
desc := r.PostFormValue("forum_desc")
preset := c.StripInvalidPreset(r.PostFormValue("forum_preset"))
factive := r.PostFormValue("forum_active")
active := false
@ -255,11 +255,11 @@ func ForumsEditSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sfid
active = true
}
err = forum.Update(fname, fdesc, active, fpreset)
err = forum.Update(name, desc, active, preset)
if err != nil {
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 {
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)
}
func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, user *c.User, sfid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ForumsEditPermsSubmit(w http.ResponseWriter, r *http.Request, u *c.User, sfid string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
if !user.Perms.ManageForums {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageForums {
return c.NoPermissions(w, r, u)
}
js := r.PostFormValue("js") == "1"
fid, err := strconv.Atoi(sfid)
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"))
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)
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 {
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"))
err = forum.SetPreset(permPreset, gid)
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 {
return c.InternalError(err, w, r)
}
@ -328,23 +327,23 @@ func forumPermsExtractDash(paramList string) (fid, gid int, err error) {
return fid, gid, err
}
func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, user *c.User, paramList string) c.RouteError {
basePage, ferr := buildBasePage(w, r, user, "edit_forum", "forums")
func ForumsEditPermsAdvance(w http.ResponseWriter, r *http.Request, u *c.User, paramList string) c.RouteError {
basePage, ferr := buildBasePage(w, r, u, "edit_forum", "forums")
if ferr != nil {
return ferr
}
if !user.Perms.ManageForums {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageForums {
return c.NoPermissions(w, r, u)
}
fid, gid, err := forumPermsExtractDash(paramList)
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)
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 {
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})
}
func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, user *c.User, paramList string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, user)
func ForumsEditPermsAdvanceSubmit(w http.ResponseWriter, r *http.Request, u *c.User, paramList string) c.RouteError {
_, ferr := c.SimplePanelUserCheck(w, r, u)
if ferr != nil {
return ferr
}
if !user.Perms.ManageForums {
return c.NoPermissions(w, r, user)
if !u.Perms.ManageForums {
return c.NoPermissions(w, r, u)
}
js := r.PostFormValue("js") == "1"
fid, gid, err := forumPermsExtractDash(paramList)
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)
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 {
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)
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 {
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}
}
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 {
/*skip, rerr := h.Hooks.VhookSkippable("route_topic_list_start", w, r, u, h)
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 forumList []c.Forum
var pagi c.Paginator
var canLock, ccanLock, canMove, ccanMove bool
var canDelete, ccanDelete, canLock, ccanLock, canMove, ccanMove bool
q := r.FormValue("q")
if q != "" && c.RepliesSearch != nil {
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?
//var sb strings.Builder
fps := make(map[int]c.QuickTools)
for _, t := range topicList {
//c.BuildTopicURLSb(&sb, c.NameToSlug(t.Title), t.ID)
//t.Link = sb.String()
//sb.Reset()
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.
forum := c.Forums.DirtyGet(t.ParentID)
t.ForumName = forum.Name
t.ForumLink = forum.Link
f := c.Forums.DirtyGet(t.ParentID)
t.ForumName = f.Name
t.ForumLink = f.Link
fp, err := c.FPStore.Get(forum.ID, user.Group)
if err == c.ErrNoRows {
fp = c.BlankForumPerms()
} else if err != nil {
return c.InternalError(err, w, r)
}
if fp.Overrides && !user.IsSuperAdmin {
ccanLock = fp.CloseTopic
ccanMove = fp.MoveTopic
} else {
ccanLock = user.Perms.CloseTopic
ccanMove = user.Perms.MoveTopic
}
if ccanLock {
canLock = true
}
if ccanMove {
canMove = true
_, ok := fps[f.ID]
if !ok {
// TODO: Abstract this?
fp, err := c.FPStore.Get(f.ID, user.Group)
if err == c.ErrNoRows {
fp = c.BlankForumPerms()
} else if err != nil {
return c.InternalError(err, w, r)
}
if fp.Overrides && !user.IsSuperAdmin {
ccanDelete = fp.DeleteTopic
ccanLock = fp.CloseTopic
ccanMove = fp.MoveTopic
} else {
ccanDelete = user.Perms.DeleteTopic
ccanLock = user.Perms.CloseTopic
ccanMove = user.Perms.MoveTopic
}
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
@ -192,7 +218,7 @@ func TopicListCommon(w http.ResponseWriter, r *http.Request, user *c.User, h *c.
// TODO: Reduce the amount of boilerplate here
if r.FormValue("js") == "1" {
outBytes, err := wsTopicList(topicList, pagi.LastPage).MarshalJSON()
outBytes, err := wsTopicList2(topicList, user, fps, pagi.LastPage).MarshalJSON()
if err != nil {
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
}
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")
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)
}
// TODO: Pass a struct back rather than passing back so many variables
var fps map[int]c.QuickTools
if user.IsSuperAdmin {
topicList, forumList, pagi, err = c.TopicList.GetList(page, tsorder, fids)
canLock, canMove = true, true
} else {
topicList, forumList, pagi, err = c.TopicList.GetListByGroup(group, page, tsorder, fids)
for _, forum := range forumList {
fp, err := c.FPStore.Get(forum.ID, user.Group)
fps = make(map[int]c.QuickTools)
for _, f := range forumList {
fp, err := c.FPStore.Get(f.ID, user.Group)
if err == c.ErrNoRows {
fp = c.BlankForumPerms()
} else if err != nil {
return c.InternalError(err, w, r)
}
if fp.Overrides {
ccanDelete = fp.DeleteTopic
ccanLock = fp.CloseTopic
ccanMove = fp.MoveTopic
} else {
ccanDelete = user.Perms.DeleteTopic
ccanLock = user.Perms.CloseTopic
ccanMove = user.Perms.MoveTopic
}
if ccanDelete {
canDelete = true
}
if ccanLock {
canLock = true
}
if ccanMove {
canMove = true
}
fps[f.ID] = c.QuickTools{ccanDelete, ccanLock, ccanMove}
}
}
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
if r.FormValue("js") == "1" {
outBytes, err := wsTopicList(topicList, pagi.LastPage).MarshalJSON()
outBytes, err := wsTopicList2(topicList, user, fps, pagi.LastPage).MarshalJSON()
if err != nil {
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
}
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" {
return renderTemplate("topics_mini", w, r, h, pi)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -47,7 +47,7 @@
{{end}}
{{end}}
<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">
<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>

View File

@ -1,13 +1,13 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_pages_head"}}</h1></div>
</div>
<div id="panel_pages" class="colstack_item rowlist">
<div id="panel_pages"class="colstack_item rowlist">
{{range .ItemList}}
<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">
<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/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>
</span>
</div>
{{else}}
@ -21,22 +21,22 @@
<div class="rowitem"><h1>{{lang "panel_pages_create_head"}}</h1></div>
</div>
<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="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 class="formrow">
<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 class="formrow">
<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 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>
</form>
</div>

View File

@ -3,20 +3,20 @@
<div class="colstack_item colstack_head">
<div class="rowitem"><h1>{{lang "panel_themes_menus_edit_head"}}</h1></div>
</div>
<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">
<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">
{{/** TODO: Let an admin move a menu item from one menu to another? **/}}
<div class="formrow">
<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 class="formrow">
<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 class="formrow">
<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 class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_position"}}</a></div>
@ -29,19 +29,19 @@
</div>
<div class="formrow">
<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 class="formrow">
<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 class="formrow">
<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 class="formrow">
<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 class="formrow">
<div class="formitem formlabel"><a>{{lang "panel_themes_menus_permissions"}}</a></div>
@ -55,7 +55,7 @@
</div>
<div class="formrow">
<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>

View File

@ -30,7 +30,7 @@
</div>
{{if .CurrentUser.Loggedin}}
{{template "topics_mod_floater.html" .}}
{{template "topics_mod_floater.html" . }}
{{if .ForumList}}
{{/** TODO: Have a seperate forum list for moving topics? Maybe an AJAX forum search compatible with plugin_guilds? **/}}
@ -52,7 +52,7 @@
</form>
</div>
<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"}}">
<div class="main_form">
<div class="topic_meta">

View File

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

View File

@ -907,7 +907,11 @@ textarea {
.topic_sticky .topic_left, .topic_sticky .topic_right {
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%);
}
.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">
<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">

View File

@ -228,7 +228,10 @@ li a {
.sidebar .rowblock:not(.topic_list):not(.rowhead):not(.opthead) .rowitem, .sidebar .search {
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);
}
.widget_search:first-child {
@ -552,7 +555,7 @@ h2 {
.topic_closed {
background-color: #4b4b4b;
}
.topic_selected {
.topic_row.topic_selected {
background-color: rgb(68, 68, 88);
}
.new_item .topic_left {

View File

@ -855,12 +855,18 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
.topic_list .topic_row {
display: flex;
}
.topics_moderate .topic_row:hover .rowitem {
background-color: hsla(0, 0%, 27%, 1);
.topics_moderate .topic_row:not(.can_mod) .rowitem {
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);
}
.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 */
.topic_list .topic_inner_right {
display: none;