Began work on the new theme, Cosora.

Added the ReplyStore and the ProfileReplyStore.
Added more allowed file extensions for attachments.
The tif, webp, and apng extensions are now recognised as images.
Added the Delete method to the Reply struct.
Added the Like method to the Reply struct.
Refactored the topic list avatars to make things easier on Cosora.
The attachment cap should now work properly on topics.
You can now attach files to replies.
The Markdown parser now ignores URLs rather than mangling them.
Fixed a bug where themes weren't able to register custom resources.
Added the ability to embed images.
Added the ability to embed videos.
Made the requirements for URLs looser.

Misc improvements to the themes and templates.
This commit is contained in:
Azareal 2017-10-12 04:24:14 +01:00
parent 22650aad27
commit 97860d4f79
52 changed files with 1151 additions and 2635 deletions

View File

@ -39,6 +39,9 @@ func gloinit() error {
return err return err
} }
rstore = NewSQLReplyStore()
prstore = NewSQLProfileReplyStore()
dbProd = db dbProd = db
//db_test, err = sql.Open("testdb","") //db_test, err = sql.Open("testdb","")
//if err != nil { //if err != nil {

BIN
images/quick-topics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

15
main.go
View File

@ -44,12 +44,16 @@ type StringList []string
// ? - Should we allow users to upload .php or .go files? It could cause security issues. We could store them with a mangled extension to render them inert // ? - Should we allow users to upload .php or .go files? It could cause security issues. We could store them with a mangled extension to render them inert
// TODO: Let admins manage this from the Control Panel // TODO: Let admins manage this from the Control Panel
var allowedFileExts = StringList{ var allowedFileExts = StringList{
"png", "jpg", "jpeg", "svg", "bmp", "gif", "png", "jpg", "jpeg", "svg", "bmp", "gif", "tif", "webp", "apng", // images
"txt", "xml", "json", "yaml", "js", "py", "rb",
"mp3", "mp4", "avi", "wmv", "txt", "xml", "json", "yaml", "toml", "ini", "md", "html", "rtf", "js", "py", "rb", "css", "scss", "less", "java", "ts", "cs", "c", "cc", "cpp", "cxx", "C", "c++", "h", "hh", "hpp", "hxx", "h++", "rs", "rlib", "htaccess", "gitignore", // text
"mp3", "mp4", "avi", "wmv", "webm", // video
"otf", "woff2", "woff", "ttf", "eot", // fonts
} }
var imageFileExts = StringList{ var imageFileExts = StringList{
"png", "jpg", "jpeg", "svg", "bmp", "gif", "png", "jpg", "jpeg", "svg", "bmp", "gif", "tif", "webp", "apng",
} }
// TODO: Write a test for this // TODO: Write a test for this
@ -100,6 +104,9 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
rstore = NewSQLReplyStore()
prstore = NewSQLProfileReplyStore()
initTemplates() initTemplates()
err = initPhrases() err = initPhrases()

View File

