Added support for desktop notifications.
The alert list is now visible on the Shadow Theme. The User Manager is now paginated. The Moderation Logs are now paginated. The Group Manager is now paginated. The alert counter no longer shows up as undefined on Edge. Added a cache control header to the static files. Fixed a few bits of mangled HTML. Fixed the Forum Manager CSS for the Shadow Theme.
This commit is contained in:
parent
3e4cfa8888
commit
ca9c755a47
18
errors.go
18
errors.go
|
@ -3,14 +3,18 @@ package main
|
||||||
import "fmt"
|
import "fmt"
|
||||||
import "log"
|
import "log"
|
||||||
import "bytes"
|
import "bytes"
|
||||||
|
import "sync"
|
||||||
import "net/http"
|
import "net/http"
|
||||||
import "runtime/debug"
|
import "runtime/debug"
|
||||||
|
|
||||||
|
// TO-DO: Use the error_buffer variable to construct the system log in the Control Panel. Should we log errors caused by users too? Or just collect statistics on those or do nothing? Intercept recover()? Could we intercept the logger instead here? We might get too much information, if we intercept the logger, maybe make it part of the Debug page?
|
||||||
|
var error_buffer_mutex sync.RWMutex
|
||||||
|
var error_buffer []error
|
||||||
//var notfound_count_per_second int
|
//var notfound_count_per_second int
|
||||||
//var noperms_count_per_second int
|
//var noperms_count_per_second int
|
||||||
|
|
||||||
var error_internal []byte
|
var error_internal []byte
|
||||||
var error_notfound []byte
|
var error_notfound []byte
|
||||||
|
|
||||||
func init_errors() error {
|
func init_errors() error {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
user := User{0,"guest","Guest","",0,false,false,false,false,false,false,GuestPerms,nil,"",false,"","","","","",0,0,"0.0.0.0.0"}
|
user := User{0,"guest","Guest","",0,false,false,false,false,false,false,GuestPerms,nil,"",false,"","","","","",0,0,"0.0.0.0.0"}
|
||||||
|
@ -34,6 +38,9 @@ func init_errors() error {
|
||||||
func LogError(err error) {
|
func LogError(err error) {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
|
error_buffer_mutex.Lock()
|
||||||
|
defer error_buffer_mutex.Unlock()
|
||||||
|
error_buffer = append(error_buffer,err)
|
||||||
log.Fatal("")
|
log.Fatal("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +48,9 @@ func InternalError(err error, w http.ResponseWriter) {
|
||||||
w.Write(error_internal)
|
w.Write(error_internal)
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
|
error_buffer_mutex.Lock()
|
||||||
|
defer error_buffer_mutex.Unlock()
|
||||||
|
error_buffer = append(error_buffer,err)
|
||||||
log.Fatal("")
|
log.Fatal("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,12 +63,18 @@ func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, is_js s
|
||||||
}
|
}
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
|
error_buffer_mutex.Lock()
|
||||||
|
defer error_buffer_mutex.Unlock()
|
||||||
|
error_buffer = append(error_buffer,err)
|
||||||
log.Fatal("")
|
log.Fatal("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) {
|
func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
w.Write([]byte(`{"errmsg":"A problem has occured in the system."}`))
|
w.Write([]byte(`{"errmsg":"A problem has occured in the system."}`))
|
||||||
|
error_buffer_mutex.Lock()
|
||||||
|
defer error_buffer_mutex.Unlock()
|
||||||
|
error_buffer = append(error_buffer,err)
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
21
gen_mysql.go
21
gen_mysql.go
|
@ -23,8 +23,10 @@ var get_themes_stmt *sql.Stmt
|
||||||
var get_widgets_stmt *sql.Stmt
|
var get_widgets_stmt *sql.Stmt
|
||||||
var is_plugin_active_stmt *sql.Stmt
|
var is_plugin_active_stmt *sql.Stmt
|
||||||
var get_users_stmt *sql.Stmt
|
var get_users_stmt *sql.Stmt
|
||||||
|
var get_users_offset_stmt *sql.Stmt
|
||||||
var is_theme_default_stmt *sql.Stmt
|
var is_theme_default_stmt *sql.Stmt
|
||||||
var get_modlogs_stmt *sql.Stmt
|
var get_modlogs_stmt *sql.Stmt
|
||||||
|
var get_modlogs_offset_stmt *sql.Stmt
|
||||||
var get_reply_tid_stmt *sql.Stmt
|
var get_reply_tid_stmt *sql.Stmt
|
||||||
var get_topic_fid_stmt *sql.Stmt
|
var get_topic_fid_stmt *sql.Stmt
|
||||||
var get_user_reply_uid_stmt *sql.Stmt
|
var get_user_reply_uid_stmt *sql.Stmt
|
||||||
|
@ -109,6 +111,7 @@ var delete_profile_reply_stmt *sql.Stmt
|
||||||
var delete_forum_perms_by_forum_stmt *sql.Stmt
|
var delete_forum_perms_by_forum_stmt *sql.Stmt
|
||||||
var report_exists_stmt *sql.Stmt
|
var report_exists_stmt *sql.Stmt
|
||||||
var group_count_stmt *sql.Stmt
|
var group_count_stmt *sql.Stmt
|
||||||
|
var modlog_count_stmt *sql.Stmt
|
||||||
var add_forum_perms_to_forum_admins_stmt *sql.Stmt
|
var add_forum_perms_to_forum_admins_stmt *sql.Stmt
|
||||||
var add_forum_perms_to_forum_staff_stmt *sql.Stmt
|
var add_forum_perms_to_forum_staff_stmt *sql.Stmt
|
||||||
var add_forum_perms_to_forum_members_stmt *sql.Stmt
|
var add_forum_perms_to_forum_members_stmt *sql.Stmt
|
||||||
|
@ -215,6 +218,12 @@ func _gen_mysql() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Print("Preparing get_users_offset statement.")
|
||||||
|
get_users_offset_stmt, err = db.Prepare("SELECT `uid`,`name`,`group`,`active`,`is_super_admin`,`avatar` FROM `users` LIMIT ?,?")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
log.Print("Preparing is_theme_default statement.")
|
log.Print("Preparing is_theme_default statement.")
|
||||||
is_theme_default_stmt, err = db.Prepare("SELECT `default` FROM `themes` WHERE `uname` = ?")
|
is_theme_default_stmt, err = db.Prepare("SELECT `default` FROM `themes` WHERE `uname` = ?")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -227,6 +236,12 @@ func _gen_mysql() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Print("Preparing get_modlogs_offset statement.")
|
||||||
|
get_modlogs_offset_stmt, err = db.Prepare("SELECT `action`,`elementID`,`elementType`,`ipaddress`,`actorID`,`doneAt` FROM `moderation_logs` LIMIT ?,?")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
log.Print("Preparing get_reply_tid statement.")
|
log.Print("Preparing get_reply_tid statement.")
|
||||||
get_reply_tid_stmt, err = db.Prepare("SELECT `tid` FROM `replies` WHERE `rid` = ?")
|
get_reply_tid_stmt, err = db.Prepare("SELECT `tid` FROM `replies` WHERE `rid` = ?")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -731,6 +746,12 @@ func _gen_mysql() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Print("Preparing modlog_count statement.")
|
||||||
|
modlog_count_stmt, err = db.Prepare("SELECT COUNT(*) AS `count` FROM `moderation_logs`")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
log.Print("Preparing add_forum_perms_to_forum_admins statement.")
|
log.Print("Preparing add_forum_perms_to_forum_admins statement.")
|
||||||
add_forum_perms_to_forum_admins_stmt, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) SELECT `gid`, ? AS `fid`, ? AS `preset`, ? AS `permissions` FROM `users_groups` WHERE `is_admin` = 1")
|
add_forum_perms_to_forum_admins_stmt, err = db.Prepare("INSERT INTO `forums_permissions`(`gid`,`fid`,`preset`,`permissions`) SELECT `gid`, ? AS `fid`, ? AS `preset`, ? AS `permissions` FROM `users_groups` WHERE `is_admin` = 1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -190,6 +190,8 @@ INSERT INTO settings(`name`,`content`,`type`) VALUES ('url_tags','1','bool');
|
||||||
INSERT INTO settings(`name`,`content`,`type`,`constraints`) VALUES ('activation_type','1','list','1-3');
|
INSERT INTO settings(`name`,`content`,`type`,`constraints`) VALUES ('activation_type','1','list','1-3');
|
||||||
INSERT INTO settings(`name`,`content`,`type`) VALUES ('bigpost_min_words','250','int');
|
INSERT INTO settings(`name`,`content`,`type`) VALUES ('bigpost_min_words','250','int');
|
||||||
INSERT INTO settings(`name`,`content`,`type`) VALUES ('megapost_min_words','1000','int');
|
INSERT INTO settings(`name`,`content`,`type`) VALUES ('megapost_min_words','1000','int');
|
||||||
|
/* TO-DO: Implement the html-attribute setting type before deploying this */
|
||||||
|
/*INSERT INTO settings(`name`,`content`,`type`) VALUES ('meta_desc','','html-attribute');*/
|
||||||
INSERT INTO themes(`uname`,`default`) VALUES ('tempra-simple',1);
|
INSERT INTO themes(`uname`,`default`) VALUES ('tempra-simple',1);
|
||||||
INSERT INTO emails(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1);
|
INSERT INTO emails(`email`,`uid`,`validated`) VALUES ('admin@localhost',1,1);
|
||||||
|
|
||||||
|
|
42
pages.go
42
pages.go
|
@ -126,19 +126,6 @@ type PanelPage struct
|
||||||
Something interface{}
|
Something interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type PanelGroupPage struct
|
|
||||||
{
|
|
||||||
Title string
|
|
||||||
CurrentUser User
|
|
||||||
Header HeaderVars
|
|
||||||
Stats PanelStats
|
|
||||||
ItemList []GroupAdmin
|
|
||||||
PageList []int
|
|
||||||
Page int
|
|
||||||
LastPage int
|
|
||||||
ExtData ExtData
|
|
||||||
}
|
|
||||||
|
|
||||||
type GridElement struct
|
type GridElement struct
|
||||||
{
|
{
|
||||||
ID string
|
ID string
|
||||||
|
@ -171,6 +158,32 @@ type PanelThemesPage struct
|
||||||
ExtData ExtData
|
ExtData ExtData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PanelUserPage struct
|
||||||
|
{
|
||||||
|
Title string
|
||||||
|
CurrentUser User
|
||||||
|
Header HeaderVars
|
||||||
|
Stats PanelStats
|
||||||
|
ItemList []User
|
||||||
|
PageList []int
|
||||||
|
Page int
|
||||||
|
LastPage int
|
||||||
|
ExtData ExtData
|
||||||
|
}
|
||||||
|
|
||||||
|
type PanelGroupPage struct
|
||||||
|
{
|
||||||
|
Title string
|
||||||
|
CurrentUser User
|
||||||
|
Header HeaderVars
|
||||||
|
Stats PanelStats
|
||||||
|
ItemList []GroupAdmin
|
||||||
|
PageList []int
|
||||||
|
Page int
|
||||||
|
LastPage int
|
||||||
|
ExtData ExtData
|
||||||
|
}
|
||||||
|
|
||||||
type PanelEditGroupPage struct
|
type PanelEditGroupPage struct
|
||||||
{
|
{
|
||||||
Title string
|
Title string
|
||||||
|
@ -245,6 +258,9 @@ type PanelLogsPage struct
|
||||||
Header HeaderVars
|
Header HeaderVars
|
||||||
Stats PanelStats
|
Stats PanelStats
|
||||||
Logs []Log
|
Logs []Log
|
||||||
|
PageList []int
|
||||||
|
Page int
|
||||||
|
LastPage int
|
||||||
ExtData ExtData
|
ExtData ExtData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -964,15 +964,19 @@ func route_panel_users(w http.ResponseWriter, r *http.Request, user User){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var userList []interface{}
|
page, _ := strconv.Atoi(r.FormValue("page"))
|
||||||
rows, err := get_users_stmt.Query()
|
perPage := 10
|
||||||
|
offset, page, lastPage := page_offset(stats.Users, page, perPage)
|
||||||
|
|
||||||
|
var userList []User
|
||||||
|
rows, err := get_users_offset_stmt.Query(offset,perPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InternalError(err,w)
|
InternalError(err,w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
// TO-DO: Add a UserStore method for iterating over global users
|
// TO-DO: Add a UserStore method for iterating over global users and global user offsets
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
puser := User{ID: 0,}
|
puser := User{ID: 0,}
|
||||||
err := rows.Scan(&puser.ID, &puser.Name, &puser.Group, &puser.Active, &puser.Is_Super_Admin, &puser.Avatar)
|
err := rows.Scan(&puser.ID, &puser.Name, &puser.Group, &puser.Active, &puser.Is_Super_Admin, &puser.Avatar)
|
||||||
|
@ -1003,7 +1007,8 @@ func route_panel_users(w http.ResponseWriter, r *http.Request, user User){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pi := PanelPage{"User Manager",user,headerVars,stats,userList,nil}
|
pageList := paginate(stats.Users, perPage, 5)
|
||||||
|
pi := PanelUserPage{"User Manager",user,headerVars,stats,userList,pageList,page,lastPage,extData}
|
||||||
if pre_render_hooks["pre_render_panel_users"] != nil {
|
if pre_render_hooks["pre_render_panel_users"] != nil {
|
||||||
if run_pre_render_hook("pre_render_panel_users", w, r, &user, &pi) {
|
if run_pre_render_hook("pre_render_panel_users", w, r, &user, &pi) {
|
||||||
return
|
return
|
||||||
|
@ -1740,7 +1745,18 @@ func route_panel_logs_mod(w http.ResponseWriter, r *http.Request, user User){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := get_modlogs_stmt.Query()
|
var logCount int
|
||||||
|
err := modlog_count_stmt.QueryRow().Scan(&logCount)
|
||||||
|
if err != nil {
|
||||||
|
InternalError(err,w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page, _ := strconv.Atoi(r.FormValue("page"))
|
||||||
|
perPage := 10
|
||||||
|
offset, page, lastPage := page_offset(logCount, page, perPage)
|
||||||
|
|
||||||
|
rows, err := get_modlogs_offset_stmt.Query(offset,perPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
InternalError(err,w)
|
InternalError(err,w)
|
||||||
return
|
return
|
||||||
|
@ -1826,7 +1842,8 @@ func route_panel_logs_mod(w http.ResponseWriter, r *http.Request, user User){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pi := PanelLogsPage{"Moderation Logs",user,headerVars,stats,logs,extData}
|
pageList := paginate(logCount, perPage, 5)
|
||||||
|
pi := PanelLogsPage{"Moderation Logs",user,headerVars,stats,logs,pageList,page,lastPage,extData}
|
||||||
if pre_render_hooks["pre_render_panel_mod_log"] != nil {
|
if pre_render_hooks["pre_render_panel_mod_log"] != nil {
|
||||||
if run_pre_render_hook("pre_render_panel_mod_log", w, r, &user, &pi) {
|
if run_pre_render_hook("pre_render_panel_mod_log", w, r, &user, &pi) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -15,7 +15,7 @@ function load_alerts(menu_alerts)
|
||||||
{
|
{
|
||||||
var alertListNode = menu_alerts.getElementsByClassName("alertList")[0];
|
var alertListNode = menu_alerts.getElementsByClassName("alertList")[0];
|
||||||
var alertCounterNode = menu_alerts.getElementsByClassName("alert_counter")[0];
|
var alertCounterNode = menu_alerts.getElementsByClassName("alert_counter")[0];
|
||||||
alertCounterNode.textContent = "";
|
alertCounterNode.textContent = "0";
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'get',
|
type: 'get',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
|
@ -58,7 +58,7 @@ function load_alerts(menu_alerts)
|
||||||
//if(anyAvatar) menu_alerts.addClass("hasAvatars");
|
//if(anyAvatar) menu_alerts.addClass("hasAvatars");
|
||||||
}
|
}
|
||||||
alertListNode.innerHTML = alist;
|
alertListNode.innerHTML = alist;
|
||||||
if(data.msgCount != 0) {
|
if(data.msgCount != 0 && data.msgCount != undefined) {
|
||||||
alertCounterNode.textContent = data.msgCount;
|
alertCounterNode.textContent = data.msgCount;
|
||||||
menu_alerts.classList.add("has_alerts");
|
menu_alerts.classList.add("has_alerts");
|
||||||
} else {
|
} else {
|
||||||
|
@ -108,6 +108,8 @@ $(document).ready(function(){
|
||||||
|
|
||||||
conn.onopen = function() {
|
conn.onopen = function() {
|
||||||
conn.send("page " + document.location.pathname + '\r');
|
conn.send("page " + document.location.pathname + '\r');
|
||||||
|
// TO-DO: Don't ask again, if it's denied. We could have a setting in the UCP which automatically requests this when someone flips desktop notifications on
|
||||||
|
Notification.requestPermission();
|
||||||
}
|
}
|
||||||
conn.onclose = function() {
|
conn.onclose = function() {
|
||||||
conn = false;
|
conn = false;
|
||||||
|
@ -141,6 +143,16 @@ $(document).ready(function(){
|
||||||
//console.log(alist);
|
//console.log(alist);
|
||||||
$("#general_alerts").find(".alertList").html(alist); // Add support for other alert feeds like PM Alerts
|
$("#general_alerts").find(".alertList").html(alist); // Add support for other alert feeds like PM Alerts
|
||||||
$("#general_alerts").find(".alert_counter").text(alertCount);
|
$("#general_alerts").find(".alert_counter").text(alertCount);
|
||||||
|
|
||||||
|
// TO-DO: Add some sort of notification queue to avoid flooding the end-user with notices?
|
||||||
|
// TO-DO: Use the site name instead of "Something Happened"
|
||||||
|
if(Notification.permission === "granted") {
|
||||||
|
var n = new Notification("Something Happened",{
|
||||||
|
body: msg,
|
||||||
|
icon: data.avatar,
|
||||||
|
});
|
||||||
|
setTimeout(n.close.bind(n), 8000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -192,10 +192,14 @@ func write_selects(adapter qgen.DB_Adapter) error {
|
||||||
|
|
||||||
adapter.SimpleSelect("get_users","users","uid, name, group, active, is_super_admin, avatar","","","")
|
adapter.SimpleSelect("get_users","users","uid, name, group, active, is_super_admin, avatar","","","")
|
||||||
|
|
||||||
|
adapter.SimpleSelect("get_users_offset","users","uid, name, group, active, is_super_admin, avatar","","","?,?")
|
||||||
|
|
||||||
adapter.SimpleSelect("is_theme_default","themes","default","uname = ?","","")
|
adapter.SimpleSelect("is_theme_default","themes","default","uname = ?","","")
|
||||||
|
|
||||||
adapter.SimpleSelect("get_modlogs","moderation_logs","action, elementID, elementType, ipaddress, actorID, doneAt","","","")
|
adapter.SimpleSelect("get_modlogs","moderation_logs","action, elementID, elementType, ipaddress, actorID, doneAt","","","")
|
||||||
|
|
||||||
|
adapter.SimpleSelect("get_modlogs_offset","moderation_logs","action, elementID, elementType, ipaddress, actorID, doneAt","","","?,?")
|
||||||
|
|
||||||
adapter.SimpleSelect("get_reply_tid","replies","tid","rid = ?","","")
|
adapter.SimpleSelect("get_reply_tid","replies","tid","rid = ?","","")
|
||||||
|
|
||||||
adapter.SimpleSelect("get_topic_fid","topics","parentID","tid = ?","","")
|
adapter.SimpleSelect("get_topic_fid","topics","parentID","tid = ?","","")
|
||||||
|
@ -395,6 +399,8 @@ func write_simple_counts(adapter qgen.DB_Adapter) error {
|
||||||
|
|
||||||
adapter.SimpleCount("group_count","users_groups","","")
|
adapter.SimpleCount("group_count","users_groups","","")
|
||||||
|
|
||||||
|
adapter.SimpleCount("modlog_count","moderation_logs","","")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ var nList []string
|
||||||
var hvars HeaderVars
|
var hvars HeaderVars
|
||||||
var extData ExtData
|
var extData ExtData
|
||||||
var success_json_bytes []byte = []byte(`{"success":"1"}`)
|
var success_json_bytes []byte = []byte(`{"success":"1"}`)
|
||||||
|
var cacheControlMaxAge string = "max-age=" + strconv.Itoa(day)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
hvars.Site = site
|
hvars.Site = site
|
||||||
|
@ -57,14 +58,14 @@ func route_static(w http.ResponseWriter, r *http.Request){
|
||||||
h := w.Header()
|
h := w.Header()
|
||||||
h.Set("Last-Modified", file.FormattedModTime)
|
h.Set("Last-Modified", file.FormattedModTime)
|
||||||
h.Set("Content-Type", file.Mimetype)
|
h.Set("Content-Type", file.Mimetype)
|
||||||
|
//Cache-Control: max-age=31536000
|
||||||
|
h.Set("Cache-Control", cacheControlMaxAge)
|
||||||
|
h.Set("Vary","Accept-Encoding")
|
||||||
//http.ServeContent(w,r,r.URL.Path,file.Info.ModTime(),file)
|
//http.ServeContent(w,r,r.URL.Path,file.Info.ModTime(),file)
|
||||||
//w.Write(file.Data)
|
//w.Write(file.Data)
|
||||||
if strings.Contains(r.Header.Get("Accept-Encoding"),"gzip") {
|
if strings.Contains(r.Header.Get("Accept-Encoding"),"gzip") {
|
||||||
h.Set("Content-Encoding","gzip")
|
h.Set("Content-Encoding","gzip")
|
||||||
h.Set("Content-Length", strconv.FormatInt(file.GzipLength, 10))
|
h.Set("Content-Length", strconv.FormatInt(file.GzipLength, 10))
|
||||||
if site.HasProxy {
|
|
||||||
h.Set("Vary","Accept-Encoding")
|
|
||||||
}
|
|
||||||
io.Copy(w, bytes.NewReader(file.GzipData)) // Use w.Write instead?
|
io.Copy(w, bytes.NewReader(file.GzipData)) // Use w.Write instead?
|
||||||
} else {
|
} else {
|
||||||
h.Set("Content-Length", strconv.FormatInt(file.Length, 10)) // Avoid doing a type conversion every time?
|
h.Set("Content-Length", strconv.FormatInt(file.Length, 10)) // Avoid doing a type conversion every time?
|
||||||
|
|
|
@ -50,6 +50,7 @@ func LoadSettings() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TO-DO: Add better support for HTML attributes (html-attribute). E.g. Meta descriptions.
|
||||||
func parseSetting(sname string, scontent string, stype string, constraint string) string {
|
func parseSetting(sname string, scontent string, stype string, constraint string) string {
|
||||||
var err error
|
var err error
|
||||||
if stype == "bool" {
|
if stype == "bool" {
|
||||||
|
|
|
@ -149,7 +149,7 @@ var topic_40 []byte = []byte(`' style="font-weight:normal;" title="Unpin Topic">
|
||||||
var topic_41 []byte = []byte(`<a href='/topic/stick/submit/`)
|
var topic_41 []byte = []byte(`<a href='/topic/stick/submit/`)
|
||||||
var topic_42 []byte = []byte(`' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username pin_label"></button></a>`)
|
var topic_42 []byte = []byte(`' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username pin_label"></button></a>`)
|
||||||
var topic_43 []byte = []byte(`
|
var topic_43 []byte = []byte(`
|
||||||
<a class="mod_button" href="/report/submit/`)
|
<a href="/report/submit/`)
|
||||||
var topic_44 []byte = []byte(`?session=`)
|
var topic_44 []byte = []byte(`?session=`)
|
||||||
var topic_45 []byte = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username flag_label"></button></a>
|
var topic_45 []byte = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username flag_label"></button></a>
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ var topic_71 []byte = []byte(`" class="mod_button" title="Edit Reply"><button cl
|
||||||
var topic_72 []byte = []byte(`<a href="/reply/delete/submit/`)
|
var topic_72 []byte = []byte(`<a href="/reply/delete/submit/`)
|
||||||
var topic_73 []byte = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>`)
|
var topic_73 []byte = []byte(`" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>`)
|
||||||
var topic_74 []byte = []byte(`
|
var topic_74 []byte = []byte(`
|
||||||
<a class="mod_button" href="/report/submit/`)
|
<a href="/report/submit/`)
|
||||||
var topic_75 []byte = []byte(`?session=`)
|
var topic_75 []byte = []byte(`?session=`)
|
||||||
var topic_76 []byte = []byte(`&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a>
|
var topic_76 []byte = []byte(`&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a>
|
||||||
|
|
||||||
|
@ -210,8 +210,8 @@ var topic_78 []byte = []byte(`</a><a class="username hide_on_micro like_count_la
|
||||||
var topic_79 []byte = []byte(`<a class="username hide_on_micro user_tag">`)
|
var topic_79 []byte = []byte(`<a class="username hide_on_micro user_tag">`)
|
||||||
var topic_80 []byte = []byte(`</a>`)
|
var topic_80 []byte = []byte(`</a>`)
|
||||||
var topic_81 []byte = []byte(`<a class="username hide_on_micro level">`)
|
var topic_81 []byte = []byte(`<a class="username hide_on_micro level">`)
|
||||||
var topic_82 []byte = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level">`)
|
var topic_82 []byte = []byte(`</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>`)
|
||||||
var topic_83 []byte = []byte(`</a>
|
var topic_83 []byte = []byte(`
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
<div class="colstack_item rowmenu">
|
<div class="colstack_item rowmenu">
|
||||||
<div class="rowitem passive"><a href="/panel/logs/mod/">Moderation Logs</a></div>
|
<div class="rowitem passive"><a href="/panel/logs/mod/">Moderation Logs</a></div>
|
||||||
{{if .CurrentUser.Perms.ViewAdminLogs}}<div class="rowitem passive"><a>Administration Logs</a></div>{{end}}
|
{{if .CurrentUser.Perms.ViewAdminLogs}}<div class="rowitem passive"><a>Administration Logs</a></div>{{end}}
|
||||||
{{if .CurrentUser.Perms.ViewAdminLogs}}<div class="rowitem passive"><a>System Logs</a></div>{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
{{template "panel-inner-menu.html" . }}
|
{{template "panel-inner-menu.html" . }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="panel_forums" class="colstack_item rowlist">
|
<div id="panel_forums" class="colstack_item rowlist">
|
||||||
{{range .ItemList}}
|
{{range .ItemList}}
|
||||||
<div class="rowitem editable_parent" style="{{if eq .ID 1}}border-bottom-style:solid;{{end}}">
|
<div class="rowitem editable_parent{{if eq .ID 1}} builtin_forum_divider{{end}}">
|
||||||
<span class="panel_floater">
|
<span class="panel_floater">
|
||||||
<span data-field="forum_active" data-type="list" class="panel_tag editable_block forum_active {{if .Active}}forum_active_Show" data-value="1{{else}}forum_active_Hide" data-value="0{{end}}" title="Hidden"></span>
|
<span data-field="forum_active" data-type="list" class="panel_tag editable_block forum_active {{if .Active}}forum_active_Show" data-value="1{{else}}forum_active_Hide" data-value="0{{end}}" title="Hidden"></span>
|
||||||
<span data-field="forum_preset" data-type="list" data-value="{{.Preset}}" class="panel_tag editable_block forum_preset forum_preset_{{.Preset}}" title="{{.PresetLang}}"></span>
|
<span data-field="forum_preset" data-type="list" data-value="{{.Preset}}" class="panel_tag editable_block forum_preset forum_preset_{{.Preset}}" title="{{.PresetLang}}"></span>
|
||||||
|
@ -24,7 +24,8 @@
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span style="float: left;">
|
<span style="float: left;">
|
||||||
<a data-field="forum_name" data-type="text" class="editable_block forum_name" style="{{if not .Active}}color:#707070;{{end}}">{{.Name}}</a>
|
{{/** TO-DO: Make sure the forum_active_name class is set and unset when the activity status of this forum is changed **/}}
|
||||||
|
<a data-field="forum_name" data-type="text" class="editable_block forum_name{{if not .Active}} forum_active_name{{end}}">{{.Name}}</a>
|
||||||
</span>
|
</span>
|
||||||
<br /><span data-field="forum_desc" data-type="text" class="editable_block forum_desc rowsmall">{{.Desc}}</span>
|
<br /><span data-field="forum_desc" data-type="text" class="editable_block forum_desc rowsmall">{{.Desc}}</span>
|
||||||
<div style="clear: both;"></div>
|
<div style="clear: both;"></div>
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
<div class="colstack_item rowmenu">
|
<div class="colstack_item rowmenu">
|
||||||
<div class="rowitem passive"><a href="/panel/logs/mod/">Moderation Logs</a></div>
|
<div class="rowitem passive"><a href="/panel/logs/mod/">Moderation Logs</a></div>
|
||||||
{{if .CurrentUser.Perms.ViewAdminLogs}}<div class="rowitem passive"><a>Administration Logs</a></div>{{end}}
|
{{if .CurrentUser.Perms.ViewAdminLogs}}<div class="rowitem passive"><a>Administration Logs</a></div>{{end}}
|
||||||
{{if .CurrentUser.Perms.ViewAdminLogs}}<div class="rowitem passive"><a>System Logs</a></div>{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
{{template "panel-inner-menu.html" . }}
|
{{template "panel-inner-menu.html" . }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,5 +27,14 @@
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
{{if gt .LastPage 1}}
|
||||||
|
<div class="pageset">
|
||||||
|
{{if gt .Page 1}}<div class="pageitem"><a href="?page={{subtract .Page 1}}">Prev</a></div>{{end}}
|
||||||
|
{{range .PageList}}
|
||||||
|
<div class="pageitem"><a href="?page={{.}}">{{.}}</a></div>
|
||||||
|
{{end}}
|
||||||
|
{{if ne .LastPage .Page}}<div class="pageitem"><a href="?page={{add .Page 1}}">Next</a></div>{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{template "footer.html" . }}
|
{{template "footer.html" . }}
|
||||||
|
|
|
@ -19,5 +19,14 @@
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
{{if gt .LastPage 1}}
|
||||||
|
<div class="pageset">
|
||||||
|
{{if gt .Page 1}}<div class="pageitem"><a href="?page={{subtract .Page 1}}">Prev</a></div>{{end}}
|
||||||
|
{{range .PageList}}
|
||||||
|
<div class="pageitem"><a href="?page={{.}}">{{.}}</a></div>
|
||||||
|
{{end}}
|
||||||
|
{{if ne .LastPage .Page}}<div class="pageitem"><a href="?page={{add .Page 1}}">Next</a></div>{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{template "footer.html" . }}
|
{{template "footer.html" . }}
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
{{if .CurrentUser.Perms.DeleteTopic}}<a href='/topic/delete/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username trash_label"></button></a>{{end}}
|
{{if .CurrentUser.Perms.DeleteTopic}}<a href='/topic/delete/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Delete Topic"><button class="username trash_label"></button></a>{{end}}
|
||||||
|
|
||||||
{{if .CurrentUser.Perms.PinTopic}}{{if .Topic.Sticky}}<a class="mod_button" href='/topic/unstick/submit/{{.Topic.ID}}' style="font-weight:normal;" title="Unpin Topic"><button class="username unpin_label"></button></a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username pin_label"></button></a>{{end}}{{end}}
|
{{if .CurrentUser.Perms.PinTopic}}{{if .Topic.Sticky}}<a class="mod_button" href='/topic/unstick/submit/{{.Topic.ID}}' style="font-weight:normal;" title="Unpin Topic"><button class="username unpin_label"></button></a>{{else}}<a href='/topic/stick/submit/{{.Topic.ID}}' class="mod_button" style="font-weight:normal;" title="Pin Topic"><button class="username pin_label"></button></a>{{end}}{{end}}
|
||||||
<a class="mod_button" href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username flag_label"></button></a>
|
<a href="/report/submit/{{.Topic.ID}}?session={{.CurrentUser.Session}}&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic"><button class="username flag_label"></button></a>
|
||||||
|
|
||||||
{{if .Topic.LikeCount}}<a class="username hide_on_micro like_count">{{.Topic.LikeCount}}</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>{{end}}
|
{{if .Topic.LikeCount}}<a class="username hide_on_micro like_count">{{.Topic.LikeCount}}</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>{{end}}
|
||||||
|
|
||||||
|
@ -65,11 +65,11 @@
|
||||||
{{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>{{end}}
|
{{if $.CurrentUser.Perms.EditReply}}<a href="/reply/edit/submit/{{.ID}}" class="mod_button" title="Edit Reply"><button class="username edit_item edit_label"></button></a>{{end}}
|
||||||
|
|
||||||
{{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>{{end}}
|
{{if $.CurrentUser.Perms.DeleteReply}}<a href="/reply/delete/submit/{{.ID}}" class="mod_button" title="Delete Reply"><button class="username delete_item trash_label"></button></a>{{end}}
|
||||||
<a class="mod_button" href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a>
|
<a href="/report/submit/{{.ID}}?session={{$.CurrentUser.Session}}&type=reply" class="mod_button report_item" title="Flag Reply"><button class="username report_item flag_label"></button></a>
|
||||||
|
|
||||||
{{if .LikeCount}}<a class="username hide_on_micro like_count">{{.LikeCount}}</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>{{end}}
|
{{if .LikeCount}}<a class="username hide_on_micro like_count">{{.LikeCount}}</a><a class="username hide_on_micro like_count_label" title="Like Count"></a>{{end}}
|
||||||
|
|
||||||
{{if .Tag}}<a class="username hide_on_micro user_tag">{{.Tag}}</a>{{else}}<a class="username hide_on_micro level">{{.Level}}</a><a class="username hide_on_micro level_label" style="float:right;" title="Level">{{end}}</a>
|
{{if .Tag}}<a class="username hide_on_micro user_tag">{{.Tag}}</a>{{else}}<a class="username hide_on_micro level">{{.Level}}</a><a class="username hide_on_micro level_label" style="float:right;" title="Level"></a>{{end}}
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -60,6 +60,8 @@
|
||||||
.forum_active_Hide:before { content: "Hidden"; }
|
.forum_active_Hide:before { content: "Hidden"; }
|
||||||
.forum_active_Hide + .forum_preset:before { content: " | "; }
|
.forum_active_Hide + .forum_preset:before { content: " | "; }
|
||||||
.forum_active_Show { display: none !important; }
|
.forum_active_Show { display: none !important; }
|
||||||
|
.forum_active_name { color: #707070; }
|
||||||
|
.builtin_forum_divider { border-bottom-style: solid; }
|
||||||
|
|
||||||
.perm_preset_no_access:before { content: "No Access"; color: maroon; }
|
.perm_preset_no_access:before { content: "No Access"; color: maroon; }
|
||||||
.perm_preset_read_only:before { content: "Read Only"; color: green; }
|
.perm_preset_read_only:before { content: "Read Only"; color: green; }
|
||||||
|
|
|
@ -60,6 +60,8 @@
|
||||||
.forum_active_Hide:before { content: "Hidden"; }
|
.forum_active_Hide:before { content: "Hidden"; }
|
||||||
.forum_active_Hide + .forum_preset:before { content: " | "; }
|
.forum_active_Hide + .forum_preset:before { content: " | "; }
|
||||||
.forum_active_Show { display: none !important; }
|
.forum_active_Show { display: none !important; }
|
||||||
|
.forum_active_name { color: #707070; }
|
||||||
|
.builtin_forum_divider { border-bottom-style: solid; }
|
||||||
|
|
||||||
.perm_preset_no_access:before { content: "No Access"; color: maroon; }
|
.perm_preset_no_access:before { content: "No Access"; color: maroon; }
|
||||||
.perm_preset_read_only:before { content: "Read Only"; color: green; }
|
.perm_preset_read_only:before { content: "Read Only"; color: green; }
|
||||||
|
|
|
@ -86,8 +86,27 @@ li {
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectedAlert .alertList {
|
.selectedAlert .alertList {
|
||||||
/* Coming Soon. Hidden so it doesn't distract me while I design this theme */
|
display: block;
|
||||||
display: none;
|
position: absolute;
|
||||||
|
top: 44px;
|
||||||
|
float: left;
|
||||||
|
width: 200px;
|
||||||
|
z-index: 50;
|
||||||
|
right: 15%;
|
||||||
|
font-size: 13px;
|
||||||
|
background-color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alertItem {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
.alertItem.withAvatar {
|
||||||
|
height: 40px;
|
||||||
|
background-size: 48px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-color: rgb(61,61,61);
|
||||||
|
padding-left: 56px;
|
||||||
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
@ -142,19 +161,13 @@ a {
|
||||||
float: left;
|
float: left;
|
||||||
width: calc(70% - 24px);
|
width: calc(70% - 24px);
|
||||||
}
|
}
|
||||||
.colstack_left:empty, .colstack_right:empty {
|
.colstack_left:empty, .colstack_right:empty, .show_on_edit {
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.show_on_edit {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic_sticky_head {
|
.topic_sticky_head {
|
||||||
|
|
||||||
}
|
}
|
||||||
.topic_closed_head {
|
.topic_closed_head {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.post_item {
|
.post_item {
|
||||||
|
@ -585,6 +598,22 @@ input, select, textarea {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selectedAlert .alertList {
|
||||||
|
right: 10px;
|
||||||
|
top: 42px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.alertItem.withAvatar {
|
||||||
|
height: 28px;
|
||||||
|
background-size: 38px;
|
||||||
|
padding-left: 46px;
|
||||||
|
padding-top: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
#back {
|
#back {
|
||||||
width: calc(100% - 20px);
|
width: calc(100% - 20px);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,3 +22,10 @@
|
||||||
#forum_quick_perms .edit_fields {
|
#forum_quick_perms .edit_fields {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.forum_active_name {
|
||||||
|
color: rgb(200,200,200);
|
||||||
|
}
|
||||||
|
.builtin_forum_divider {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
|
@ -70,6 +70,8 @@
|
||||||
.forum_active_Hide:before { content: "Hidden"; }
|
.forum_active_Hide:before { content: "Hidden"; }
|
||||||
.forum_active_Hide + .forum_preset:before { content: " | "; }
|
.forum_active_Hide + .forum_preset:before { content: " | "; }
|
||||||
.forum_active_Show { display: none !important; }
|
.forum_active_Show { display: none !important; }
|
||||||
|
.forum_active_name { color: #707070; }
|
||||||
|
.builtin_forum_divider { border-bottom-style: solid; }
|
||||||
|
|
||||||
.perm_preset_no_access:before { content: "No Access"; color: maroon; }
|
.perm_preset_no_access:before { content: "No Access"; color: maroon; }
|
||||||
.perm_preset_read_only:before { content: "Read Only"; color: green; }
|
.perm_preset_read_only:before { content: "Read Only"; color: green; }
|
||||||
|
|
|
@ -58,6 +58,8 @@
|
||||||
.forum_active_Hide:before { content: "Hidden"; }
|
.forum_active_Hide:before { content: "Hidden"; }
|
||||||
.forum_active_Hide + .forum_preset:before { content: " | "; }
|
.forum_active_Hide + .forum_preset:before { content: " | "; }
|
||||||
.forum_active_Show { display: none !important; }
|
.forum_active_Show { display: none !important; }
|
||||||
|
.forum_active_name { color: #707070; }
|
||||||
|
.builtin_forum_divider { border-bottom-style: solid; }
|
||||||
|
|
||||||
.perm_preset_no_access:before { content: "No Access"; color: maroon; }
|
.perm_preset_no_access:before { content: "No Access"; color: maroon; }
|
||||||
.perm_preset_read_only:before { content: "Read Only"; color: green; }
|
.perm_preset_read_only:before { content: "Read Only"; color: green; }
|
||||||
|
|
|
@ -62,6 +62,8 @@
|
||||||
.forum_preset_all, .forum_preset_custom, .forum_preset_ { display: none !important; }
|
.forum_preset_all, .forum_preset_custom, .forum_preset_ { display: none !important; }
|
||||||
.forum_active_Hide:before { content: "🕵️"; }
|
.forum_active_Hide:before { content: "🕵️"; }
|
||||||
.forum_active_Show { display: none !important; }
|
.forum_active_Show { display: none !important; }
|
||||||
|
.forum_active_name { color: #707070; }
|
||||||
|
.builtin_forum_divider { border-bottom-style: solid; }
|
||||||
|
|
||||||
.perm_preset_no_access:before { content: "No Access"; color: maroon; }
|
.perm_preset_no_access:before { content: "No Access"; color: maroon; }
|
||||||
.perm_preset_read_only:before { content: "Read Only"; color: green; }
|
.perm_preset_read_only:before { content: "Read Only"; color: green; }
|
||||||
|
|
Loading…
Reference in New Issue