@ -170,17 +170,18 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) {
} }
// Handle the file attachments // Handle the file attachments
// TODO: Stop duplicating this code
if user.Perms.UploadFiles { if user.Perms.UploadFiles {
var mpartFiles = r.MultipartForm.File files, ok := r.MultipartForm.File["upload_files"]
if len(mpartFiles) > 5 { if ok {
LocalError("You can't attach more than five files", w, r, user) if len(files) > 5 {
return LocalError("You can't attach more than five files", w, r, user)
} return
}
for _, fheaders := range r.MultipartForm.File { for _, file := range files {
for _, hdr := range fheaders { log.Print("file.Filename ", file.Filename)
log.Print("hdr.Filename ", hdr.Filename) extarr := strings.Split(file.Filename, ".")
extarr := strings.Split(hdr.Filename, ".")
if len(extarr) < 2 { if len(extarr) < 2 {
LocalError("Bad file", w, r, user) LocalError("Bad file", w, r, user)
return return
@ -195,11 +196,11 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) {
} }
ext = strings.ToLower(reg.ReplaceAllString(ext, "")) ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
if !allowedFileExts.Contains(ext) { if !allowedFileExts.Contains(ext) {
LocalError("You're not allowed this upload files with this extension", w, r, user) LocalError("You're not allowed to upload files with this extension", w, r, user)
return return
} }
infile, err := hdr.Open() infile, err := file.Open()
if err != nil { if err != nil {
LocalError("Upload failed", w, r, user) LocalError("Upload failed", w, r, user)
return return
@ -223,7 +224,7 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) {
} }
defer outfile.Close() defer outfile.Close()
infile, err = hdr.Open() infile, err = file.Open()
if err != nil { if err != nil {
LocalError("Upload failed", w, r, user) LocalError("Upload failed", w, r, user)
return return
@ -249,11 +250,20 @@ func routeTopicCreateSubmit(w http.ResponseWriter, r *http.Request, user User) {
} }
func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) { func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm() // TODO: Reduce this to 1MB for attachments for each file?
if err != nil { if r.ContentLength > int64(config.MaxRequestSize) {
PreError("Bad Form", w, r) size, unit := convertByteUnit(float64(config.MaxRequestSize))
CustomError("Your attachments are too big. Your files need to be smaller than "+strconv.Itoa(int(size))+unit+".", http.StatusExpectationFailed, "Error", w, r, user)
return return
} }
r.Body = http.MaxBytesReader(w, r.Body, int64(config.MaxRequestSize))
err := r.ParseMultipartForm(int64(megabyte))
if err != nil {
LocalError("Unable to parse the form", w, r, user)
return
}
tid, err := strconv.Atoi(r.PostFormValue("tid")) tid, err := strconv.Atoi(r.PostFormValue("tid"))
if err != nil { if err != nil {
PreError("Failed to convert the Topic ID", w, r) PreError("Failed to convert the Topic ID", w, r)
@ -279,6 +289,83 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
// Handle the file attachments
// TODO: Stop duplicating this code
if user.Perms.UploadFiles {
files, ok := r.MultipartForm.File["upload_files"]
if ok {
if len(files) > 5 {
LocalError("You can't attach more than five files", w, r, user)
return
}
for _, file := range files {
log.Print("file.Filename ", file.Filename)
extarr := strings.Split(file.Filename, ".")
if len(extarr) < 2 {
LocalError("Bad file", w, r, user)
return
}
ext := extarr[len(extarr)-1]
// TODO: Can we do this without a regex?
reg, err := regexp.Compile("[^A-Za-z0-9]+")
if err != nil {
LocalError("Bad file extension", w, r, user)
return
}
ext = strings.ToLower(reg.ReplaceAllString(ext, ""))
if !allowedFileExts.Contains(ext) {
LocalError("You're not allowed to upload files with this extension", w, r, user)
return
}
infile, err := file.Open()
if err != nil {
LocalError("Upload failed", w, r, user)
return
}
defer infile.Close()
hasher := sha256.New()
_, err = io.Copy(hasher, infile)
if err != nil {
LocalError("Upload failed [Hashing Failed]", w, r, user)
return
}
infile.Close()
checksum := hex.EncodeToString(hasher.Sum(nil))
filename := checksum + "." + ext
outfile, err := os.Create("." + "/attachs/" + filename)
if err != nil {
LocalError("Upload failed [File Creation Failed]", w, r, user)
return
}
defer outfile.Close()
infile, err = file.Open()
if err != nil {
LocalError("Upload failed", w, r, user)
return
}
defer infile.Close()
_, err = io.Copy(outfile, infile)
if err != nil {
LocalError("Upload failed [Copy Failed]", w, r, user)
return
}
_, err = addAttachmentStmt.Exec(topic.ParentID, "forums", tid, "replies", user.ID, filename)
if err != nil {
InternalError(err, w)
return
}
}
}
}
content := preparseMessage(html.EscapeString(r.PostFormValue("reply-content"))) content := preparseMessage(html.EscapeString(r.PostFormValue("reply-content")))
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr) ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil { if err != nil {
@ -286,25 +373,12 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
wcount := wordCount(content) _, err = rstore.Create(tid, content, ipaddress, topic.ParentID, user.ID)
_, err = createReplyStmt.Exec(tid, content, parseMessage(content, topic.ParentID, "forums"), ipaddress, wcount, user.ID)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
return return
} }
_, err = addRepliesToTopicStmt.Exec(1, user.ID, tid)
if err != nil {
InternalError(err, w)
return
}
// Flush the topic out of the cache
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(tid)
}
err = fstore.UpdateLastTopic(tid, user.ID, topic.ParentID) err = fstore.UpdateLastTopic(tid, user.ID, topic.ParentID)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
InternalError(err, w) InternalError(err, w)
@ -334,6 +408,8 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
} }
http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther) http.Redirect(w, r, "/topic/"+strconv.Itoa(tid), http.StatusSeeOther)
wcount := wordCount(content)
err = user.increasePostStats(wcount, false) err = user.increasePostStats(wcount, false)
if err != nil { if err != nil {
InternalError(err, w) InternalError(err, w)
@ -341,6 +417,7 @@ func routeCreateReply(w http.ResponseWriter, r *http.Request, user User) {
} }
} }
// TODO: Refactor this
func routeLikeTopic(w http.ResponseWriter, r *http.Request, user User) { func routeLikeTopic(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
@ -450,7 +527,7 @@ func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
reply, err := getReply(rid) reply, err := rstore.Get(rid)
if err == ErrNoRows { if err == ErrNoRows {
PreError("You can't like something which doesn't exist!", w, r) PreError("You can't like something which doesn't exist!", w, r)
return return
@ -484,15 +561,6 @@ func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
err = hasLikedReplyStmt.QueryRow(user.ID, rid).Scan(&rid)
if err != nil && err != ErrNoRows {
InternalError(err, w)
return
} else if err != ErrNoRows {
LocalError("You already liked this!", w, r, user)
return
}
_, err = users.Get(reply.CreatedBy) _, err = users.Get(reply.CreatedBy)
if err != nil && err != ErrNoRows { if err != nil && err != ErrNoRows {
LocalError("The target user doesn't exist", w, r, user) LocalError("The target user doesn't exist", w, r, user)
@ -502,15 +570,11 @@ func routeReplyLikeSubmit(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
score := 1 err = reply.Like(user.ID)
_, err = createLikeStmt.Exec(score, rid, "replies", user.ID) if err == ErrAlreadyLiked {
if err != nil { LocalError("You've already liked this!", w, r, user)
InternalError(err, w)
return return
} } else if err != nil {
_, err = addLikesToReplyStmt.Exec(1, rid)
if err != nil {
InternalError(err, w) InternalError(err, w)
return return
} }
@ -612,7 +676,7 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemI
var fid = 1 var fid = 1
var title, content string var title, content string
if itemType == "reply" { if itemType == "reply" {
reply, err := getReply(itemID) reply, err := rstore.Get(itemID)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("We were unable to find the reported post", w, r, user) LocalError("We were unable to find the reported post", w, r, user)
return return
@ -633,7 +697,7 @@ func routeReportSubmit(w http.ResponseWriter, r *http.Request, user User, sitemI
title = "Reply: " + topic.Title title = "Reply: " + topic.Title
content = reply.Content + "\n\nOriginal Post: #rid-" + strconv.Itoa(itemID) content = reply.Content + "\n\nOriginal Post: #rid-" + strconv.Itoa(itemID)
} else if itemType == "user-reply" { } else if itemType == "user-reply" {
userReply, err := getUserReply(itemID) userReply, err := prstore.Get(itemID)
if err == ErrNoRows { if err == ErrNoRows {
LocalError("We weren't able to find the reported post", w, r, user) LocalError("We weren't able to find the reported post", w, r, user)
return return

View File

@ -377,6 +377,58 @@ func TestGroupStore(t *testing.T) {
} }
} }
func TestReplyStore(t *testing.T) {
if !gloinited {
gloinit()
}
if !pluginsInited {
initPlugins()
}
reply, err := rstore.Get(-1)
if err == nil {
t.Error("RID #-1 shouldn't exist")
}
reply, err = rstore.Get(0)
if err == nil {
t.Error("RID #0 shouldn't exist")
}
reply, err = rstore.Get(1)
if err != nil {
t.Fatal(err)
}
if reply.ID != 1 {
t.Error("RID #1 has the wrong ID. It should be 1 not " + strconv.Itoa(reply.ID))
}
if reply.ParentID != 1 {
t.Error("The parent topic of RID #1 should be 1 not " + strconv.Itoa(reply.ParentID))
}
if reply.CreatedBy != 1 {
t.Error("The creator of RID #1 should be 1 not " + strconv.Itoa(reply.CreatedBy))
}
}
func TestProfileReplyStore(t *testing.T) {
if !gloinited {
gloinit()
}
if !pluginsInited {
initPlugins()
}
_, err := prstore.Get(-1)
if err == nil {
t.Error("RID #-1 shouldn't exist")
}
_, err = prstore.Get(0)
if err == nil {
t.Error("RID #0 shouldn't exist")
}
}
func TestSlugs(t *testing.T) { func TestSlugs(t *testing.T) {
var res string var res string
var msgList []MEPair var msgList []MEPair

View File

@ -387,6 +387,7 @@ func routeReplyEditSubmit(w http.ResponseWriter, r *http.Request, user User) {
} }
} }
// TODO: Refactor this
// TODO: Disable stat updates in posts handled by plugin_socialgroups // TODO: Disable stat updates in posts handled by plugin_socialgroups
func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user User) { func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user User) {
err := r.ParseForm() err := r.ParseForm()
@ -402,7 +403,7 @@ func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
reply, err := getReply(rid) reply, err := rstore.Get(rid)
if err == ErrNoRows { if err == ErrNoRows {
PreErrorJSQ("The reply you tried to delete doesn't exist.", w, r, isJs) PreErrorJSQ("The reply you tried to delete doesn't exist.", w, r, isJs)
return return
@ -431,11 +432,12 @@ func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
_, err = deleteReplyStmt.Exec(rid) err = reply.Delete()
if err != nil { if err != nil {
InternalErrorJSQ(err, w, r, isJs) InternalErrorJSQ(err, w, r, isJs)
return return
} }
//log.Print("Reply #" + strconv.Itoa(rid) + " was deleted by User #" + strconv.Itoa(user.ID)) //log.Print("Reply #" + strconv.Itoa(rid) + " was deleted by User #" + strconv.Itoa(user.ID))
if !isJs { if !isJs {
//http.Redirect(w,r, "/topic/" + strconv.Itoa(tid), http.StatusSeeOther) //http.Redirect(w,r, "/topic/" + strconv.Itoa(tid), http.StatusSeeOther)
@ -455,24 +457,15 @@ func routeReplyDeleteSubmit(w http.ResponseWriter, r *http.Request, user User) {
InternalErrorJSQ(err, w, r, isJs) InternalErrorJSQ(err, w, r, isJs)
return return
} }
_, err = removeRepliesFromTopicStmt.Exec(1, reply.ParentID)
if err != nil {
InternalErrorJSQ(err, w, r, isJs)
}
ipaddress, _, err := net.SplitHostPort(r.RemoteAddr) ipaddress, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil { if err != nil {
LocalError("Bad IP", w, r, user) LocalErrorJSQ("Bad IP", w, r, user, isJs)
return return
} }
err = addModLog("delete", reply.ParentID, "reply", ipaddress, user.ID) err = addModLog("delete", reply.ParentID, "reply", ipaddress, user.ID)
if err != nil { if err != nil {
InternalError(err, w) InternalErrorJSQ(err, w, r, isJs)
return
}
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(reply.ParentID)
} }
} }
@ -570,7 +563,7 @@ func routeIps(w http.ResponseWriter, r *http.Request, user User) {
return return
} }
ip := r.FormValue("ip") var ip = r.FormValue("ip")
var uid int var uid int
var reqUserList = make(map[int]bool) var reqUserList = make(map[int]bool)

112
pages.go
View File

@ -611,13 +611,56 @@ func parseMessage(msg string, sectionID int, sectionType string /*, user User*/)
continue continue
} }
//log.Print("Normal URL")
outbytes = append(outbytes, msgbytes[lastItem:i]...) outbytes = append(outbytes, msgbytes[lastItem:i]...)
urlLen := partialURLBytesLen(msgbytes[i:]) urlLen := partialURLBytesLen(msgbytes[i:])
if msgbytes[i+urlLen] > 32 { // space and invisibles if msgbytes[i+urlLen] > 32 { // space and invisibles
//log.Print("INVALID URL")
//log.Print("msgbytes[i+urlLen]", msgbytes[i+urlLen])
//log.Print("string(msgbytes[i+urlLen])", string(msgbytes[i+urlLen]))
//log.Print("msgbytes[i:i+urlLen]", msgbytes[i:i+urlLen])
//log.Print("string(msgbytes[i:i+urlLen])", string(msgbytes[i:i+urlLen]))
outbytes = append(outbytes, invalidURL...) outbytes = append(outbytes, invalidURL...)
i += urlLen i += urlLen
continue continue
} }
media, ok := parseMediaBytes(msgbytes[i : i+urlLen])
if !ok {
outbytes = append(outbytes, invalidURL...)
i += urlLen
continue
}
if media.Type == "attach" {
outbytes = append(outbytes, imageOpen...)
outbytes = append(outbytes, []byte(media.URL+"?sectionID="+strconv.Itoa(sectionID)+"&sectionType="+sectionType)...)
outbytes = append(outbytes, imageOpen2...)
outbytes = append(outbytes, []byte(media.URL+"?sectionID="+strconv.Itoa(sectionID)+"&sectionType="+sectionType)...)
outbytes = append(outbytes, imageClose...)
i += urlLen
lastItem = i
continue
} else if media.Type == "image" {
outbytes = append(outbytes, imageOpen...)
outbytes = append(outbytes, []byte(media.URL)...)
outbytes = append(outbytes, imageOpen2...)
outbytes = append(outbytes, []byte(media.URL)...)
outbytes = append(outbytes, imageClose...)
i += urlLen
lastItem = i
continue
} else if media.Type == "raw" {
outbytes = append(outbytes, []byte(media.Body)...)
i += urlLen
lastItem = i
continue
} else if media.Type != "" {
outbytes = append(outbytes, unknownMedia...)
i += urlLen
continue
}
outbytes = append(outbytes, urlOpen...) outbytes = append(outbytes, urlOpen...)
outbytes = append(outbytes, msgbytes[i:i+urlLen]...) outbytes = append(outbytes, msgbytes[i:i+urlLen]...)
outbytes = append(outbytes, urlOpen2...) outbytes = append(outbytes, urlOpen2...)
@ -649,7 +692,7 @@ func parseMessage(msg string, sectionID int, sectionType string /*, user User*/)
continue continue
} }
if media.Type == "image" { if media.Type == "attach" {
outbytes = append(outbytes, imageOpen...) outbytes = append(outbytes, imageOpen...)
outbytes = append(outbytes, []byte(media.URL+"?sectionID="+strconv.Itoa(sectionID)+"&sectionType="+sectionType)...) outbytes = append(outbytes, []byte(media.URL+"?sectionID="+strconv.Itoa(sectionID)+"&sectionType="+sectionType)...)
outbytes = append(outbytes, imageOpen2...) outbytes = append(outbytes, imageOpen2...)
@ -658,6 +701,20 @@ func parseMessage(msg string, sectionID int, sectionType string /*, user User*/)
i += urlLen i += urlLen
lastItem = i lastItem = i
continue continue
} else if media.Type == "image" {
outbytes = append(outbytes, imageOpen...)
outbytes = append(outbytes, []byte(media.URL)...)
outbytes = append(outbytes, imageOpen2...)
outbytes = append(outbytes, []byte(media.URL)...)
outbytes = append(outbytes, imageClose...)
i += urlLen
lastItem = i
continue
} else if media.Type == "raw" {
outbytes = append(outbytes, []byte(media.Body)...)
i += urlLen
lastItem = i
continue
} else if media.Type != "" { } else if media.Type != "" {
outbytes = append(outbytes, unknownMedia...) outbytes = append(outbytes, unknownMedia...)
i += urlLen i += urlLen
@ -730,9 +787,9 @@ func validateURLBytes(data []byte) bool {
i = 2 i = 2
} }
// ? - There should only be one : and that's only if the URL is on a non-standard port // ? - There should only be one : and that's only if the URL is on a non-standard port. Same for ?s.
for ; datalen > i; i++ { for ; datalen > i; i++ {
if data[i] != '\\' && data[i] != '_' && data[i] != ':' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) { if data[i] != '\\' && data[i] != '_' && data[i] != ':' && data[i] != '?' && data[i] != '&' && data[i] != '=' && data[i] != ';' && data[i] != '@' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
return false return false
} }
} }
@ -756,9 +813,9 @@ func validatedURLBytes(data []byte) (url []byte) {
i = 2 i = 2
} }
// ? - There should only be one : and that's only if the URL is on a non-standard port // ? - There should only be one : and that's only if the URL is on a non-standard port. Same for ?s.
for ; datalen > i; i++ { for ; datalen > i; i++ {
if data[i] != '\\' && data[i] != '_' && data[i] != ':' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) { if data[i] != '\\' && data[i] != '_' && data[i] != ':' && data[i] != '?' && data[i] != '&' && data[i] != '=' && data[i] != ';' && data[i] != '@' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
return invalidURL return invalidURL
} }
} }
@ -785,9 +842,9 @@ func partialURLBytes(data []byte) (url []byte) {
i = 2 i = 2
} }
// ? - There should only be one : and that's only if the URL is on a non-standard port // ? - There should only be one : and that's only if the URL is on a non-standard port. Same for ?s.
for ; end >= i; i++ { for ; end >= i; i++ {
if data[i] != '\\' && data[i] != '_' && data[i] != ':' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) { if data[i] != '\\' && data[i] != '_' && data[i] != ':' && data[i] != '?' && data[i] != '&' && data[i] != '=' && data[i] != ';' && data[i] != '@' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
end = i end = i
} }
} }
@ -814,9 +871,9 @@ func partialURLBytesLen(data []byte) int {
i = 2 i = 2
} }
// ? - There should only be one : and that's only if the URL is on a non-standard port // ? - There should only be one : and that's only if the URL is on a non-standard port. Same for ?s.
for ; datalen > i; i++ { for ; datalen > i; i++ {
if data[i] != '\\' && data[i] != '_' && data[i] != ':' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) { if data[i] != '\\' && data[i] != '_' && data[i] != ':' && data[i] != '?' && data[i] != '&' && data[i] != '=' && data[i] != ';' && data[i] != '@' && !(data[i] > 44 && data[i] < 58) && !(data[i] > 64 && data[i] < 91) && !(data[i] > 96 && data[i] < 123) {
//log.Print("Bad Character: ", data[i]) //log.Print("Bad Character: ", data[i])
return i return i
} }
@ -828,6 +885,7 @@ func partialURLBytesLen(data []byte) int {
type MediaEmbed struct { type MediaEmbed struct {
Type string //image Type string //image
URL string URL string
Body string
} }
// TODO: Write a test for this // TODO: Write a test for this
@ -846,6 +904,8 @@ func parseMediaBytes(data []byte) (media MediaEmbed, ok bool) {
port := url.Port() port := url.Port()
//log.Print("hostname ", hostname) //log.Print("hostname ", hostname)
//log.Print("scheme ", scheme) //log.Print("scheme ", scheme)
query := url.Query()
//log.Printf("query %+v\n", query)
var samesite = hostname == "localhost" || hostname == site.URL var samesite = hostname == "localhost" || hostname == site.URL
if samesite { if samesite {
@ -870,15 +930,47 @@ func parseMediaBytes(data []byte) (media MediaEmbed, ok bool) {
if len(pathFrags) >= 2 { if len(pathFrags) >= 2 {
if samesite && pathFrags[1] == "attachs" && (scheme == "http" || scheme == "https") { if samesite && pathFrags[1] == "attachs" && (scheme == "http" || scheme == "https") {
//log.Print("Attachment") //log.Print("Attachment")
media.Type = "image" media.Type = "attach"
var sport string var sport string
// ? - Assumes the sysadmin hasn't mixed up the two standard ports // ? - Assumes the sysadmin hasn't mixed up the two standard ports
if port != "443" && port != "80" { if port != "443" && port != "80" {
sport = ":" + port sport = ":" + port
} }
media.URL = scheme + "://" + hostname + sport + path media.URL = scheme + "://" + hostname + sport + path
return media, true
} }
} }
// ? - I don't think this hostname will hit every YT domain
// TODO: Make this a more customisable handler rather than hard-coding it in here
if hostname == "www.youtube.com" && path == "/watch" {
video, ok := query["v"]
if ok && len(video) >= 1 && video[0] != "" {
media.Type = "raw"
// TODO: Filter the URL to make sure no nasties end up in there
media.Body = "<iframe class='postIframe' src='https://www.youtube-nocookie.com/embed/" + video[0] + "' frameborder='0' allowfullscreen></iframe>"
return media, true
}
}
lastFrag := pathFrags[len(pathFrags)-1]
if lastFrag != "" {
// TODO: Write a function for getting the file extension of a string
extarr := strings.Split(lastFrag, ".")
if len(extarr) >= 2 {
ext := extarr[len(extarr)-1]
if imageFileExts.Contains(ext) {
media.Type = "image"
var sport string
if port != "443" && port != "80" {
sport = ":" + port
}
media.URL = scheme + "://" + hostname + sport + path
return media, true
}
}
}
return media, true return media, true
} }

View File

@ -1,8 +1,10 @@
package main package main
//import "fmt" //import "fmt"
import "regexp" import (
import "strings" "regexp"
"strings"
)
var markdownMaxDepth = 25 // How deep the parser will go when parsing Markdown strings var markdownMaxDepth = 25 // How deep the parser will go when parsing Markdown strings
var markdownUnclosedElement []byte var markdownUnclosedElement []byte
@ -87,6 +89,15 @@ func _markdownParse(msg string, n int) string {
//log.Print(" ")*/ //log.Print(" ")*/
switch msg[index] { switch msg[index] {
// TODO: Do something slightly less hacky for skipping URLs
case '/':
if len(msg) > (index+2) && msg[index+1] == '/' {
for ; index < len(msg) && msg[index] != ' '; index++ {
}
index--
continue
}
case '_': case '_':
var startIndex = index var startIndex = index
if (index + 1) >= len(msg) { if (index + 1) >= len(msg) {

37
public/EQCSS.min.js vendored Normal file
View File

@ -0,0 +1,37 @@
// EQCSS / Tommy Hodgins, Maxime Euzière / MIT license
// version 1.7.0
(function(root,factory){if(typeof define==="function"&&define.amd)define([],factory);else if(typeof module==="object"&&module.exports)module.exports=factory();else root.EQCSS=factory()})(this,function(){var EQCSS={data:[]};EQCSS.load=function(){var styles=document.getElementsByTagName("style");for(var i=0;i<styles.length;i++)if(styles[i].getAttribute("data-eqcss-read")===null){styles[i].setAttribute("data-eqcss-read","true");EQCSS.process(styles[i].innerHTML)}var script=document.getElementsByTagName("script");
for(var i=0;i<script.length;i++)if(script[i].getAttribute("data-eqcss-read")===null&&script[i].type==="text/eqcss"){if(script[i].src)(function(){var xhr=new XMLHttpRequest;xhr.open("GET",script[i].src,true);xhr.send(null);xhr.onreadystatechange=function(){EQCSS.process(xhr.responseText)}})();else EQCSS.process(script[i].innerHTML);script[i].setAttribute("data-eqcss-read","true")}var link=document.getElementsByTagName("link");for(i=0;i<link.length;i++)if(link[i].getAttribute("data-eqcss-read")===null&&
link[i].rel==="stylesheet"){if(link[i].href)(function(){var xhr=new XMLHttpRequest;xhr.open("GET",link[i].href,true);xhr.send(null);xhr.onreadystatechange=function(){EQCSS.process(xhr.responseText)}})();link[i].setAttribute("data-eqcss-read","true")}};EQCSS.parse=function(code){var parsed_queries=new Array;code=code.replace(/\s+/g," ");code=code.replace(/\/\*[\w\W]*?\*\//g,"");code=code.replace(/@element/g,"\n@element");code=code.replace(/(@element.*?\{([^}]*?\{[^}]*?\}[^}]*?)*\}).*/g,"$1");code.replace(/(@element.*(?!@element))/g,
function(string,query){var dataEntry={};query.replace(/(@element)\s*(".*?"|'.*?'|.*?)\s*(and\s*\(|{)/g,function(string,atrule,selector,extra){selector=selector.replace(/^\s?['](.*)[']/,"$1");selector=selector.replace(/^\s?["](.*)["]/,"$1");dataEntry.selector=selector});dataEntry.conditions=[];query.replace(/and ?\( ?([^:]*) ?: ?([^)]*) ?\)/g,function(string,measure,value){var unit=null;unit=value.replace(/^(\d*\.?\d+)(\D+)$/,"$2");if(unit===value)unit=null;value=value.replace(/^(\d*\.?\d+)\D+$/,"$1");
dataEntry.conditions.push({measure:measure,value:value,unit:unit})});query.replace(/{(.*)}/g,function(string,style){dataEntry.style=style});parsed_queries.push(dataEntry)});return parsed_queries};EQCSS.register=function(queries){if(Object.prototype.toString.call(queries)==="[object Object]"){EQCSS.data.push(queries);EQCSS.apply()}if(Object.prototype.toString.call(queries)==="[object Array]"){for(var i=0;i<queries.length;i++)EQCSS.data.push(queries[i]);EQCSS.apply()}};EQCSS.process=function(code){var queries=
EQCSS.parse(code);return EQCSS.register(queries)};EQCSS.apply=function(){var i,j,k;var elements;var element_guid;var css_block;var element_guid_parent;var element_guid_prev;var element_guid_next;var css_code;var element_width,parent_width;var element_height,parent_height;var element_line_height;var test;var computed_style;var parent_computed_style;for(i=0;i<EQCSS.data.length;i++){elements=document.querySelectorAll(EQCSS.data[i].selector);for(j=0;j<elements.length;j++){element_guid="data-eqcss-"+i+
"-"+j;elements[j].setAttribute(element_guid,"");element_guid_parent="data-eqcss-"+i+"-"+j+"-parent";if(elements[j]!=document.documentElement)elements[j].parentNode.setAttribute(element_guid_parent,"");element_guid_prev="data-eqcss-"+i+"-"+j+"-prev";var prev_sibling=function(el){while(el=el.previousSibling)if(el.nodeType===1)return el}(elements[j]);if(prev_sibling)prev_sibling.setAttribute(element_guid_prev,"");element_guid_next="data-eqcss-"+i+"-"+j+"-next";var next_sibling=function(el){while(el=
el.nextSibling)if(el.nodeType===1)return el}(elements[j]);if(next_sibling)next_sibling.setAttribute(element_guid_next,"");css_block=document.querySelector("#"+element_guid);if(!css_block){css_block=document.createElement("style");css_block.id=element_guid;css_block.setAttribute("data-eqcss-read","true");document.querySelector("head").appendChild(css_block)}css_block=document.querySelector("#"+element_guid);test=true;test_conditions:for(k=0;k<EQCSS.data[i].conditions.length;k++){computed_style=window.getComputedStyle(elements[j],
null);parent_computed_style=null;if(elements[j]!=document.documentElement)parent_computed_style=window.getComputedStyle(elements[j].parentNode,null);var recomputed=false;if(EQCSS.data[i].conditions[k].unit==="vw"){recomputed=true;var value=parseInt(EQCSS.data[i].conditions[k].value);EQCSS.data[i].conditions[k].recomputed_value=value*window.innerWidth/100}else if(EQCSS.data[i].conditions[k].unit==="vh"){recomputed=true;var value=parseInt(EQCSS.data[i].conditions[k].value);EQCSS.data[i].conditions[k].recomputed_value=
value*window.innerHeight/100}else if(EQCSS.data[i].conditions[k].unit==="vmin"){recomputed=true;var value=parseInt(EQCSS.data[i].conditions[k].value);EQCSS.data[i].conditions[k].recomputed_value=value*Math.min(window.innerWidth,window.innerHeight)/100}else if(EQCSS.data[i].conditions[k].unit==="vmax"){recomputed=true;var value=parseInt(EQCSS.data[i].conditions[k].value);EQCSS.data[i].conditions[k].recomputed_value=value*Math.max(window.innerWidth,window.innerHeight)/100}else if(EQCSS.data[i].conditions[k].unit!=
null&&EQCSS.data[i].conditions[k].unit!="px"&&EQCSS.data[i].conditions[k].unit!="%"){var div=document.createElement("div");div.style.visibility="hidden";div.style.border="1px solid red";div.style.width=EQCSS.data[i].conditions[k].value+EQCSS.data[i].conditions[k].unit;var position=elements[j];if(elements[j]!=document.documentElement)position=elements[j].parentNode;position.appendChild(div);EQCSS.data[i].conditions[k].value=parseInt(window.getComputedStyle(div,null).getPropertyValue("width"));EQCSS.data[i].conditions[k].unit=
"px";position.removeChild(div)}var final_value=recomputed?EQCSS.data[i].conditions[k].recomputed_value:parseInt(EQCSS.data[i].conditions[k].value);switch(EQCSS.data[i].conditions[k].measure){case "min-width":if(recomputed===true||EQCSS.data[i].conditions[k].unit==="px"){element_width=parseInt(computed_style.getPropertyValue("width"));if(!(element_width>=final_value)){test=false;break test_conditions}}if(EQCSS.data[i].conditions[k].unit==="%"){element_width=parseInt(computed_style.getPropertyValue("width"));
parent_width=parseInt(parent_computed_style.getPropertyValue("width"));if(!(parent_width/element_width<=100/final_value)){test=false;break test_conditions}}break;case "max-width":if(recomputed===true||EQCSS.data[i].conditions[k].unit==="px"){element_width=parseInt(computed_style.getPropertyValue("width"));if(!(element_width<=final_value)){test=false;break test_conditions}}if(EQCSS.data[i].conditions[k].unit==="%"){element_width=parseInt(computed_style.getPropertyValue("width"));parent_width=parseInt(parent_computed_style.getPropertyValue("width"));
if(!(parent_width/element_width>=100/final_value)){test=false;break test_conditions}}break;case "min-height":if(recomputed===true||EQCSS.data[i].conditions[k].unit==="px"){element_height=parseInt(computed_style.getPropertyValue("height"));if(!(element_height>=final_value)){test=false;break test_conditions}}if(EQCSS.data[i].conditions[k].unit==="%"){element_height=parseInt(computed_style.getPropertyValue("height"));parent_height=parseInt(parent_computed_style.getPropertyValue("height"));if(!(parent_height/
element_height<=100/final_value)){test=false;break test_conditions}}break;case "max-height":if(recomputed===true||EQCSS.data[i].conditions[k].unit==="px"){element_height=parseInt(computed_style.getPropertyValue("height"));if(!(element_height<=final_value)){test=false;break test_conditions}}if(EQCSS.data[i].conditions[k].unit==="%"){element_height=parseInt(computed_style.getPropertyValue("height"));parent_height=parseInt(parent_computed_style.getPropertyValue("height"));if(!(parent_height/element_height>=
100/final_value)){test=false;break test_conditions}}break;case "min-scroll-x":var element=elements[j];var element_scroll=element.scrollLeft;if(!element.hasScrollListener)if(element===document.documentElement||element===document.body)window.addEventListener("scroll",function(){EQCSS.throttle();element.hasScrollListener=true});else element.addEventListener("scroll",function(){EQCSS.throttle();element.hasScrollListener=true});if(recomputed===true||EQCSS.data[i].conditions[k].unit==="px"){if(!(element_scroll>=
final_value)){test=false;break test_conditions}}else if(EQCSS.data[i].conditions[k].unit==="%"){var element_scroll_size=elements[j].scrollWidth;var element_size;if(elements[j]===document.documentElement||elements[j]===document.body)element_size=window.innerWidth;else element_size=parseInt(computed_style.getPropertyValue("width"));if(!(element_scroll/(element_scroll_size-element_size)*100>=final_value)){test=false;break test_conditions}}break;case "min-scroll-y":var element=elements[j];element_scroll=
elements[j].scrollTop;if(!element.hasScrollListener)if(element===document.documentElement||element===document.body)window.addEventListener("scroll",function(){EQCSS.throttle();element.hasScrollListener=true});else element.addEventListener("scroll",function(){EQCSS.throttle();element.hasScrollListener=true});if(recomputed===true||EQCSS.data[i].conditions[k].unit==="px"){if(!(element_scroll>=final_value)){test=false;break test_conditions}}else if(EQCSS.data[i].conditions[k].unit==="%"){var element_scroll_size=
elements[j].scrollHeight;var element_size;if(elements[j]===document.documentElement||elements[j]===document.body)element_size=window.innerHeight;else element_size=parseInt(computed_style.getPropertyValue("height"));if(!(element_scroll/(element_scroll_size-element_size)*100>=final_value)){test=false;break test_conditions}}break;case "max-scroll-x":var element=elements[j];element_scroll=elements[j].scrollLeft;if(!element.hasScrollListener)if(element===document.documentElement||element===document.body)window.addEventListener("scroll",
function(){EQCSS.throttle();element.hasScrollListener=true});else element.addEventListener("scroll",function(){EQCSS.throttle();element.hasScrollListener=true});if(recomputed===true||EQCSS.data[i].conditions[k].unit==="px"){if(!(element_scroll<=final_value)){test=false;break test_conditions}}else if(EQCSS.data[i].conditions[k].unit==="%"){var element_scroll_size=elements[j].scrollWidth;var element_size;if(elements[j]===document.documentElement||elements[j]===document.body)element_size=window.innerWidth;
else element_size=parseInt(computed_style.getPropertyValue("width"));if(!(element_scroll/(element_scroll_size-element_size)*100<=final_value)){test=false;break test_conditions}}break;case "max-scroll-y":var element=elements[j];element_scroll=elements[j].scrollTop;if(!element.hasScrollListener)if(element===document.documentElement||element===document.body)window.addEventListener("scroll",function(){EQCSS.throttle();element.hasScrollListener=true});else element.addEventListener("scroll",function(){EQCSS.throttle();
element.hasScrollListener=true});if(recomputed===true||EQCSS.data[i].conditions[k].unit==="px"){if(!(element_scroll<=final_value)){test=false;break test_conditions}}else if(EQCSS.data[i].conditions[k].unit==="%"){var element_scroll_size=elements[j].scrollHeight;var element_size;if(elements[j]===document.documentElement||elements[j]===document.body)element_size=window.innerHeight;else element_size=parseInt(computed_style.getPropertyValue("height"));if(!(element_scroll/(element_scroll_size-element_size)*
100<=final_value)){test=false;break test_conditions}}break;case "min-characters":if(elements[j].value){if(!(elements[j].value.length>=final_value)){test=false;break test_conditions}}else if(!(elements[j].textContent.length>=final_value)){test=false;break test_conditions}break;case "max-characters":if(elements[j].value){if(!(elements[j].value.length<=final_value)){test=false;break test_conditions}}else if(!(elements[j].textContent.length<=final_value)){test=false;break test_conditions}break;case "min-children":if(!(elements[j].children.length>=
final_value)){test=false;break test_conditions}break;case "max-children":if(!(elements[j].children.length<=final_value)){test=false;break test_conditions}break;case "min-lines":element_height=parseInt(computed_style.getPropertyValue("height"))-parseInt(computed_style.getPropertyValue("border-top-width"))-parseInt(computed_style.getPropertyValue("border-bottom-width"))-parseInt(computed_style.getPropertyValue("padding-top"))-parseInt(computed_style.getPropertyValue("padding-bottom"));element_line_height=
computed_style.getPropertyValue("line-height");if(element_line_height==="normal"){var element_font_size=parseInt(computed_style.getPropertyValue("font-size"));element_line_height=element_font_size*1.125}else element_line_height=parseInt(element_line_height);if(!(element_height/element_line_height>=final_value)){test=false;break test_conditions}break;case "max-lines":element_height=parseInt(computed_style.getPropertyValue("height"))-parseInt(computed_style.getPropertyValue("border-top-width"))-parseInt(computed_style.getPropertyValue("border-bottom-width"))-
parseInt(computed_style.getPropertyValue("padding-top"))-parseInt(computed_style.getPropertyValue("padding-bottom"));element_line_height=computed_style.getPropertyValue("line-height");if(element_line_height==="normal"){var element_font_size=parseInt(computed_style.getPropertyValue("font-size"));element_line_height=element_font_size*1.125}else element_line_height=parseInt(element_line_height);if(!(element_height/element_line_height+1<=final_value)){test=false;break test_conditions}break;case "orientation":if(EQCSS.data[i].conditions[k].value===
"square")if(!(elements[j].offsetWidth===elements[j].offsetHeight)){test=false;break test_conditions}if(EQCSS.data[i].conditions[k].value==="portrait")if(!(elements[j].offsetWidth<elements[j].offsetHeight)){test=false;break test_conditions}if(EQCSS.data[i].conditions[k].value==="landscape")if(!(elements[j].offsetHeight<elements[j].offsetWidth)){test=false;break test_conditions}break;case "min-aspect-ratio":var el_width=EQCSS.data[i].conditions[k].value.split("/")[0];var el_height=EQCSS.data[i].conditions[k].value.split("/")[1];
if(!(el_width/el_height<=elements[j].offsetWidth/elements[j].offsetHeight)){test=false;break test_conditions}break;case "max-aspect-ratio":var el_width=EQCSS.data[i].conditions[k].value.split("/")[0];var el_height=EQCSS.data[i].conditions[k].value.split("/")[1];if(!(elements[j].offsetWidth/elements[j].offsetHeight<=el_width/el_height)){test=false;break test_conditions}break}}if(test===true){css_code=EQCSS.data[i].style;css_code=css_code.replace(/eval\( *((".*?")|('.*?')) *\)/g,function(string,match){return EQCSS.tryWithEval(elements[j],
match)});css_code=css_code.replace(/(\$|eq_)this/gi,"["+element_guid+"]");css_code=css_code.replace(/(\$|eq_)parent/gi,"["+element_guid_parent+"]");css_code=css_code.replace(/(\$|eq_)prev/gi,"["+element_guid_prev+"]");css_code=css_code.replace(/(\$|eq_)next/gi,"["+element_guid_next+"]");css_code=css_code.replace(/(\$|eq_)root/gi,"html");css_code=css_code.replace(/(\d*\.?\d+)(?:\s*)(ew|eh|emin|emax)/gi,function(match,$1,$2){switch($2){case "ew":return elements[j].offsetWidth/100*$1+"px";break;case "eh":return elements[j].offsetHeight/
100*$1+"px";break;case "emin":return Math.min(elements[j].offsetWidth,elements[j].offsetHeight)/100*$1+"px";break;case "emax":return Math.max(elements[j].offsetWidth,elements[j].offsetHeight)/100*$1+"px";break}});try{css_block.innerHTML=css_code}catch(e){css_block.styleSheet.cssText=css_code}}else try{css_block.innerHTML=""}catch(e$0){css_block.styleSheet.cssText=""}}}};EQCSS.tryWithEval=function(element,string){var $it=element;var ret="";try{with($it)ret=eval(string.slice(1,-1))}catch(e){ret=""}return ret};
EQCSS.reset=function(){EQCSS.data=[];var style_tag=document.querySelectorAll('head style[id^="data-eqcss-"]');for(var i=0;i<style_tag.length;i++)style_tag[i].parentNode.removeChild(style_tag[i]);var tag=document.querySelectorAll("*");for(var j=0;j<tag.length;j++)for(var k=0;k<tag[j].attributes.length;k++)if(tag[j].attributes[k].name.indexOf("data-eqcss-")===0)tag[j].removeAttribute(tag[j].attributes[k].name)};EQCSS.domReady=function(fn){var done=false;var top=true;var doc=window.document;var root=
doc.documentElement;var modern=!~navigator.userAgent.indexOf("MSIE 8");var add=modern?"addEventListener":"attachEvent";var rem=modern?"removeEventListener":"detachEvent";var pre=modern?"":"on";var init=function(e){if(e.type==="readystatechange"&&doc.readyState!=="complete")return;(e.type==="load"?window:doc)[rem](pre+e.type,init,false);if(!done&&(done=true))fn.call(window,e.type||e)},poll=function(){try{root.doScroll("left")}catch(e){setTimeout(poll,50);return}init("poll")};if(doc.readyState==="complete")fn.call(window,
"lazy");else{if(!modern&&root.doScroll){try{top=!window.frameElement}catch(e){}if(top)poll()}doc[add](pre+"DOMContentLoaded",init,false);doc[add](pre+"readystatechange",init,false);window[add](pre+"load",init,false)}};var EQCSS_throttle_available=true;var EQCSS_throttle_queued=false;var EQCSS_mouse_down=false;var EQCSS_timeout=200;EQCSS.throttle=function(){if(EQCSS_throttle_available){EQCSS.apply();EQCSS_throttle_available=false;setTimeout(function(){EQCSS_throttle_available=true;if(EQCSS_throttle_queued){EQCSS_throttle_queued=
false;EQCSS.apply()}},EQCSS_timeout)}else EQCSS_throttle_queued=true};EQCSS.domReady(function(){EQCSS.load();EQCSS.throttle()});window.addEventListener("resize",EQCSS.throttle);window.addEventListener("input",EQCSS.throttle);window.addEventListener("click",EQCSS.throttle);window.addEventListener("mousedown",function(e){if(e.which===1)EQCSS_mouse_down=true});window.addEventListener("mousemove",function(){if(EQCSS_mouse_down)EQCSS.throttle()});window.addEventListener("mouseup",function(){EQCSS_mouse_down=
false;EQCSS.throttle()});function l(a){console.log(a)}return EQCSS});

View File

@ -448,7 +448,7 @@ $(document).ready(function(){
return hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('') return hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('')
}).then(function(hash) { }).then(function(hash) {
console.log("hash",hash); console.log("hash",hash);
let content = document.getElementById("topic_content") let content = document.getElementById("input_content")
console.log("content.value",content.value); console.log("content.value",content.value);
if(content.value == "") content.value = content.value + "//" + siteURL + "/attachs/" + hash + "." + ext; if(content.value == "") content.value = content.value + "//" + siteURL + "/attachs/" + hash + "." + ext;
@ -462,7 +462,7 @@ $(document).ready(function(){
} }
} }
var uploadFiles = document.getElementById("quick_topic_upload_files"); var uploadFiles = document.getElementById("upload_files");
if(uploadFiles != null) { if(uploadFiles != null) {
uploadFiles.addEventListener("change", uploadFileHandler, false); uploadFiles.addEventListener("change", uploadFileHandler, false);
} }

View File

@ -6,7 +6,11 @@
*/ */
package main package main
import "errors"
// ? - Should we add a reply store to centralise all the reply logic? Would this cover profile replies too or would that be seperate? // ? - Should we add a reply store to centralise all the reply logic? Would this cover profile replies too or would that be seperate?
var rstore ReplyStore
var prstore ProfileReplyStore
type ReplyUser struct { type ReplyUser struct {
ID int ID int
@ -50,18 +54,100 @@ type Reply struct {
LikeCount int LikeCount int
} }
var ErrAlreadyLiked = errors.New("You already liked this!")
// TODO: Write tests for this
// TODO: Wrap these queries in a transaction to make sure the state is consistent
func (reply *Reply) Like(uid int) (err error) {
var rid int // unused, just here to avoid mutating reply.ID
err = hasLikedReplyStmt.QueryRow(uid, reply.ID).Scan(&rid)
if err != nil && err != ErrNoRows {
return err
} else if err != ErrNoRows {
return ErrAlreadyLiked
}
score := 1
_, err = createLikeStmt.Exec(score, reply.ID, "replies", uid)
if err != nil {
return err
}
_, err = addLikesToReplyStmt.Exec(1, reply.ID)
return err
}
// TODO: Write tests for this
func (reply *Reply) Delete() error {
_, err := deleteReplyStmt.Exec(reply.ID)
if err != nil {
return err
}
_, err = removeRepliesFromTopicStmt.Exec(1, reply.ParentID)
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(reply.ParentID)
}
return err
}
// Copy gives you a non-pointer concurrency safe copy of the reply // Copy gives you a non-pointer concurrency safe copy of the reply
func (reply *Reply) Copy() Reply { func (reply *Reply) Copy() Reply {
return *reply return *reply
} }
func getReply(id int) (*Reply, error) { type ReplyStore interface {
Get(id int) (*Reply, error)
Create(tid int, content string, ipaddress string, fid int, uid int) (id int, err error)
}
type SQLReplyStore struct {
}
func NewSQLReplyStore() *SQLReplyStore {
return &SQLReplyStore{}
}
func (store *SQLReplyStore) Get(id int) (*Reply, error) {
reply := Reply{ID: id} reply := Reply{ID: id}
err := getReplyStmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress, &reply.LikeCount) err := getReplyStmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress, &reply.LikeCount)
return &reply, err return &reply, err
} }
func getUserReply(id int) (*Reply, error) { // TODO: Write a test for this
func (store *SQLReplyStore) Create(tid int, content string, ipaddress string, fid int, uid int) (id int, err error) {
wcount := wordCount(content)
res, err := createReplyStmt.Exec(tid, content, parseMessage(content, fid, "forums"), ipaddress, wcount, uid)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
if err != nil {
return 0, err
}
_, err = addRepliesToTopicStmt.Exec(1, uid, tid)
if err != nil {
return int(lastID), err
}
tcache, ok := topics.(TopicCache)
if ok {
tcache.CacheRemove(tid)
}
return int(lastID), err
}
type ProfileReplyStore interface {
Get(id int) (*Reply, error)
}
type SQLProfileReplyStore struct {
}
func NewSQLProfileReplyStore() *SQLProfileReplyStore {
return &SQLProfileReplyStore{}
}
func (store *SQLProfileReplyStore) Get(id int) (*Reply, error) {
reply := Reply{ID: id} reply := Reply{ID: id}
err := getUserReplyStmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress) err := getUserReplyStmt.QueryRow(id).Scan(&reply.ParentID, &reply.Content, &reply.CreatedBy, &reply.CreatedAt, &reply.LastEdit, &reply.LastEditBy, &reply.IPAddress)
return &reply, err return &reply, err

View File

@ -169,17 +169,15 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV
} }
headerVars.Stylesheets = append(headerVars.Stylesheets, headerVars.ThemeName+"/panel.css") headerVars.Stylesheets = append(headerVars.Stylesheets, headerVars.ThemeName+"/panel.css")
if len(themes[headerVars.ThemeName].Resources) != 0 { if len(themes[headerVars.ThemeName].Resources) > 0 {
rlist := themes[headerVars.ThemeName].Resources rlist := themes[headerVars.ThemeName].Resources
for _, resource := range rlist { for _, resource := range rlist {
if resource.Location == "global" || resource.Location == "panel" { if resource.Location == "global" || resource.Location == "panel" {
halves := strings.Split(resource.Name, ".") extarr := strings.Split(resource.Name, ".")
if len(halves) != 2 { ext := extarr[len(extarr)-1]
continue if ext == "css" {
}
if halves[1] == "css" {
headerVars.Stylesheets = append(headerVars.Stylesheets, resource.Name) headerVars.Stylesheets = append(headerVars.Stylesheets, resource.Name)
} else if halves[1] == "js" { } else if ext == "js" {
headerVars.Scripts = append(headerVars.Scripts, resource.Name) headerVars.Scripts = append(headerVars.Scripts, resource.Name)
} }
} }
@ -268,17 +266,15 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *
headerVars.NoticeList = append(headerVars.NoticeList, "Your account has been suspended. Some of your permissions may have been revoked.") headerVars.NoticeList = append(headerVars.NoticeList, "Your account has been suspended. Some of your permissions may have been revoked.")
} }
if len(themes[headerVars.ThemeName].Resources) != 0 { if len(themes[headerVars.ThemeName].Resources) > 0 {
rlist := themes[headerVars.ThemeName].Resources rlist := themes[headerVars.ThemeName].Resources
for _, resource := range rlist { for _, resource := range rlist {
if resource.Location == "global" || resource.Location == "frontend" { if resource.Location == "global" || resource.Location == "frontend" {
halves := strings.Split(resource.Name, ".") extarr := strings.Split(resource.Name, ".")
if len(halves) != 2 { ext := extarr[len(extarr)-1]
continue if ext == "css" {
}
if halves[1] == "css" {
headerVars.Stylesheets = append(headerVars.Stylesheets, resource.Name) headerVars.Stylesheets = append(headerVars.Stylesheets, resource.Name)
} else if halves[1] == "js" { } else if ext == "js" {
headerVars.Scripts = append(headerVars.Scripts, resource.Name) headerVars.Scripts = append(headerVars.Scripts, resource.Name)
} }
} }

View File

@ -56,12 +56,13 @@ if tmpl_forum_vars.CurrentUser.Loggedin {
w.Write(menu_3) w.Write(menu_3)
w.Write([]byte(tmpl_forum_vars.CurrentUser.Link)) w.Write([]byte(tmpl_forum_vars.CurrentUser.Link))
w.Write(menu_4) w.Write(menu_4)
w.Write([]byte(tmpl_forum_vars.CurrentUser.Session))
w.Write(menu_5) w.Write(menu_5)
} else { w.Write([]byte(tmpl_forum_vars.CurrentUser.Session))
w.Write(menu_6) w.Write(menu_6)
} } else {
w.Write(menu_7) w.Write(menu_7)
}
w.Write(menu_8)
w.Write(header_14) w.Write(header_14)
if tmpl_forum_vars.Header.Widgets.RightSidebar != "" { if tmpl_forum_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_15) w.Write(header_15)
@ -137,48 +138,54 @@ w.Write([]byte(item.Creator.Avatar))
w.Write(forum_27) w.Write(forum_27)
} }
w.Write(forum_28) w.Write(forum_28)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(forum_29)
w.Write([]byte(item.LastReplyAt))
w.Write(forum_30)
w.Write([]byte(item.Link)) w.Write([]byte(item.Link))
w.Write(forum_31) w.Write(forum_29)
w.Write([]byte(item.Title)) w.Write([]byte(item.Title))
w.Write(forum_32) w.Write(forum_30)
w.Write([]byte(item.Creator.Link)) w.Write([]byte(item.Creator.Link))
w.Write(forum_33) w.Write(forum_31)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Name))
w.Write(forum_34) w.Write(forum_32)
if item.IsClosed { if item.IsClosed {
w.Write(forum_35) w.Write(forum_33)
} }
if item.Sticky { if item.Sticky {
w.Write(forum_34)
}
w.Write(forum_35)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(forum_36) w.Write(forum_36)
} if item.Sticky {
w.Write(forum_37) w.Write(forum_37)
if item.LastUser.Avatar != "" { } else {
if item.IsClosed {
w.Write(forum_38) w.Write(forum_38)
w.Write([]byte(item.LastUser.Avatar))
w.Write(forum_39)
} }
}
w.Write(forum_39)
if item.LastUser.Avatar != "" {
w.Write(forum_40) w.Write(forum_40)
w.Write([]byte(item.LastUser.Link)) w.Write([]byte(item.LastUser.Avatar))
w.Write(forum_41) w.Write(forum_41)
w.Write([]byte(item.LastUser.Name)) }
w.Write(forum_42) w.Write(forum_42)
w.Write([]byte(item.LastReplyAt)) w.Write([]byte(item.LastUser.Link))
w.Write(forum_43) w.Write(forum_43)
w.Write([]byte(item.LastUser.Name))
w.Write(forum_44)
w.Write([]byte(item.LastReplyAt))
w.Write(forum_45)
} }
} else { } else {
w.Write(forum_44)
if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_45)
w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_46) w.Write(forum_46)
} if tmpl_forum_vars.CurrentUser.Perms.CreateTopic {
w.Write(forum_47) w.Write(forum_47)
} w.Write([]byte(strconv.Itoa(tmpl_forum_vars.Forum.ID)))
w.Write(forum_48) w.Write(forum_48)
}
w.Write(forum_49)
}
w.Write(forum_50)
w.Write(footer_0) w.Write(footer_0)
if len(tmpl_forum_vars.Header.Themes) != 0 { if len(tmpl_forum_vars.Header.Themes) != 0 {
for _, item := range tmpl_forum_vars.Header.Themes { for _, item := range tmpl_forum_vars.Header.Themes {

View File

@ -55,12 +55,13 @@ if tmpl_forums_vars.CurrentUser.Loggedin {
w.Write(menu_3) w.Write(menu_3)
w.Write([]byte(tmpl_forums_vars.CurrentUser.Link)) w.Write([]byte(tmpl_forums_vars.CurrentUser.Link))
w.Write(menu_4) w.Write(menu_4)
w.Write([]byte(tmpl_forums_vars.CurrentUser.Session))
w.Write(menu_5) w.Write(menu_5)
} else { w.Write([]byte(tmpl_forums_vars.CurrentUser.Session))
w.Write(menu_6) w.Write(menu_6)
} } else {
w.Write(menu_7) w.Write(menu_7)
}
w.Write(menu_8)
w.Write(header_14) w.Write(header_14)
if tmpl_forums_vars.Header.Widgets.RightSidebar != "" { if tmpl_forums_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_15) w.Write(header_15)

View File

@ -56,16 +56,17 @@ var menu_2 = []byte(`</a></li>
var menu_3 = []byte(` var menu_3 = []byte(`
<li class="menu_left menu_account"><a href="/user/edit/critical/">Account</a></li> <li class="menu_left menu_account"><a href="/user/edit/critical/">Account</a></li>
<li class="menu_left menu_profile"><a href="`) <li class="menu_left menu_profile"><a href="`)
var menu_4 = []byte(`">Profile</a></li> var menu_4 = []byte(`">Profile</a></li>`)
<li class="menu_left menu_account supermod_only"><a href="/panel/">Panel</a></li> var menu_5 = []byte(`
<li class="menu_left menu_panel menu_account supermod_only"><a href="/panel/">Panel</a></li>
<li class="menu_left menu_logout"><a href="/accounts/logout/?session=`) <li class="menu_left menu_logout"><a href="/accounts/logout/?session=`)
var menu_5 = []byte(`">Logout</a></li> var menu_6 = []byte(`">Logout</a></li>
`) `)
var menu_6 = []byte(` var menu_7 = []byte(`
<li class="menu_left menu_register"><a href="/accounts/create/">Register</a></li> <li class="menu_left menu_register"><a href="/accounts/create/">Register</a></li>
<li class="menu_left menu_login"><a href="/accounts/login/">Login</a></li> <li class="menu_left menu_login"><a href="/accounts/login/">Login</a></li>
`) `)
var menu_7 = []byte(` var menu_8 = []byte(`
</ul> </ul>
</div> </div>
</div> </div>
@ -233,20 +234,29 @@ var topic_93 = []byte(`</div>
`) `)
var topic_94 = []byte(` var topic_94 = []byte(`
<div class="rowblock topic_reply_form"> <div class="rowblock topic_reply_form quick_create_form">
<form action="/reply/create/" method="post"> <form id="reply_form" enctype="multipart/form-data" action="/reply/create/" method="post"></form>
<input name="tid" value='`) <input form="reply_form" name="tid" value='`)
var topic_95 = []byte(`' type="hidden" /> var topic_95 = []byte(`' type="hidden" />
<div class="formrow real_first_child"> <div class="formrow real_first_child">
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here" required></textarea></div> <div class="formitem">
<textarea id="input_content" form="reply_form" name="reply-content" placeholder="Insert reply here" required></textarea>
</div> </div>
<div class="formrow"> </div>
<div class="formitem"><button name="reply-button" class="formbutton">Create Reply</button></div> <div class="formrow quick_button_row">
<div class="formitem">
<button form="reply_form" name="reply-button" class="formbutton">Create Reply</button>
`)
var topic_96 = []byte(`
<input name="upload_files" form="reply_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`)
var topic_97 = []byte(`
</div> </div>
</form> </div>
</div> </div>
`) `)
var topic_96 = []byte(` var topic_98 = []byte(`
</main> </main>
@ -428,20 +438,29 @@ var topic_alt_86 = []byte(`
var topic_alt_87 = []byte(`</div> var topic_alt_87 = []byte(`</div>
`) `)
var topic_alt_88 = []byte(` var topic_alt_88 = []byte(`
<div class="rowblock topic_reply_form"> <div class="rowblock topic_reply_form quick_create_form">
<form action="/reply/create/" method="post"> <form id="reply_form" enctype="multipart/form-data" action="/reply/create/" method="post"></form>
<input name="tid" value='`) <input form="reply_form" name="tid" value='`)
var topic_alt_89 = []byte(`' type="hidden" /> var topic_alt_89 = []byte(`' type="hidden" />
<div class="formrow"> <div class="formrow real_first_child">
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here" required></textarea></div> <div class="formitem">
<textarea id="input_content" form="reply_form" name="reply-content" placeholder="Insert reply here" required></textarea>
</div> </div>
<div class="formrow"> </div>
<div class="formitem"><button name="reply-button" class="formbutton">Create Reply</button></div> <div class="formrow quick_button_row">
<div class="formitem">
<button form="reply_form" name="reply-button" class="formbutton">Create Reply</button>
`)
var topic_alt_90 = []byte(`
<input name="upload_files" form="reply_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`)
var topic_alt_91 = []byte(`
</div> </div>
</form> </div>
</div> </div>
`) `)
var topic_alt_90 = []byte(` var topic_alt_92 = []byte(`
</main> </main>
@ -675,7 +694,7 @@ var topics_6 = []byte(`
</div> </div>
`) `)
var topics_7 = []byte(` var topics_7 = []byte(`
<div class="rowblock topic_create_form" style="display: none;"> <div class="rowblock topic_create_form quick_create_form" style="display: none;">
<form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> <form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<div class="formrow topic_board_row real_first_child"> <div class="formrow topic_board_row real_first_child">
<div class="formitem"><select form="topic_create_form_form" id="topic_board_input" name="topic-board"> <div class="formitem"><select form="topic_create_form_form" id="topic_board_input" name="topic-board">
@ -695,18 +714,18 @@ var topics_13 = []byte(`
</div> </div>
<div class="formrow topic_content_row"> <div class="formrow topic_content_row">
<div class="formitem"> <div class="formitem">
<textarea form="topic_create_form_form" id="topic_content" name="topic-content" placeholder="Insert post here" required></textarea> <textarea form="topic_create_form_form" id="input_content" name="topic-content" placeholder="Insert post here" required></textarea>
</div> </div>
</div> </div>
<div class="formrow topic_button_row"> <div class="formrow quick_button_row">
<div class="formitem"> <div class="formitem">
<button form="topic_create_form_form" class="formbutton">Create Topic</button> <button form="topic_create_form_form" class="formbutton">Create Topic</button>
`) `)
var topics_14 = []byte(` var topics_14 = []byte(`
<input name="quick_topic_upload_files" form="topic_create_form_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" /> <input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="quick_topic_upload_files" class="formbutton add_file_button">Add File</label>`) <label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`)
var topics_15 = []byte(` var topics_15 = []byte(`
<div id="upload_file_dock"></div>
<button class="formbutton close_form">Cancel</button> <button class="formbutton close_form">Cancel</button>
</div> </div>
</div> </div>
@ -715,56 +734,58 @@ var topics_15 = []byte(`
var topics_16 = []byte(` var topics_16 = []byte(`
<div id="topic_list" class="rowblock topic_list" aria-label="A list containing topics from every forum"> <div id="topic_list" class="rowblock topic_list" aria-label="A list containing topics from every forum">
`) `)
var topics_17 = []byte(`<div class="rowitem topic_left passive datarow `) var topics_17 = []byte(`<div class="topic_row">
<div class="rowitem topic_left passive datarow `)
var topics_18 = []byte(`topic_sticky`) var topics_18 = []byte(`topic_sticky`)
var topics_19 = []byte(`topic_closed`) var topics_19 = []byte(`topic_closed`)
var topics_20 = []byte(`" style="`) var topics_20 = []byte(`">
var topics_21 = []byte(`background-image: url(`) `)
var topics_22 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`) var topics_21 = []byte(`<img src="`)
var topics_23 = []byte(`"> var topics_22 = []byte(`" height="64" />`)
var topics_23 = []byte(`
<span class="topic_inner_left">
<a class="rowtopic" href="`)
var topics_24 = []byte(`">`)
var topics_25 = []byte(`</a> `)
var topics_26 = []byte(`<a class="rowsmall parent_forum" href="`)
var topics_27 = []byte(`">`)
var topics_28 = []byte(`</a>`)
var topics_29 = []byte(`
<br /><a class="rowsmall starter" href="`)
var topics_30 = []byte(`">`)
var topics_31 = []byte(`</a>
`)
var topics_32 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var topics_33 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var topics_34 = []byte(`
</span>
<span class="topic_inner_right rowsmall" style="float: right;"> <span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`) <span class="replyCount">`)
var topics_24 = []byte(` replies</span><br /> var topics_35 = []byte(` replies</span><br />
<span class="lastReplyAt">`) <span class="topicCount">x topics</span>
var topics_25 = []byte(`</span>
</span>
<span>
<a class="rowtopic" href="`)
var topics_26 = []byte(`">`)
var topics_27 = []byte(`</a> `)
var topics_28 = []byte(`<a class="rowsmall" href="`)
var topics_29 = []byte(`">`)
var topics_30 = []byte(`</a>`)
var topics_31 = []byte(`
<br /><a class="rowsmall" href="`)
var topics_32 = []byte(`">Starter: `)
var topics_33 = []byte(`</a>
`)
var topics_34 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var topics_35 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var topics_36 = []byte(`
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow `) <div class="rowitem topic_right passive datarow `)
var topics_37 = []byte(`topic_sticky`) var topics_36 = []byte(`topic_sticky`)
var topics_38 = []byte(`topic_closed`) var topics_37 = []byte(`topic_closed`)
var topics_39 = []byte(`" style="`) var topics_38 = []byte(`">
var topics_40 = []byte(`background-image: url(`) `)
var topics_41 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`) var topics_39 = []byte(`<img src="`)
var topics_42 = []byte(`"> var topics_40 = []byte(`" height="64" />`)
var topics_41 = []byte(`
<span> <span>
<a href="`) <a href="`)
var topics_43 = []byte(`" class="lastName" style="font-size: 14px;">`) var topics_42 = []byte(`" class="lastName" style="font-size: 14px;">`)
var topics_44 = []byte(`</a><br> var topics_43 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">Last: `) <span class="rowsmall lastReplyAt">Last: `)
var topics_45 = []byte(`</span> var topics_44 = []byte(`</span>
</span> </span>
</div> </div>
`) </div>`)
var topics_46 = []byte(`<div class="rowitem passive">There aren't any topics yet.`) var topics_45 = []byte(`<div class="rowitem passive">There aren't any topics yet.`)
var topics_47 = []byte(` <a href="/topics/create/">Start one?</a>`) var topics_46 = []byte(` <a href="/topics/create/">Start one?</a>`)
var topics_48 = []byte(`</div>`) var topics_47 = []byte(`</div>`)
var topics_49 = []byte(` var topics_48 = []byte(`
</div> </div>
</main> </main>
@ -801,7 +822,7 @@ var forum_16 = []byte(`
</div> </div>
`) `)
var forum_17 = []byte(` var forum_17 = []byte(`
<div class="rowblock topic_create_form" style="display: none;"> <div class="rowblock topic_create_form quick_create_form" style="display: none;">
<form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> <form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="`) <input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="`)
var forum_18 = []byte(`" type="hidden"> var forum_18 = []byte(`" type="hidden">
@ -812,18 +833,18 @@ var forum_18 = []byte(`" type="hidden">
</div> </div>
<div class="formrow topic_content_row"> <div class="formrow topic_content_row">
<div class="formitem"> <div class="formitem">
<textarea form="topic_create_form_form" id="topic_content" name="topic-content" placeholder="Insert post here" required></textarea> <textarea form="topic_create_form_form" id="input_content" name="topic-content" placeholder="Insert post here" required></textarea>
</div> </div>
</div> </div>
<div class="formrow topic_button_row"> <div class="formrow quick_button_row">
<div class="formitem"> <div class="formitem">
<button form="topic_create_form_form" name="topic-button" class="formbutton">Create Topic</button> <button form="topic_create_form_form" name="topic-button" class="formbutton">Create Topic</button>
`) `)
var forum_19 = []byte(` var forum_19 = []byte(`
<input name="quick_topic_upload_files" form="topic_create_form_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" /> <input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="quick_topic_upload_files" class="formbutton add_file_button">Add File</label>`) <label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>`)
var forum_20 = []byte(` var forum_20 = []byte(`
<div id="upload_file_dock"></div>
<button class="formbutton close_form">Cancel</button> <button class="formbutton close_form">Cancel</button>
</div> </div>
</div> </div>
@ -832,50 +853,55 @@ var forum_20 = []byte(`
var forum_21 = []byte(` var forum_21 = []byte(`
<div id="forum_topic_list" class="rowblock topic_list"> <div id="forum_topic_list" class="rowblock topic_list">
`) `)
var forum_22 = []byte(`<div class="rowitem topic_left passive datarow `) var forum_22 = []byte(`<div class="topic_row">
<div class="rowitem topic_left passive datarow `)
var forum_23 = []byte(`topic_sticky`) var forum_23 = []byte(`topic_sticky`)
var forum_24 = []byte(`topic_closed`) var forum_24 = []byte(`topic_closed`)
var forum_25 = []byte(`" style="`) var forum_25 = []byte(`">
var forum_26 = []byte(`background-image: url(`) `)
var forum_27 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`) var forum_26 = []byte(`<img src="`)
var forum_28 = []byte(`"> var forum_27 = []byte(`" height="64" />`)
<span class="topic_inner_right rowsmall" style="float: right;"> var forum_28 = []byte(`
<span class="replyCount">`) <span class="topic_inner_left">
var forum_29 = []byte(` replies</span><br />
<span class="lastReplyAt">`)
var forum_30 = []byte(`</span>
</span>
<span>
<a class="rowtopic" href="`) <a class="rowtopic" href="`)
var forum_29 = []byte(`">`)
var forum_30 = []byte(`</a>
<br /><a class="rowsmall starter" href="`)
var forum_31 = []byte(`">`) var forum_31 = []byte(`">`)
var forum_32 = []byte(`</a> var forum_32 = []byte(`</a>
<br /><a class="rowsmall" href="`)
var forum_33 = []byte(`">Starter: `)
var forum_34 = []byte(`</a>
`) `)
var forum_35 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`) var forum_33 = []byte(`<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>`)
var forum_36 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`) var forum_34 = []byte(`<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>`)
var forum_37 = []byte(` var forum_35 = []byte(`
</span>
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">`)
var forum_36 = []byte(` replies</span><br />
<span class="topicCount">x topics</span>
</span> </span>
</div> </div>
<div class="rowitem topic_right passive datarow" style="`) <div class="rowitem topic_right passive datarow `)
var forum_38 = []byte(`background-image: url(`) var forum_37 = []byte(`topic_sticky`)
var forum_39 = []byte(`);background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;`) var forum_38 = []byte(`topic_closed`)
var forum_40 = []byte(`"> var forum_39 = []byte(`">
`)
var forum_40 = []byte(`<img src="`)
var forum_41 = []byte(`" height="64" />`)
var forum_42 = []byte(`
<span> <span>
<a href="`) <a href="`)
var forum_41 = []byte(`" class="lastName" style="font-size: 14px;">`) var forum_43 = []byte(`" class="lastName" style="font-size: 14px;">`)
var forum_42 = []byte(`</a><br> var forum_44 = []byte(`</a><br>
<span class="rowsmall lastReplyAt">Last: `) <span class="rowsmall lastReplyAt">Last: `)
var forum_43 = []byte(`</span> var forum_45 = []byte(`</span>
</span> </span>
</div> </div>
`) </div>`)
var forum_44 = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`) var forum_46 = []byte(`<div class="rowitem passive">There aren't any topics in this forum yet.`)
var forum_45 = []byte(` <a href="/topics/create/`) var forum_47 = []byte(` <a href="/topics/create/`)
var forum_46 = []byte(`">Start one?</a>`) var forum_48 = []byte(`">Start one?</a>`)
var forum_47 = []byte(`</div>`) var forum_49 = []byte(`</div>`)
var forum_48 = []byte(` var forum_50 = []byte(`
</div> </div>
</main> </main>

View File

@ -56,12 +56,13 @@ if tmpl_profile_vars.CurrentUser.Loggedin {
w.Write(menu_3) w.Write(menu_3)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Link)) w.Write([]byte(tmpl_profile_vars.CurrentUser.Link))
w.Write(menu_4) w.Write(menu_4)
w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(menu_5) w.Write(menu_5)
} else { w.Write([]byte(tmpl_profile_vars.CurrentUser.Session))
w.Write(menu_6) w.Write(menu_6)
} } else {
w.Write(menu_7) w.Write(menu_7)
}
w.Write(menu_8)
w.Write(header_14) w.Write(header_14)
if tmpl_profile_vars.Header.Widgets.RightSidebar != "" { if tmpl_profile_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_15) w.Write(header_15)

View File

@ -56,12 +56,13 @@ if tmpl_topic_vars.CurrentUser.Loggedin {
w.Write(menu_3) w.Write(menu_3)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Link)) w.Write([]byte(tmpl_topic_vars.CurrentUser.Link))
w.Write(menu_4) w.Write(menu_4)
w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(menu_5) w.Write(menu_5)
} else { w.Write([]byte(tmpl_topic_vars.CurrentUser.Session))
w.Write(menu_6) w.Write(menu_6)
} } else {
w.Write(menu_7) w.Write(menu_7)
}
w.Write(menu_8)
w.Write(header_14) w.Write(header_14)
if tmpl_topic_vars.Header.Widgets.RightSidebar != "" { if tmpl_topic_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_15) w.Write(header_15)
@ -286,8 +287,12 @@ if tmpl_topic_vars.CurrentUser.Perms.CreateReply {
w.Write(topic_94) w.Write(topic_94)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID))) w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.ID)))
w.Write(topic_95) w.Write(topic_95)
} if tmpl_topic_vars.CurrentUser.Perms.UploadFiles {
w.Write(topic_96) w.Write(topic_96)
}
w.Write(topic_97)
}
w.Write(topic_98)
w.Write(footer_0) w.Write(footer_0)
if len(tmpl_topic_vars.Header.Themes) != 0 { if len(tmpl_topic_vars.Header.Themes) != 0 {
for _, item := range tmpl_topic_vars.Header.Themes { for _, item := range tmpl_topic_vars.Header.Themes {

View File

@ -56,12 +56,13 @@ if tmpl_topic_alt_vars.CurrentUser.Loggedin {
w.Write(menu_3) w.Write(menu_3)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Link)) w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Link))
w.Write(menu_4) w.Write(menu_4)
w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(menu_5) w.Write(menu_5)
} else { w.Write([]byte(tmpl_topic_alt_vars.CurrentUser.Session))
w.Write(menu_6) w.Write(menu_6)
} } else {
w.Write(menu_7) w.Write(menu_7)
}
w.Write(menu_8)
w.Write(header_14) w.Write(header_14)
if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" { if tmpl_topic_alt_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_15) w.Write(header_15)
@ -274,8 +275,12 @@ if tmpl_topic_alt_vars.CurrentUser.Perms.CreateReply {
w.Write(topic_alt_88) w.Write(topic_alt_88)
w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID))) w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.ID)))
w.Write(topic_alt_89) w.Write(topic_alt_89)
} if tmpl_topic_alt_vars.CurrentUser.Perms.UploadFiles {
w.Write(topic_alt_90) w.Write(topic_alt_90)
}
w.Write(topic_alt_91)
}
w.Write(topic_alt_92)
w.Write(footer_0) w.Write(footer_0)
if len(tmpl_topic_alt_vars.Header.Themes) != 0 { if len(tmpl_topic_alt_vars.Header.Themes) != 0 {
for _, item := range tmpl_topic_alt_vars.Header.Themes { for _, item := range tmpl_topic_alt_vars.Header.Themes {

View File

@ -56,12 +56,13 @@ if tmpl_topics_vars.CurrentUser.Loggedin {
w.Write(menu_3) w.Write(menu_3)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Link)) w.Write([]byte(tmpl_topics_vars.CurrentUser.Link))
w.Write(menu_4) w.Write(menu_4)
w.Write([]byte(tmpl_topics_vars.CurrentUser.Session))
w.Write(menu_5) w.Write(menu_5)
} else { w.Write([]byte(tmpl_topics_vars.CurrentUser.Session))
w.Write(menu_6) w.Write(menu_6)
} } else {
w.Write(menu_7) w.Write(menu_7)
}
w.Write(menu_8)
w.Write(header_14) w.Write(header_14)
if tmpl_topics_vars.Header.Widgets.RightSidebar != "" { if tmpl_topics_vars.Header.Widgets.RightSidebar != "" {
w.Write(header_15) w.Write(header_15)
@ -129,62 +130,60 @@ w.Write([]byte(item.Creator.Avatar))
w.Write(topics_22) w.Write(topics_22)
} }
w.Write(topics_23) w.Write(topics_23)
w.Write([]byte(strconv.Itoa(item.PostCount)))
w.Write(topics_24)
w.Write([]byte(item.LastReplyAt))
w.Write(topics_25)
w.Write([]byte(item.Link)) w.Write([]byte(item.Link))
w.Write(topics_26) w.Write(topics_24)
w.Write([]byte(item.Title)) w.Write([]byte(item.Title))
w.Write(topics_27) w.Write(topics_25)
if item.ForumName != "" { if item.ForumName != "" {
w.Write(topics_28) w.Write(topics_26)
w.Write([]byte(item.ForumLink)) w.Write([]byte(item.ForumLink))
w.Write(topics_29) w.Write(topics_27)
w.Write([]byte(item.ForumName)) w.Write([]byte(item.ForumName))
w.Write(topics_30) w.Write(topics_28)
} }
w.Write(topics_31) w.Write(topics_29)
w.Write([]byte(item.Creator.Link)) w.Write([]byte(item.Creator.Link))
w.Write(topics_32) w.Write(topics_30)
w.Write([]byte(item.Creator.Name)) w.Write([]byte(item.Creator.Name))
w.Write(topics_31)
if item.IsClosed {
w.Write(topics_32)
}
if item.Sticky {
w.Write(topics_33) w.Write(topics_33)
if item.IsClosed { }
w.Write(topics_34) w.Write(topics_34)
} w.Write([]byte(strconv.Itoa(item.PostCount)))
if item.Sticky {
w.Write(topics_35) w.Write(topics_35)
}
w.Write(topics_36)
if item.Sticky { if item.Sticky {
w.Write(topics_37) w.Write(topics_36)
} else { } else {
if item.IsClosed { if item.IsClosed {
w.Write(topics_37)
}
}
w.Write(topics_38) w.Write(topics_38)
}
}
w.Write(topics_39)
if item.LastUser.Avatar != "" { if item.LastUser.Avatar != "" {
w.Write(topics_40) w.Write(topics_39)
w.Write([]byte(item.LastUser.Avatar)) w.Write([]byte(item.LastUser.Avatar))
w.Write(topics_41) w.Write(topics_40)
} }
w.Write(topics_42) w.Write(topics_41)
w.Write([]byte(item.LastUser.Link)) w.Write([]byte(item.LastUser.Link))
w.Write(topics_43) w.Write(topics_42)
w.Write([]byte(item.LastUser.Name)) w.Write([]byte(item.LastUser.Name))
w.Write(topics_44) w.Write(topics_43)
w.Write([]byte(item.LastReplyAt)) w.Write([]byte(item.LastReplyAt))
w.Write(topics_45) w.Write(topics_44)
} }
} else { } else {
w.Write(topics_46) w.Write(topics_45)
if tmpl_topics_vars.CurrentUser.Perms.CreateTopic { if tmpl_topics_vars.CurrentUser.Perms.CreateTopic {
w.Write(topics_46)
}
w.Write(topics_47) w.Write(topics_47)
} }
w.Write(topics_48) w.Write(topics_48)
}
w.Write(topics_49)
w.Write(footer_0) w.Write(footer_0)
if len(tmpl_topics_vars.Header.Themes) != 0 { if len(tmpl_topics_vars.Header.Themes) != 0 {
for _, item := range tmpl_topics_vars.Header.Themes { for _, item := range tmpl_topics_vars.Header.Themes {

View File

@ -17,7 +17,7 @@
{{end}} {{end}}
</div> </div>
{{if .CurrentUser.Perms.CreateTopic}} {{if .CurrentUser.Perms.CreateTopic}}
<div class="rowblock topic_create_form" style="display: none;"> <div class="rowblock topic_create_form quick_create_form" style="display: none;">
<form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> <form id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="{{.Forum.ID}}" type="hidden"> <input form="topic_create_form_form" id="topic_board_input" name="topic-board" value="{{.Forum.ID}}" type="hidden">
<div class="formrow topic_name_row real_first_child"> <div class="formrow topic_name_row real_first_child">
@ -27,42 +27,45 @@
</div> </div>
<div class="formrow topic_content_row"> <div class="formrow topic_content_row">
<div class="formitem"> <div class="formitem">
<textarea form="topic_create_form_form" id="topic_content" name="topic-content" placeholder="Insert post here" required></textarea> <textarea form="topic_create_form_form" id="input_content" name="topic-content" placeholder="Insert post here" required></textarea>
</div> </div>
</div> </div>
<div class="formrow topic_button_row"> <div class="formrow quick_button_row">
<div class="formitem"> <div class="formitem">
<button form="topic_create_form_form" name="topic-button" class="formbutton">Create Topic</button> <button form="topic_create_form_form" name="topic-button" class="formbutton">Create Topic</button>
{{if .CurrentUser.Perms.UploadFiles}} {{if .CurrentUser.Perms.UploadFiles}}
<input name="quick_topic_upload_files" form="topic_create_form_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" /> <input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="quick_topic_upload_files" class="formbutton add_file_button">Add File</label>{{end}} <label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div> <div id="upload_file_dock"></div>{{end}}
<button class="formbutton close_form">Cancel</button> <button class="formbutton close_form">Cancel</button>
</div> </div>
</div> </div>
</div> </div>
{{end}} {{end}}
<div id="forum_topic_list" class="rowblock topic_list"> <div id="forum_topic_list" class="rowblock topic_list">
{{range .ItemList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}"> {{range .ItemList}}<div class="topic_row">
<span class="topic_inner_right rowsmall" style="float: right;"> <div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
<span class="replyCount">{{.PostCount}} replies</span><br /> {{if .Creator.Avatar}}<img src="{{.Creator.Avatar}}" height="64" />{{end}}
<span class="lastReplyAt">{{.LastReplyAt}}</span> <span class="topic_inner_left">
</span>
<span>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</a> <a class="rowtopic" href="{{.Link}}">{{.Title}}</a>
<br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a> <br /><a class="rowsmall starter" href="{{.Creator.Link}}">{{.Creator.Name}}</a>
{{/** TODO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}} {{/** TODO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}}
{{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}} {{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}}
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}} {{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span> </span>
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}} replies</span><br />
<span class="topicCount">x topics</span>
</span>
</div> </div>
<div class="rowitem topic_right passive datarow" style="{{if .LastUser.Avatar}}background-image: url({{.LastUser.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}"> <div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
{{if .LastUser.Avatar}}<img src="{{.LastUser.Avatar}}" height="64" />{{end}}
<span> <span>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br> <a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span> <span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span>
</span> </span>
</div> </div>
{{else}}<div class="rowitem passive">There aren't any topics in this forum yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/{{.Forum.ID}}">Start one?</a>{{end}}</div>{{end}} </div>{{else}}<div class="rowitem passive">There aren't any topics in this forum yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/{{.Forum.ID}}">Start one?</a>{{end}}</div>{{end}}
</div> </div>
</main> </main>

View File

@ -13,8 +13,8 @@
</li> </li>
{{if .CurrentUser.Loggedin}} {{if .CurrentUser.Loggedin}}
<li class="menu_left menu_account"><a href="/user/edit/critical/">Account</a></li> <li class="menu_left menu_account"><a href="/user/edit/critical/">Account</a></li>
<li class="menu_left menu_profile"><a href="{{.CurrentUser.Link}}">Profile</a></li> <li class="menu_left menu_profile"><a href="{{.CurrentUser.Link}}">Profile</a></li>{{/** TODO: Remove the menu_account class once we confirm this won't break any theme **/}}
<li class="menu_left menu_account supermod_only"><a href="/panel/">Panel</a></li> <li class="menu_left menu_panel menu_account supermod_only"><a href="/panel/">Panel</a></li>
<li class="menu_left menu_logout"><a href="/accounts/logout/?session={{.CurrentUser.Session}}">Logout</a></li> <li class="menu_left menu_logout"><a href="/accounts/logout/?session={{.CurrentUser.Session}}">Logout</a></li>
{{else}} {{else}}
<li class="menu_left menu_register"><a href="/accounts/create/">Register</a></li> <li class="menu_left menu_register"><a href="/accounts/create/">Register</a></li>

View File

@ -79,16 +79,23 @@
{{end}}{{end}}</div> {{end}}{{end}}</div>
{{if .CurrentUser.Perms.CreateReply}} {{if .CurrentUser.Perms.CreateReply}}
<div class="rowblock topic_reply_form"> <div class="rowblock topic_reply_form quick_create_form">
<form action="/reply/create/" method="post"> <form id="reply_form" enctype="multipart/form-data" action="/reply/create/" method="post"></form>
<input name="tid" value='{{.Topic.ID}}' type="hidden" /> <input form="reply_form" name="tid" value='{{.Topic.ID}}' type="hidden" />
<div class="formrow real_first_child"> <div class="formrow real_first_child">
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here" required></textarea></div> <div class="formitem">
<textarea id="input_content" form="reply_form" name="reply-content" placeholder="Insert reply here" required></textarea>
</div> </div>
<div class="formrow"> </div>
<div class="formitem"><button name="reply-button" class="formbutton">Create Reply</button></div> <div class="formrow quick_button_row">
<div class="formitem">
<button form="reply_form" name="reply-button" class="formbutton">Create Reply</button>
{{if .CurrentUser.Perms.UploadFiles}}
<input name="upload_files" form="reply_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>{{end}}
</div> </div>
</form> </div>
</div> </div>
{{end}} {{end}}

View File

@ -77,16 +77,23 @@
</article> </article>
{{end}}</div> {{end}}</div>
{{if .CurrentUser.Perms.CreateReply}} {{if .CurrentUser.Perms.CreateReply}}
<div class="rowblock topic_reply_form"> <div class="rowblock topic_reply_form quick_create_form">
<form action="/reply/create/" method="post"> <form id="reply_form" enctype="multipart/form-data" action="/reply/create/" method="post"></form>
<input name="tid" value='{{.Topic.ID}}' type="hidden" /> <input form="reply_form" name="tid" value='{{.Topic.ID}}' type="hidden" />
<div class="formrow"> <div class="formrow real_first_child">
<div class="formitem"><textarea name="reply-content" placeholder="Insert reply here" required></textarea></div> <div class="formitem">
<textarea id="input_content" form="reply_form" name="reply-content" placeholder="Insert reply here" required></textarea>
</div> </div>
<div class="formrow"> </div>
<div class="formitem"><button name="reply-button" class="formbutton">Create Reply</button></div> <div class="formrow quick_button_row">
<div class="formitem">
<button form="reply_form" name="reply-button" class="formbutton">Create Reply</button>
{{if .CurrentUser.Perms.UploadFiles}}
<input name="upload_files" form="reply_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div>{{end}}
</div> </div>
</form> </div>
</div> </div>
{{end}} {{end}}

View File

@ -12,7 +12,7 @@
</div> </div>
{{if ne .CurrentUser.ID 0}} {{if ne .CurrentUser.ID 0}}
{{if .ForumList}} {{if .ForumList}}
<div class="rowblock topic_create_form" style="display: none;"> <div class="rowblock topic_create_form quick_create_form" style="display: none;">
<form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form> <form name="topic_create_form_form" id="topic_create_form_form" enctype="multipart/form-data" action="/topic/create/submit/" method="post"></form>
<div class="formrow topic_board_row real_first_child"> <div class="formrow topic_board_row real_first_child">
<div class="formitem"><select form="topic_create_form_form" id="topic_board_input" name="topic-board"> <div class="formitem"><select form="topic_create_form_form" id="topic_board_input" name="topic-board">
@ -26,16 +26,16 @@
</div> </div>
<div class="formrow topic_content_row"> <div class="formrow topic_content_row">
<div class="formitem"> <div class="formitem">
<textarea form="topic_create_form_form" id="topic_content" name="topic-content" placeholder="Insert post here" required></textarea> <textarea form="topic_create_form_form" id="input_content" name="topic-content" placeholder="Insert post here" required></textarea>
</div> </div>
</div> </div>
<div class="formrow topic_button_row"> <div class="formrow quick_button_row">
<div class="formitem"> <div class="formitem">
<button form="topic_create_form_form" class="formbutton">Create Topic</button> <button form="topic_create_form_form" class="formbutton">Create Topic</button>
{{if .CurrentUser.Perms.UploadFiles}} {{if .CurrentUser.Perms.UploadFiles}}
<input name="quick_topic_upload_files" form="topic_create_form_form" id="quick_topic_upload_files" multiple type="file" style="display: none;" /> <input name="upload_files" form="topic_create_form_form" id="upload_files" multiple type="file" style="display: none;" />
<label for="quick_topic_upload_files" class="formbutton add_file_button">Add File</label>{{end}} <label for="upload_files" class="formbutton add_file_button">Add File</label>
<div id="upload_file_dock"></div> <div id="upload_file_dock"></div>{{end}}
<button class="formbutton close_form">Cancel</button> <button class="formbutton close_form">Cancel</button>
</div> </div>
</div> </div>
@ -43,26 +43,29 @@
{{end}} {{end}}
{{end}} {{end}}
<div id="topic_list" class="rowblock topic_list" aria-label="A list containing topics from every forum"> <div id="topic_list" class="rowblock topic_list" aria-label="A list containing topics from every forum">
{{range .TopicList}}<div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}" style="{{if .Creator.Avatar}}background-image: url({{.Creator.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}"> {{range .TopicList}}<div class="topic_row">
<span class="topic_inner_right rowsmall" style="float: right;"> <div class="rowitem topic_left passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
<span class="replyCount">{{.PostCount}} replies</span><br /> {{if .Creator.Avatar}}<img src="{{.Creator.Avatar}}" height="64" />{{end}}
<span class="lastReplyAt">{{.LastReplyAt}}</span> <span class="topic_inner_left">
</span> <a class="rowtopic" href="{{.Link}}">{{.Title}}</a> {{if .ForumName}}<a class="rowsmall parent_forum" href="{{.ForumLink}}">{{.ForumName}}</a>{{end}}
<span> <br /><a class="rowsmall starter" href="{{.Creator.Link}}">{{.Creator.Name}}</a>
<a class="rowtopic" href="{{.Link}}">{{.Title}}</a> {{if .ForumName}}<a class="rowsmall" href="{{.ForumLink}}">{{.ForumName}}</a>{{end}}
<br /><a class="rowsmall" href="{{.Creator.Link}}">Starter: {{.Creator.Name}}</a>
{{/** TODO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}} {{/** TODO: Avoid the double '|' when both .IsClosed and .Sticky are set to true. We could probably do this with CSS **/}}
{{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}} {{if .IsClosed}}<span class="rowsmall topic_status_e topic_status_closed" title="Status: Closed"> | &#x1F512;&#xFE0E</span>{{end}}
{{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}} {{if .Sticky}}<span class="rowsmall topic_status_e topic_status_sticky" title="Status: Pinned"> | &#x1F4CD;&#xFE0E</span>{{end}}
</span> </span>
<span class="topic_inner_right rowsmall" style="float: right;">
<span class="replyCount">{{.PostCount}} replies</span><br />
<span class="topicCount">x topics</span>
</span>
</div> </div>
<div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}" style="{{if .LastUser.Avatar}}background-image: url({{.LastUser.Avatar}});background-position: left;background-repeat: no-repeat;background-size: 64px;padding-left: 72px;{{end}}"> <div class="rowitem topic_right passive datarow {{if .Sticky}}topic_sticky{{else if .IsClosed}}topic_closed{{end}}">
{{if .LastUser.Avatar}}<img src="{{.LastUser.Avatar}}" height="64" />{{end}}
<span> <span>
<a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br> <a href="{{.LastUser.Link}}" class="lastName" style="font-size: 14px;">{{.LastUser.Name}}</a><br>
<span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span> <span class="rowsmall lastReplyAt">Last: {{.LastReplyAt}}</span>
</span> </span>
</div> </div>
{{else}}<div class="rowitem passive">There aren't any topics yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/">Start one?</a>{{end}}</div>{{end}} </div>{{else}}<div class="rowitem passive">There aren't any topics yet.{{if .CurrentUser.Perms.CreateTopic}} <a href="/topics/create/">Start one?</a>{{end}}</div>{{end}}
</div> </div>
</main> </main>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@ -1 +0,0 @@
/* Sample CSS file. Doesn't do anything. */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@ -1,16 +0,0 @@
{
"Name": "cosmo-classic",
"FriendlyName": "Cosmo Classic",
"Version": "Coming Soon",
"Creator": "Azareal",
"Disabled": true,
"HideFromThemes": true,
"Tag": "🏗️",
"URL": "github.com/Azareal/Gosora",
"Templates": [
{
"Name": "topic",
"Source": "topic_alt"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,71 +0,0 @@
/* Control Panel */
.tag-mini {
margin-left: 0px;
padding-left: 0px;
padding-right: 0px;
padding-top: 2px;
padding-bottom: 2px;
color: black;
font-size: 12px;
}
.panel_tag {
padding-left: 0px;
padding-right: 0px;
border: none;
background: none;
color: #202020;
font-size: 14px;
}
.panel_floater {
float: right;
}
#panel_groups > .rowitem > .panel_floater {
float: none;
}
#panel_groups > .rowitem > .panel_floater > .panel_right_button {
float: right;
}
#panel_forums > .rowitem > .panel_floater {
float: none;
}
#panel_forums > .rowitem > .panel_floater > .panel_buttons {
float: right;
}
#panel_forums > .rowitem > span > .forum_name {
margin-right: 4px;
}
.panel_rank_tag, .forum_preset, .forum_active {
float: none;
color: #202020 !important;
font-size: 11px;
}
.panel_rank_tag_admin:before { content: "Admins"; }
.panel_rank_tag_mod:before { content: "Mods"; }
.panel_rank_tag_banned:before { content: "Banned"; }
.panel_rank_tag_guest:before { content: "Guests"; }
.panel_rank_tag_member:before { content: "Members"; }
.forum_preset_announce:after { content: "Announcements"; }
.forum_preset_members:after { content: "Member Only"; }
.forum_preset_staff:after { content: "Staff Only"; }
.forum_preset_admins:after { content: "Admin Only"; }
.forum_preset_archive:after { content: "Archive"; }
.forum_preset_all:after { content: "Public"; }
.forum_preset_custom, .forum_preset_ { display: none !important; }
.forum_active_Hide:before { content: "Hidden"; }
.forum_active_Hide + .forum_preset:before { content: " | "; }
.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_read_only:before { content: "Read Only"; color: green; }
.perm_preset_can_post:before { content: "Can Post"; color: green; }
.perm_preset_can_moderate:before { content: "Can Moderate"; color: darkblue; }
.perm_preset_custom:before { content: "Custom"; color: black; }
.perm_preset_default:before { content: "Default"; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@ -1,18 +0,0 @@
{
"Name": "cosmo-conflux",
"FriendlyName": "Cosmo Conflux",
"Version": "Coming Soon",
"Creator": "Azareal",
"FullImage": "cosmo-conflux.png",
"ForkOf": "cosmo",
"MobileFriendly": true,
"HideFromThemes": true,
"Tag": "🏗️",
"URL": "github.com/Azareal/Gosora",
"Templates": [
{
"Name": "topic",
"Source": "topic_alt"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,71 +0,0 @@
/* Control Panel */
.tag-mini {
margin-left: 0px;
padding-left: 0px;
padding-right: 0px;
padding-top: 2px;
padding-bottom: 2px;
color: black;
font-size: 12px;
}
.panel_tag {
padding-left: 0px;
padding-right: 0px;
border: none;
background: none;
color: #202020;
font-size: 14px;
}
.panel_floater {
float: right;
}
#panel_groups > .rowitem > .panel_floater {
float: none;
}
#panel_groups > .rowitem > .panel_floater > .panel_right_button {
float: right;
}
#panel_forums > .rowitem > .panel_floater {
float: none;
}
#panel_forums > .rowitem > .panel_floater > .panel_buttons {
float: right;
}
#panel_forums > .rowitem > span > .forum_name {
margin-right: 4px;
}
.panel_rank_tag, .forum_preset, .forum_active {
float: none;
color: #202020 !important;
font-size: 11px;
}
.panel_rank_tag_admin:before { content: "Admins"; }
.panel_rank_tag_mod:before { content: "Mods"; }
.panel_rank_tag_banned:before { content: "Banned"; }
.panel_rank_tag_guest:before { content: "Guests"; }
.panel_rank_tag_member:before { content: "Members"; }
.forum_preset_announce:after { content: "Announcements"; }
.forum_preset_members:after { content: "Member Only"; }
.forum_preset_staff:after { content: "Staff Only"; }
.forum_preset_admins:after { content: "Admin Only"; }
.forum_preset_archive:after { content: "Archive"; }
.forum_preset_all:after { content: "Public"; }
.forum_preset_custom, .forum_preset_ { display: none !important; }
.forum_active_Hide:before { content: "Hidden"; }
.forum_active_Hide + .forum_preset:before { content: " | "; }
.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_read_only:before { content: "Read Only"; color: green; }
.perm_preset_can_post:before { content: "Can Post"; color: green; }
.perm_preset_can_moderate:before { content: "Can Moderate"; color: darkblue; }
.perm_preset_custom:before { content: "Custom"; color: black; }
.perm_preset_default:before { content: "Default"; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@ -1,17 +0,0 @@
{
"Name": "cosmo",
"FriendlyName": "AtomBB Cosmo",
"Version": "Coming Soon",
"Creator": "Azareal",
"FullImage": "cosmo.png",
"MobileFriendly": true,
"HideFromThemes": true,
"Tag": "🏗️",
"URL": "github.com/Azareal/Gosora",
"Templates": [
{
"Name": "topic",
"Source": "topic_alt"
}
]
}

View File

@ -0,0 +1,235 @@
:root {
--header-border-color: hsl(0,0%,85%);
--element-border-color: hsl(0,0%,90%);
--element-background-color: white;
}
* {
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
body {
font-size: 16px;
font-family: arial;
margin: 0px;
}
a {
text-decoration: none;
color: hsl(0,0%,40%);
}
#back {
padding: 8px;
padding-top: 14px;
display: flex;
/*background-color: hsl(0,0%,97%);*/
}
#main {
width: 100%;
}
.sidebar {
width: 200px;
display: none;
}
.nav {
border-bottom: 1px solid var(--header-border-color);
}
li {
margin-right: 12px;
}
.menu_left a:after {
content: "|";
margin-left: 12px;
color: var(--header-border-color);
}
.menu_overview {
font-size: 22px;
margin-right: 12px;
}
.menu_overview a:after {
margin-left: 14px;
margin-right: 4px;
position: relative;
top: -3px;
}
.menu_forums a:before {
content: "\f03a";
font: normal normal normal 14px/1 FontAwesome;
margin-right: 6px;
}
.menu_topics a:before {
font: normal normal normal 14px/1 FontAwesome;
margin-right: 4px;
content: "\f27b";
position: relative;
top: -2px;
}
.menu_alerts {
display: none;
}
.menu_account a:before {
content: "\f2c3";
font: normal normal normal 14px/1 FontAwesome;
margin-right: 6px;
}
.menu_profile a:before {
content: "\f2c0";
font: normal normal normal 14px/1 FontAwesome;
margin-right: 5px;
position: relative;
top: -1px;
font-size: 14px;
}
.menu_panel a:before {
font: normal normal normal 14px/1 FontAwesome;
margin-right: 6px;
content: "\f108";
}
.menu_logout a:before {
content: "\f08b";
font: normal normal normal 14px/1 FontAwesome;
margin-right: 3px;
}
ul {
display: flex;
list-style-type: none;
padding: 0px;
margin-left: 14px;
margin-bottom: 12px;
}
.alertList {
display: none;
}
.rowblock {
margin-bottom: 12px;
border: 1px solid var(--header-border-color);
border-bottom: 2px solid var(--header-border-color);
background-color: var(--element-background-color);
margin-left: 4px;
margin-right: 4px;
}
.rowhead {
padding: 13px;
padding-top: 14px;
padding-bottom: 14px;
}
.rowhead h1 {
font-size: 20px;
font-weight: normal;
color: hsl(0,0%,10%);
-webkit-margin-before: 0;
-webkit-margin-after: 0;
display: inline-block;
}
.topic_list {
border: none;
}
.topic_list .topic_row {
display: flex;
flex-wrap: wrap;
}
.topic_left, .topic_right {
margin-bottom: 8px;
padding: 4px;
display: flex;
border: 1px solid var(--element-border-color);
border-bottom: 2px solid var(--element-border-color);
}
.topic_list .rowtopic {
font-size: 18px;
color: hsl(0,0%,30%);
margin-right: 2px;
max-width: 230px;
overflow: hidden;
text-overflow: ellipsis;
}
.topic_list .rowsmall.starter:before {
content: "\f007";
font: normal normal normal 14px/1 FontAwesome;
margin-right: 5px;
font-size: 16px;
}
.topic_list .rowsmall.starter:before {
content: "\f007";
font: normal normal normal 14px/1 FontAwesome;
margin-right: 5px;
font-size: 16px;
}
.topic_list .topic_status_e {
display: none;
}
.topic_left {
flex: 1 1 calc(100% - 400px);
border-right: none;
}
.topic_inner_right {
margin-left: auto;
margin-right: auto;
font-size: 17px;
margin-top: 12px;
}
.topic_right {
flex: 1 1 150px;
border-left: none;
}
.topic_left img {
border-radius: 30px;
height: 48px;
width: 48px;
margin-top: 8px;
margin-left: 4px;
}
.topic_right img {
border-radius: 30px;
height: 48px;
width: 48px;
margin-top: 8px;
}
.topic_left .topic_inner_left {
margin-top: 12px;
margin-left: 8px;
margin-bottom: 14px;
}
.topic_right > span {
margin-top: 12px;
margin-left: 8px;
}
.topic_sticky {
border-bottom: 2px solid hsl(51, 60%, 50%);
}
/*@element .topic_left .rowtopic and (min-characters: 10) {
}*/
@media(max-width: 670px) {
.topic_inner_right {
display: none;
}
}

View File

@ -4,6 +4,16 @@
"Version": "0.0.1", "Version": "0.0.1",
"Creator": "Azareal", "Creator": "Azareal",
"URL": "github.com/Azareal/Gosora", "URL": "github.com/Azareal/Gosora",
"HideFromThemes":true, "Tag": "WIP",
"Tag": "WIP" "Sidebars":"right",
"Resources": [
{
"Name":"font-awesome-4.7.0/css/font-awesome.css",
"Location":"global"
},
{
"Name":"EQCSS.min.js",
"Location":"global"
}
]
} }

View File

@ -483,19 +483,16 @@ input, select, textarea {
width: calc(100% - 14px); width: calc(100% - 14px);
} }
.topic_button_row .formitem { .quick_button_row .formitem, .quick_create_form .upload_file_dock {
display: flex; display: flex;
} }
.topic_create_form .add_file_button { .quick_create_form .add_file_button {
margin-left: 8px; margin-left: 8px;
} }
.topic_create_form .close_form { .quick_create_form .close_form {
margin-left: auto; margin-left: auto;
} }
.topic_create_form .upload_file_dock { .quick_create_form .uploadItem {
display: flex;
}
.topic_create_form .uploadItem {
display: inline-block; display: inline-block;
margin-left: 8px; margin-left: 8px;
background-size: 25px 30px; background-size: 25px 30px;
@ -564,6 +561,9 @@ input, select, textarea {
content: "Locked"; content: "Locked";
} }
.topic_list .topic_row {
display: flex;
}
/* 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;
@ -573,19 +573,35 @@ input, select, textarea {
overflow: hidden; overflow: hidden;
} }
.topic_list .topic_left { .topic_list .topic_left {
width: calc(100% - 284px); width: 100%;
height: 59px;
display: flex;
padding: 0px;
overflow: hidden;
} }
.topic_list .topic_right { .topic_list .topic_right {
height: 35px; height: 59px;
margin-left: 8px; margin-left: 8px;
width: 108px; display: flex;
width: 284px;
padding: 0px;
} }
.topic_list .rowitem:last-child { .topic_list .topic_left img, .topic_list .topic_right img {
width: 64px;
}
.topic_list .topic_inner_left, .topic_right > span {
margin-left: 8px;
margin-top: 12px;
}
.topic_list .topic_row:last-child {
margin-bottom: 10px; margin-bottom: 10px;
} }
.topic_list .lastReplyAt { .topic_list .lastReplyAt {
white-space: nowrap; white-space: nowrap;
} }
.topic_list .starter:before {
content: "Starter: ";
}
.topic_name_input { .topic_name_input {
width: 100%; width: 100%;
@ -606,6 +622,10 @@ input, select, textarea {
top: -5px; top: -5px;
} }
.prev_link, .next_link {
display: none;
}
.postImage { .postImage {
max-width: 100%; max-width: 100%;
max-height: 200px;/*300px;*/ max-height: 200px;/*300px;*/

View File

@ -0,0 +1,13 @@
$(document).ready(function(){
// TODO: Run this when the image is loaded rather than when the document is ready?
$(".topic_list img").each(function(){
let aspectRatio = this.naturalHeight / this.naturalWidth;
console.log("aspectRatio ",aspectRatio);
console.log("this.height ",this.naturalHeight);
console.log("this.width ",this.naturalWidth);
$(this).css({
height: aspectRatio * this.width
});
});
});

View File

@ -4,5 +4,11 @@
"Version": "0.0.1", "Version": "0.0.1",
"Creator": "Azareal", "Creator": "Azareal",
"FullImage": "shadow.png", "FullImage": "shadow.png",
"URL": "github.com/Azareal/Gosora" "URL": "github.com/Azareal/Gosora",
"Resources": [
{
"Name":"shadow/misc.js",
"Location":"global"
}
]
} }

View File

@ -395,7 +395,7 @@ li a {
/* Topics */ /* Topics */
.topic_list { .topic_list .topic_row {
display: grid; display: grid;
grid-template-columns: calc(100% - 204px) 204px; grid-template-columns: calc(100% - 204px) 204px;
} }
@ -405,6 +405,9 @@ li a {
.topic_list .lastReplyAt { .topic_list .lastReplyAt {
white-space: nowrap; white-space: nowrap;
} }
.topic_list .starter:before {
content: "Starter: ";
}
@supports not (display: grid) { @supports not (display: grid) {
.topic_list .rowitem { .topic_list .rowitem {
@ -419,6 +422,20 @@ li a {
} }
} }
.topic_left, .topic_right {
display: flex;
padding: 0px;
height: 58px;
}
.topic_left img, .topic_right img {
width: 64px;
height: auto;
}
.topic_left .topic_inner_left, .topic_right > span {
margin-top: 10px;
margin-left: 8px;
}
.postImage { .postImage {
max-width: 100%; max-width: 100%;
max-height: 200px; max-height: 200px;

View File

@ -0,0 +1,13 @@
$(document).ready(function(){
// TODO: Run this when the image is loaded rather than when the document is ready?
$(".topic_list img").each(function(){
let aspectRatio = this.naturalHeight / this.naturalWidth;
console.log("aspectRatio ",aspectRatio);
console.log("this.height ",this.naturalHeight);
console.log("this.width ",this.naturalWidth);
$(this).css({
height: aspectRatio * this.width
});
});
});

View File

@ -12,5 +12,11 @@
"Name": "topic", "Name": "topic",
"Source": "topic_alt" "Source": "topic_alt"
} }
],
"Resources": [
{
"Name":"tempra-conflux/misc.js",
"Location":"global"
}
] ]
} }

View File

@ -228,10 +228,22 @@ li a {
margin-bottom: 3px; margin-bottom: 3px;
} }
.stat_green { background-color: lightgreen; border-color: lightgreen; } .stat_green {
.stat_orange { background-color: #ffe4b3; border-color: #ffe4b3; } background-color: lightgreen;
.stat_red { background-color: #ffb2b2; border-color: #ffb2b2; } border-color: lightgreen;
.stat_disabled { background-color: lightgray; border-color: lightgray; } }
.stat_orange {
background-color: #ffe4b3;
border-color: #ffe4b3;
}
.stat_red {
background-color: #ffb2b2;
border-color: #ffb2b2;
}
.stat_disabled {
background-color: lightgray;
border-color: lightgray;
}
.rowitem { .rowitem {
width: 100%; width: 100%;
@ -314,6 +326,10 @@ button {
/* Topics */ /* Topics */
.topic_list .starter:before {
content: "Starter: ";
}
.topic_sticky { .topic_sticky {
background-color: rgb(255,255,234); background-color: rgb(255,255,234);
} }

View File

@ -231,11 +231,25 @@ li a {
font-size: 16px; font-size: 16px;
} }
.grid_istat { margin-bottom: 5px; } .grid_istat {
.stat_green { background-color: lightgreen; border-color: lightgreen; } margin-bottom: 5px;
.stat_orange { background-color: #ffe4b3; border-color: #ffe4b3; } }
.stat_red { background-color: #ffb2b2; border-color: #ffb2b2; } .stat_green {
.stat_disabled { background-color: lightgray; border-color: lightgray; } background-color: lightgreen;
border-color: lightgreen;
}
.stat_orange {
background-color: #ffe4b3;
border-color: #ffe4b3;
}
.stat_red {
background-color: #ffb2b2;
border-color: #ffb2b2;
}
.stat_disabled {
background-color: lightgray;
border-color: lightgray;
}
.rowhead .rowitem, .colstack_head .rowitem { .rowhead .rowitem, .colstack_head .rowitem {
background-color: rgb(252,252,252); background-color: rgb(252,252,252);
@ -259,15 +273,24 @@ li a {
padding-right: 10px; padding-right: 10px;
background-color: white; background-color: white;
} }
/*.rowitem:not(.passive) { font-size: 17px; }*/ .rowitem:not(:last-child) {
.rowitem:not(:last-child) { border-bottom: 1px dotted hsl(0, 0%, 80%); } border-bottom: 1px solid hsl(0,0%,85%);
}
.rowitem a { .rowitem a {
text-decoration: none; text-decoration: none;
color: black; color: black;
} }
.rowitem a:hover { color: silver; } .rowitem a:hover {
.top_post { margin-bottom: 12px; } color: silver;
.opthead { display: none; } }
.top_post {
margin-bottom: 12px;
}
.opthead {
display: none;
}
.rowitem.has_opt { .rowitem.has_opt {
float: left; float: left;
@ -372,16 +395,22 @@ li a {
/* Topics */ /* Topics */
.topic_list { .topic_list .topic_row {
display: grid; display: grid;
grid-template-columns: calc(100% - 204px) 204px; grid-template-columns: calc(100% - 204px) 204px;
} }
.topic_list .rowitem {
border-bottom: 1px solid hsl(0,0%,85%);
}
.topic_list .topic_inner_right { .topic_list .topic_inner_right {
display: none; display: none;
} }
.topic_list .lastReplyAt { .topic_list .lastReplyAt {
white-space: nowrap; white-space: nowrap;
} }
.topic_list .starter:before {
content: "Starter: ";
}
@supports not (display: grid) { @supports not (display: grid) {
.topic_list .rowitem { .topic_list .rowitem {
@ -396,6 +425,21 @@ li a {
} }
} }
.topic_left, .topic_right {
display: flex;
padding: 0px;
height: 58px;
overflow: hidden;
}
.topic_left img, .topic_right img {
width: 64px;
height: auto;
}
.topic_left .topic_inner_left, .topic_right > span {
margin-top: 10px;
margin-left: 8px;
}
.postImage { .postImage {
max-width: 100%; max-width: 100%;
max-height: 200px; max-height: 200px;

View File

@ -0,0 +1,13 @@
$(document).ready(function(){
// TODO: Run this when the image is loaded rather than when the document is ready?
$(".topic_list img").each(function(){
let aspectRatio = this.naturalHeight / this.naturalWidth;
console.log("aspectRatio ",aspectRatio);
console.log("this.height ",this.naturalHeight);
console.log("this.width ",this.naturalWidth);
$(this).css({
height: aspectRatio * this.width
});
});
});

View File

@ -9,8 +9,8 @@
"Sidebars":"right", "Sidebars":"right",
"Resources": [ "Resources": [
{ {
"Name": "sample.css", "Name":"tempra-simple/misc.js",
"Location": "none" "Location":"global"
} }
] ]
} }