diff --git a/README.md b/README.md
index 365cea0c..14b776dd 100644
--- a/README.md
+++ b/README.md
@@ -24,14 +24,17 @@ Instructions on how to do so on Linux: https://downloads.mariadb.org/mariadb/rep
**Run the following commands:**
+go get github.com/go-sql-driver/mysql
go install github.com/go-sql-driver/mysql
+go get golang.org/x/crypto/bcrypt
go install golang.org/x/crypto/bcrypt
Tweak the config.go file and put your database details in there. Import data.sql into the same database. Comment out the first line (put /* and */ around it), if you've already made a database, and don't want the script to generate it for you.
Set the password column of your user account in the database to what you want your password to be. The system will encrypt your password when you login for the first time.
+Add -u after go get to update those libraries, if you've already got them installed.
# Run the program
diff --git a/config.go b/config.go
index edc05845..d695304f 100644
--- a/config.go
+++ b/config.go
@@ -12,7 +12,9 @@ var max_request_size = 5 * megabyte
// Misc
var default_route = route_topics
-var staff_css = "background-color: #ffeaff;"
+var default_group = 3
+var staff_css = " background-color: #ffeaff;"
var uncategorised_forum_visible = true
var siteurl = "localhost:8080"
-var noavatar = "https://api.adorable.io/avatars/285/{id}@" + siteurl + ".png"
\ No newline at end of file
+var noavatar = "https://api.adorable.io/avatars/285/{id}@" + siteurl + ".png"
+var items_per_page = 50
\ No newline at end of file
diff --git a/data.sql b/data.sql
index e4e17b44..6b64f2a6 100644
--- a/data.sql
+++ b/data.sql
@@ -13,7 +13,7 @@ CREATE TABLE `users`(
`email` varchar(200) DEFAULT '' not null,
`avatar` varchar(20) DEFAULT '' not null,
primary key(`uid`)
-);
+) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
CREATE TABLE `users_groups`(
`gid` int not null AUTO_INCREMENT,
@@ -24,7 +24,7 @@ CREATE TABLE `users_groups`(
`is_banned` tinyint DEFAULT 0 not null,
`tag` varchar(50) DEFAULT '' not null,
primary key(`gid`)
-);
+) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
CREATE TABLE `forums`(
`fid` int not null AUTO_INCREMENT,
@@ -36,7 +36,7 @@ CREATE TABLE `forums`(
`lastReplyerID` int DEFAULT 0 not null,
`lastTopicTime` datetime not null,
primary key(`fid`)
-);
+) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
CREATE TABLE `topics`(
`tid` int not null AUTO_INCREMENT,
@@ -50,7 +50,7 @@ CREATE TABLE `topics`(
`sticky` tinyint DEFAULT 0 not null,
`parentID` int DEFAULT 1 not null,
primary key(`tid`)
-);
+) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
CREATE TABLE `replies`(
`rid` int not null AUTO_INCREMENT,
@@ -62,7 +62,7 @@ CREATE TABLE `replies`(
`lastEdit` int not null,
`lastEditBy` int not null,
primary key(`rid`)
-);
+) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
CREATE TABLE `users_replies`(
`rid` int not null AUTO_INCREMENT,
@@ -74,7 +74,7 @@ CREATE TABLE `users_replies`(
`lastEdit` int not null,
`lastEditBy` int not null,
primary key(`rid`)
-);
+) CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
/*CREATE TABLE `replies_reports` (
`rid` int not null AUTO_INCREMENT,
@@ -86,9 +86,12 @@ CREATE TABLE `users_replies`(
INSERT INTO users(`name`,`group`,`is_super_admin`,`createdAt`,`lastActiveAt`)
VALUES ('Admin',1,1,NOW(),NOW());
+
INSERT INTO users_groups(`name`,`permissions`,`is_mod`,`is_admin`,`tag`) VALUES ('Administrator','{}',1,1,"Admin");
-INSERT INTO users_groups(`name`,`permissions`) VALUES ('Member','{}');
INSERT INTO users_groups(`name`,`permissions`,`is_mod`,`tag`) VALUES ('Moderator','{}',1,"Mod");
+INSERT INTO users_groups(`name`,`permissions`) VALUES ('Member','{}');
+INSERT INTO users_groups(`name`,`permissions`,`is_banned`) VALUES ('Banned','{}',1);
+
INSERT INTO forums(`name`,`lastTopicTime`) VALUES ('General',NOW());
INSERT INTO topics(`title`,`content`,`createdAt`,`lastReplyAt`,`createdBy`,`parentID`)
VALUES ('Test Topic','A topic automatically generated by the software.',NOW(),NOW(),1,1);
diff --git a/grosolo.exe b/grosolo.exe
index e20f1d6c..1ddf0377 100644
Binary files a/grosolo.exe and b/grosolo.exe differ
diff --git a/grosolo.exe~ b/grosolo.exe~
index 2254edd8..11cc778e 100644
Binary files a/grosolo.exe~ and b/grosolo.exe~ differ
diff --git a/main.go b/main.go
index 8ed78fc1..cad77237 100644
--- a/main.go
+++ b/main.go
@@ -6,7 +6,10 @@ import (
_ "github.com/go-sql-driver/mysql"
"log"
"mime"
+ "strings"
+ "strconv"
"path/filepath"
+ "os"
"io/ioutil"
"html/template"
)
@@ -40,6 +43,7 @@ var set_avatar_stmt *sql.Stmt
var set_username_stmt *sql.Stmt
var register_stmt *sql.Stmt
var username_exists_stmt *sql.Stmt
+var change_group_stmt *sql.Stmt
var create_profile_reply_stmt *sql.Stmt
var edit_profile_reply_stmt *sql.Stmt
var delete_profile_reply_stmt *sql.Stmt
@@ -48,8 +52,8 @@ var create_forum_stmt *sql.Stmt
var delete_forum_stmt *sql.Stmt
var update_forum_stmt *sql.Stmt
-var custom_pages map[string]string = make(map[string]string)
var templates = template.Must(template.ParseGlob("templates/*"))
+var custom_pages = template.Must(template.ParseGlob("pages/*"))
var no_css_tmpl = template.CSS("")
var staff_css_tmpl = template.CSS(staff_css)
var groups map[int]Group = make(map[int]Group)
@@ -60,7 +64,7 @@ func init_database(err error) {
if(dbpassword != ""){
dbpassword = ":" + dbpassword
}
- db, err = sql.Open("mysql",dbuser + dbpassword + "@tcp(" + dbhost + ":" + dbport + ")/" + dbname)
+ db, err = sql.Open("mysql",dbuser + dbpassword + "@tcp(" + dbhost + ":" + dbport + ")/" + dbname + "?collation=utf8mb4_general_ci")
if err != nil {
log.Fatal(err)
}
@@ -177,7 +181,7 @@ func init_database(err error) {
// create_account_stmt, err = db.Prepare("INSERT INTO
log.Print("Preparing register statement.")
- register_stmt, err = db.Prepare("INSERT INTO users(`name`,`password`,`salt`,`group`,`is_super_admin`,`session`) VALUES(?,?,?,2,0,?)")
+ register_stmt, err = db.Prepare("INSERT INTO users(`name`,`password`,`salt`,`group`,`is_super_admin`,`session`) VALUES(?,?,?," + strconv.Itoa(default_group) + ",0,?)")
if err != nil {
log.Fatal(err)
}
@@ -188,6 +192,12 @@ func init_database(err error) {
log.Fatal(err)
}
+ log.Print("Preparing change_group statement.")
+ change_group_stmt, err = db.Prepare("UPDATE `users` SET `group` = ? WHERE `uid` = ?")
+ if err != nil {
+ log.Fatal(err)
+ }
+
log.Print("Preparing create_profile_reply statement.")
create_profile_reply_stmt, err = db.Prepare("INSERT INTO users_replies(uid,content,parsed_content,createdAt,createdBy) VALUES(?,?,?,NOW(),?)")
if err != nil {
@@ -285,31 +295,31 @@ func main(){
var err error
init_database(err);
- log.Print("Loading the custom pages.")
- err = filepath.Walk("pages/", add_custom_page)
- if err != nil {
- log.Fatal(err)
- }
-
log.Print("Loading the static files.")
- files, err := ioutil.ReadDir("./public")
- if err != nil {
- log.Fatal(err)
- }
-
- for _, f := range files {
+ err = filepath.Walk("./public", func(path string, f os.FileInfo, err error) error {
if f.IsDir() {
- continue
- }
- data, err := ioutil.ReadFile("./public/" + f.Name())
- if err != nil {
- log.Fatal(err)
+ return nil
}
- log.Print("Added the '" + f.Name() + "' static file.")
- static_files["/static/" + f.Name()] = SFile{data,0,int64(len(data)),mime.TypeByExtension(filepath.Ext(f.Name())),f,f.ModTime().UTC().Format(http.TimeFormat)}
+ path = strings.Replace(path,"\\","/",-1)
+ data, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+
+ path = strings.TrimPrefix(path,"public/")
+ log.Print("Added the '" + path + "' static file.")
+ static_files["/static/" + path] = SFile{data,0,int64(len(data)),mime.TypeByExtension(filepath.Ext("/public/" + path)),f,f.ModTime().UTC().Format(http.TimeFormat)}
+ return nil
+ })
+ if err != nil {
+ log.Fatal(err)
}
+ //parse_map["grinning"] = []byte("๐")
+ //parse_map["grin"] = []byte("๐")
+ //parse_map["joy"] = []byte("๐")
+
// In a directory to stop it clashing with the other paths
http.HandleFunc("/static/", route_static)
//http.HandleFunc("/static/", route_fstatic)
@@ -359,7 +369,9 @@ func main(){
http.HandleFunc("/profile/reply/edit/submit/", route_profile_reply_edit_submit)
http.HandleFunc("/profile/reply/delete/submit/", route_profile_reply_delete_submit)
//http.HandleFunc("/user/:id/edit/", route_logout)
- //http.HandleFunc("/user/:id/ban/", route_logout)
+ http.HandleFunc("/users/ban/", route_ban)
+ http.HandleFunc("/users/ban/submit/", route_ban_submit)
+ http.HandleFunc("/users/unban/", route_unban)
// Admin
http.HandleFunc("/panel/forums/", route_panel_forums)
diff --git a/mod_routes.go b/mod_routes.go
index 74fbd876..b80d2a3b 100644
--- a/mod_routes.go
+++ b/mod_routes.go
@@ -47,7 +47,7 @@ func route_edit_topic(w http.ResponseWriter, r *http.Request) {
}
topic_content := html.EscapeString(r.PostFormValue("topic_content"))
- _, err = edit_topic_stmt.Exec(topic_name, topic_content, parse_message(topic_content), is_closed, tid)
+ _, err = edit_topic_stmt.Exec(topic_name, preparse_message(topic_content), parse_message(html.EscapeString(preparse_message(topic_content))), is_closed, tid)
if err != nil {
InternalErrorJSQ(err,w,r,user,is_js)
return
@@ -160,7 +160,7 @@ func route_reply_edit_submit(w http.ResponseWriter, r *http.Request) {
return
}
- content := html.EscapeString(r.PostFormValue("edit_item"))
+ content := html.EscapeString(preparse_message(r.PostFormValue("edit_item")))
_, err = edit_reply_stmt.Exec(content, parse_message(content), rid)
if err != nil {
InternalError(err,w,r,user)
@@ -262,7 +262,7 @@ func route_profile_reply_edit_submit(w http.ResponseWriter, r *http.Request) {
return
}
- content := html.EscapeString(r.PostFormValue("edit_item"))
+ content := html.EscapeString(preparse_message(r.PostFormValue("edit_item")))
_, err = edit_profile_reply_stmt.Exec(content, parse_message(content), rid)
if err != nil {
InternalError(err,w,r,user)
@@ -324,6 +324,127 @@ func route_profile_reply_delete_submit(w http.ResponseWriter, r *http.Request) {
}
}
+func route_ban(w http.ResponseWriter, r *http.Request) {
+ user := SessionCheck(w,r)
+ if !user.Is_Mod && !user.Is_Admin {
+ NoPermissions(w,r,user)
+ return
+ }
+
+ uid, err := strconv.Atoi(r.URL.Path[len("/users/ban/"):])
+ if err != nil {
+ LocalError("The provided User ID is not a valid number.",w,r,user)
+ return
+ }
+
+ var uname string
+ err = db.QueryRow("SELECT name from users where uid = ?", uid).Scan(&uname)
+ if err == sql.ErrNoRows {
+ LocalError("The user you're trying to ban no longer exists.",w,r,user)
+ return
+ } else if err != nil {
+ InternalError(err,w,r,user)
+ return
+ }
+
+ confirm_msg := "Are you sure you want to ban '" + uname + "'?"
+ yousure := AreYouSure{"/users/ban/submit/" + strconv.Itoa(uid),confirm_msg}
+
+ pi := Page{"Ban User","ban-user",user,tList,yousure}
+ templates.ExecuteTemplate(w,"areyousure.html", pi)
+}
+
+func route_ban_submit(w http.ResponseWriter, r *http.Request) {
+ user := SessionCheck(w,r)
+ if !user.Is_Mod && !user.Is_Admin {
+ NoPermissions(w,r,user)
+ return
+ }
+ if r.FormValue("session") != user.Session {
+ SecurityError(w,r,user)
+ return
+ }
+
+ uid, err := strconv.Atoi(r.URL.Path[len("/users/ban/submit/"):])
+ if err != nil {
+ LocalError("The provided User ID is not a valid number.",w,r,user)
+ return
+ }
+
+ var group int
+ var is_super_admin bool
+ err = db.QueryRow("SELECT `group`, `is_super_admin` from `users` where `uid` = ?", uid).Scan(&group, &is_super_admin)
+ if err == sql.ErrNoRows {
+ LocalError("The user you're trying to ban no longer exists.",w,r,user)
+ return
+ } else if err != nil {
+ InternalError(err,w,r,user)
+ return
+ }
+
+ if is_super_admin || groups[group].Is_Admin || groups[group].Is_Mod {
+ LocalError("You may not ban another staff member.",w,r,user)
+ return
+ }
+ if uid == user.ID {
+ LocalError("You may not ban yourself.",w,r,user)
+ return
+ }
+ if uid == -2 {
+ LocalError("You may not ban me. Fine, I will offer up some guidance unto thee. Come to my lair, young one. /arcane-tower/",w,r,user)
+ return
+ }
+
+ if groups[group].Is_Banned {
+ LocalError("The user you're trying to unban is already banned.",w,r,user)
+ return
+ }
+
+ _, err = change_group_stmt.Exec(4, uid)
+ if err != nil {
+ InternalError(err,w,r,user)
+ return
+ }
+ http.Redirect(w,r,"/users/" + strconv.Itoa(uid),http.StatusSeeOther)
+}
+
+func route_unban(w http.ResponseWriter, r *http.Request) {
+ user := SessionCheck(w,r)
+ if !user.Is_Mod && !user.Is_Admin {
+ NoPermissions(w,r,user)
+ return
+ }
+
+ uid, err := strconv.Atoi(r.URL.Path[len("/users/unban/"):])
+ if err != nil {
+ LocalError("The provided User ID is not a valid number.",w,r,user)
+ return
+ }
+
+ var uname string
+ var group int
+ err = db.QueryRow("SELECT `name`, `group` from users where `uid` = ?", uid).Scan(&uname, &group)
+ if err == sql.ErrNoRows {
+ LocalError("The user you're trying to unban no longer exists.",w,r,user)
+ return
+ } else if err != nil {
+ InternalError(err,w,r,user)
+ return
+ }
+
+ if !groups[group].Is_Banned {
+ LocalError("The user you're trying to unban isn't banned.",w,r,user)
+ return
+ }
+
+ _, err = change_group_stmt.Exec(default_group, uid)
+ if err != nil {
+ InternalError(err,w,r,user)
+ return
+ }
+ http.Redirect(w,r,"/users/" + strconv.Itoa(uid),http.StatusSeeOther)
+}
+
func route_panel_forums(w http.ResponseWriter, r *http.Request){
user := SessionCheck(w,r)
if !user.Is_Admin {
diff --git a/pages.go b/pages.go
index efdff4ec..1c925b03 100644
--- a/pages.go
+++ b/pages.go
@@ -1,9 +1,6 @@
package main
import "strings"
-import "os"
-import "log"
-import "io/ioutil"
-import "path/filepath"
+//import "regexp"
type Page struct
{
@@ -27,31 +24,55 @@ type AreYouSure struct
Message string
}
-func add_custom_page(path string, f os.FileInfo, err error) error {
- if err != nil {
- return err
- }
-
- // Is this a directory..?
- fileInfo, err := os.Stat(path)
- is_dir := fileInfo.IsDir()
- if err != nil {
- return err
- }
- if is_dir {
- return err
- }
-
- custom_page, err := ioutil.ReadFile(path)
- if err != nil {
- return err
- }
- log.Print("Loaded the '" + path + "' page.")
- name := strings.TrimSuffix(path, filepath.Ext(path))
- custom_pages[name] = string(custom_page)
- return nil
+func shortcode_to_unicode(msg string) string {
+ //re := regexp.MustCompile(":(.):")
+ msg = strings.Replace(msg,":grinning:","๐",-1)
+ msg = strings.Replace(msg,":grin:","๐",-1)
+ msg = strings.Replace(msg,":joy:","๐",-1)
+ msg = strings.Replace(msg,":rofl:","๐คฃ",-1)
+ msg = strings.Replace(msg,":smiley:","๐",-1)
+ msg = strings.Replace(msg,":smile:","๐",-1)
+ msg = strings.Replace(msg,":sweat_smile:","๐
",-1)
+ msg = strings.Replace(msg,":laughing:","๐",-1)
+ msg = strings.Replace(msg,":satisfied:","๐",-1)
+ msg = strings.Replace(msg,":wink:","๐",-1)
+ msg = strings.Replace(msg,":blush:","๐",-1)
+ msg = strings.Replace(msg,":yum:","๐",-1)
+ msg = strings.Replace(msg,":sunglasses:","๐",-1)
+ msg = strings.Replace(msg,":heart_eyes:","๐",-1)
+ msg = strings.Replace(msg,":kissing_heart:","๐",-1)
+ msg = strings.Replace(msg,":kissing:","๐",-1)
+ msg = strings.Replace(msg,":kissing_smiling_eyes:","๐",-1)
+ msg = strings.Replace(msg,":kissing_closed_eyes:","๐",-1)
+ msg = strings.Replace(msg,":relaxed:","โบ๏ธ",-1)
+ msg = strings.Replace(msg,":slight_smile:","๐",-1)
+ msg = strings.Replace(msg,":hugging:","๐ค",-1)
+ msg = strings.Replace(msg,":thinking:","๐ค",-1)
+ msg = strings.Replace(msg,":neutral_face:","๐",-1)
+ msg = strings.Replace(msg,":expressionless:","๐",-1)
+ msg = strings.Replace(msg,":no_mouth:","๐ถ",-1)
+ msg = strings.Replace(msg,":rolling_eyes:","๐",-1)
+ msg = strings.Replace(msg,":smirk:","๐",-1)
+ msg = strings.Replace(msg,":persevere:","๐ฃ",-1)
+ msg = strings.Replace(msg,":disappointed_relieved:","๐ฅ",-1)
+ msg = strings.Replace(msg,":open_mouth:","๐ฎ",-1)
+ msg = strings.Replace(msg,":zipper_mouth:","๐ค",-1)
+ msg = strings.Replace(msg,":hushed:","๐ฏ",-1)
+ msg = strings.Replace(msg,":sleepy:","๐ช",-1)
+ msg = strings.Replace(msg,":tired_face:","๐ซ",-1)
+ msg = strings.Replace(msg,":sleeping:","๐ด",-1)
+ msg = strings.Replace(msg,":relieved:","๐",-1)
+ msg = strings.Replace(msg,":nerd:","๐ค",-1)
+ return strings.Replace(msg,":stuck_out_tongue:","๐",-1)
+}
+
+func preparse_message(msg string) string {
+ return shortcode_to_unicode(msg)
}
func parse_message(msg string) string {
+ msg = strings.Replace(msg,":)","๐",-1)
+ msg = strings.Replace(msg,":D","๐",-1)
+ //msg = shortcode_to_unicode(msg)
return strings.Replace(msg,"\n","
",-1)
}
\ No newline at end of file
diff --git a/pages/test.html b/pages/test.html
index e9440bae..b2af7cb0 100644
--- a/pages/test.html
+++ b/pages/test.html
@@ -1 +1,6 @@
-
Testing
\ No newline at end of file
+{{template "header.html" . }}
+
+Testing
+{{template "footer.html" . }}
\ No newline at end of file
diff --git a/public/jquery-emojiarea/LICENSE b/public/jquery-emojiarea/LICENSE
new file mode 100644
index 00000000..31ee9edd
--- /dev/null
+++ b/public/jquery-emojiarea/LICENSE
@@ -0,0 +1,14 @@
+The MIT License
+
+Copyright (c) 2011-2012 by linyows
+
+Permission is hereby granted, freef charge, to any personbtaining a copyf this software and associated documentation files (the 'Software'),
+to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copiesf the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copiesr substantial portionsf the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/public/jquery-emojiarea/README.md b/public/jquery-emojiarea/README.md
new file mode 100644
index 00000000..b3ca174a
--- /dev/null
+++ b/public/jquery-emojiarea/README.md
@@ -0,0 +1,99 @@
+*NOTICE* This extends the [original plugin](https://github.com/diy/jquery-emojiarea) by groups. (See screenshot at dropdown menu)
+
+#### About this extension
+This was originally created for my project *The Msngr*, which should have become an open-source end-to-end encrypted messanger. I am always happy about an attribution to me and my website, e.g. [\Pius Ladenburger\](https://pius-ladenburger.de).
+
+I hope this helps you and makes your day easier.
+
+#.emojiarea()
+
+A small **6kb** [jQuery](http://jquery.com/) plugin for turning regular textareas into ones that support emojis, WYSIWYG style! Set up a list of available emojis, call `$('textarea').emojiarea()` and you're done (basically). There's a plain-text fallback, so if the browser doesn't support [contentEditable](http://caniuse.com/#search=contenteditable), it will degrade gracefullyโthe user will still be able to use the dropdown menu of emojis.
+
+![Screenshot](http://i.imgur.com/C4Z8F.gif)
+
+```html
+
+
+```
+
+## Configuration
+
+### Dropdown Menu
+
+![Dropdown Screenshot](http://i.imgur.com/EuTTpHk.png)
+
+By default, the plugin will insert a link after the editor that toggles the emoji selector when clicked.
+
+```html
+Emojis
+```
+
+If you wish change this behavior and have the button placed before the editor, or change the label of the link, use:
+
+```javascript
+$('textarea').emojiarea({
+ buttonLabel: 'Add Emoji',
+ buttonPosition: 'before'
+});
+```
+
+Alternatively, if you wish to use your own button:
+
+```javascript
+$('textarea').emojiarea({button: '#your-button'});
+```
+
+For customizing the visual appearance, see the [CSS / Skinning](#css--skinning) section.
+
+### Available Emojis
+
+```javascript
+$.emojiarea.path = '/path/to/folder/with/icons';
+$.emojiarea.icons = {
+ ':smile:' : 'smile.png',
+ ':angry:' : 'angry.png',
+ ':flushed:' : 'flushed.png',
+ ':neckbeard:' : 'neckbeard.png',
+ ':laughing:' : 'laughing.png'
+};
+```
+
+### Defaults
+
+If you wish to set the defaults for `$().emojiarea()`, extend `$.emojiarea.defaults` like so:
+
+```javascript
+$.extend($.emojiarea.defaults, {
+ buttonPosition: 'before'
+});
+```
+
+For a basic set of emojis, see "packs/basic".
+
+## CSS / Skinning
+
+See [jquery.emojiarea.css](https://github.com/diy/jquery-emojiarea/blob/master/jquery.emojiarea.css) for the few fundamental CSS styles needed for this to work.
+
+Basically, you'll want to adjust the following styles:
+
+```css
+.emoji-wysiwyg-editor /* the editor box itself */
+.emoji-menu > div /* the dropdown menu with options */
+.emoji-wysiwyg-editor img /* the emoji images in the editor */
+```
+
+## Footnotes
+
+* Huge props to [Tim Down](http://stackoverflow.com/users/96100/tim-down) for the many insightful answers on Stack Overflow having to deal with cross-browser selection handling.
+* If you have a really rad set of emojis and would like to share, please fork this, add them to "packs/", and submit a pull request!
+* For a giant list of emojis (used by Github, Basecamp, et al), see ["Emoji cheat sheet"](http://www.emoji-cheat-sheet.com/).
+
+## License
+
+Copyright © 2012 DIY Co and 2015 Pius Ladenburger
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+
+I am always happy about an attribution to me and my website, e.g. [\Pius Ladenburger\](https://pius-ladenburger.de)
diff --git a/public/jquery-emojiarea/emojis.js b/public/jquery-emojiarea/emojis.js
new file mode 100644
index 00000000..ff684dd4
--- /dev/null
+++ b/public/jquery-emojiarea/emojis.js
@@ -0,0 +1,862 @@
+$.emojiarea.path = '/static/smilies/emojiarea';
+$.emojiarea.icons = [{"name" : "", "icons" : {':bowtie:' : 'bowtie.png',
+':smile:' : 'smile.png',
+':laughing:' : 'laughing.png',
+':blush:' : 'blush.png',
+':smiley:' : 'smiley.png',
+':relaxed:' : 'relaxed.png',
+':smirk:' : 'smirk.png',
+':heart_eyes:' : 'heart_eyes.png',
+':kissing_heart:' : 'kissing_heart.png',
+':kissing_closed_eyes:' : 'kissing_closed_eyes.png',
+':flushed:' : 'flushed.png',
+':relieved:' : 'relieved.png',
+':satisfied:' : 'satisfied.png',
+':grin:' : 'grin.png',
+':wink:' : 'wink.png',
+':stuck_out_tongue_winking_eye:' : 'stuck_out_tongue_winking_eye.png',
+':stuck_out_tongue_closed_eyes:' : 'stuck_out_tongue_closed_eyes.png',
+':grinning:' : 'grinning.png',
+':kissing:' : 'kissing.png',
+':kissing_smiling_eyes:' : 'kissing_smiling_eyes.png',
+':stuck_out_tongue:' : 'stuck_out_tongue.png',
+':sleeping:' : 'sleeping.png',
+':worried:' : 'worried.png',
+':frowning:' : 'frowning.png',
+':anguished:' : 'anguished.png',
+':open_mouth:' : 'open_mouth.png',
+':grimacing:' : 'grimacing.png',
+':confused:' : 'confused.png',
+':hushed:' : 'hushed.png',
+':expressionless:' : 'expressionless.png',
+':unamused:' : 'unamused.png',
+':sweat_smile:' : 'sweat_smile.png',
+':sweat:' : 'sweat.png',
+':weary:' : 'weary.png',
+':pensive:' : 'pensive.png',
+':disappointed:' : 'disappointed.png',
+':confounded:' : 'confounded.png',
+':fearful:' : 'fearful.png',
+':cold_sweat:' : 'cold_sweat.png',
+':persevere:' : 'persevere.png',
+':joy:' : 'joy.png',
+':astonished:' : 'astonished.png',
+':scream:' : 'scream.png',
+':neckbeard:' : 'neckbeard.png',
+':tired_face:' : 'tired_face.png',
+':angry:' : 'angry.png',
+':rage:' : 'rage.png',
+':triumph:' : 'triumph.png',
+':sleepy:' : 'sleepy.png',
+':yum:' : 'yum.png',
+':mask:' : 'mask.png',
+':sunglasses:' : 'sunglasses.png',
+':dizzy_face:' : 'dizzy_face.png',
+':imp:' : 'imp.png',
+':smiling_imp:' : 'smiling_imp.png',
+':neutral_face:' : 'neutral_face.png',
+':no_mouth:' : 'no_mouth.png',
+':innocent:' : 'innocent.png',
+':alien:' : 'alien.png',
+':yellow_heart:' : 'yellow_heart.png',
+':blue_heart:' : 'blue_heart.png',
+':purple_heart:' : 'purple_heart.png',
+':heart:' : 'heart.png',
+':green_heart:' : 'green_heart.png',
+':broken_heart:' : 'broken_heart.png',
+':heartbeat:' : 'heartbeat.png',
+':heartpulse:' : 'heartpulse.png',
+':two_hearts:' : 'two_hearts.png',
+':revolving_hearts:' : 'revolving_hearts.png',
+':cupid:' : 'cupid.png',
+':sparkling_heart:' : 'sparkling_heart.png',
+':sparkles:' : 'sparkles.png',
+':star:' : 'star.png',
+':star2:' : 'star2.png',
+':dizzy:' : 'dizzy.png',
+':boom:' : 'boom.png',
+':collision:' : 'collision.png',
+':anger:' : 'anger.png',
+':exclamation:' : 'exclamation.png',
+':question:' : 'question.png',
+':grey_exclamation:' : 'grey_exclamation.png',
+':grey_question:' : 'grey_question.png',
+':zzz:' : 'zzz.png',
+':dash:' : 'dash.png',
+':sweat_drops:' : 'sweat_drops.png',
+':notes:' : 'notes.png',
+':musical_note:' : 'musical_note.png',
+':fire:' : 'fire.png',
+':hankey:' : 'hankey.png',
+':poop:' : 'poop.png',
+':shit:' : 'shit.png',
+':+1:' : '+1.png',
+':thumbsup:' : 'thumbsup.png',
+':-1:' : '-1.png',
+':thumbsdown:' : 'thumbsdown.png',
+':ok_hand:' : 'ok_hand.png',
+':punch:' : 'punch.png',
+':facepunch:' : 'facepunch.png',
+':fist:' : 'fist.png',
+':v:' : 'v.png',
+':wave:' : 'wave.png',
+':hand:' : 'hand.png',
+':raised_hand:' : 'raised_hand.png',
+':open_hands:' : 'open_hands.png',
+':point_up:' : 'point_up.png',
+':point_down:' : 'point_down.png',
+':point_left:' : 'point_left.png',
+':point_right:' : 'point_right.png',
+':raised_hands:' : 'raised_hands.png',
+':pray:' : 'pray.png',
+':point_up_2:' : 'point_up_2.png',
+':clap:' : 'clap.png',
+':muscle:' : 'muscle.png',
+':metal:' : 'metal.png',
+':walking:' : 'walking.png',
+':runner:' : 'runner.png',
+':running:' : 'running.png',
+':couple:' : 'couple.png',
+':family:' : 'family.png',
+':two_men_holding_hands:' : 'two_men_holding_hands.png',
+':two_women_holding_hands:' : 'two_women_holding_hands.png',
+':ok_woman:' : 'ok_woman.png',
+':no_good:' : 'no_good.png',
+':information_desk_person:' : 'information_desk_person.png',
+':bride_with_veil:' : 'bride_with_veil.png',
+':person_with_pouting_face:' : 'person_with_pouting_face.png',
+':person_frowning:' : 'person_frowning.png',
+':bow:' : 'bow.png',
+':couplekiss:' : 'couplekiss.png',
+':couple_with_heart:' : 'couple_with_heart.png',
+':massage:' : 'massage.png',
+':haircut:' : 'haircut.png',
+':nail_care:' : 'nail_care.png',
+':boy:' : 'boy.png',
+':girl:' : 'girl.png',
+':woman:' : 'woman.png',
+':man:' : 'man.png',
+':baby:' : 'baby.png',
+':older_woman:' : 'older_woman.png',
+':older_man:' : 'older_man.png',
+':person_with_blond_hair:' : 'person_with_blond_hair.png',
+':man_with_gua_pi_mao:' : 'man_with_gua_pi_mao.png',
+':man_with_turban:' : 'man_with_turban.png',
+':construction_worker:' : 'construction_worker.png',
+':cop:' : 'cop.png',
+':angel:' : 'angel.png',
+':princess:' : 'princess.png',
+':smiley_cat:' : 'smiley_cat.png',
+':smile_cat:' : 'smile_cat.png',
+':heart_eyes_cat:' : 'heart_eyes_cat.png',
+':kissing_cat:' : 'kissing_cat.png',
+':smirk_cat:' : 'smirk_cat.png',
+':scream_cat:' : 'scream_cat.png',
+':crying_cat_face:' : 'crying_cat_face.png',
+':joy_cat:' : 'joy_cat.png',
+':pouting_cat:' : 'pouting_cat.png',
+':japanese_ogre:' : 'japanese_ogre.png',
+':japanese_goblin:' : 'japanese_goblin.png',
+':see_no_evil:' : 'see_no_evil.png',
+':hear_no_evil:' : 'hear_no_evil.png',
+':speak_no_evil:' : 'speak_no_evil.png',
+':guardsman:' : 'guardsman.png',
+':skull:' : 'skull.png',
+':feet:' : 'feet.png',
+':lips:' : 'lips.png',
+':kiss:' : 'kiss.png',
+':droplet:' : 'droplet.png',
+':ear:' : 'ear.png',
+':eyes:' : 'eyes.png',
+':nose:' : 'nose.png',
+':tongue:' : 'tongue.png',
+':love_letter:' : 'love_letter.png',
+':bust_in_silhouette:' : 'bust_in_silhouette.png',
+':busts_in_silhouette:' : 'busts_in_silhouette.png',
+':speech_balloon:' : 'speech_balloon.png',
+':thought_balloon:' : 'thought_balloon.png',
+':feelsgood:' : 'feelsgood.png',
+':finnadie:' : 'finnadie.png',
+':goberserk:' : 'goberserk.png',
+':godmode:' : 'godmode.png',
+':hurtrealbad:' : 'hurtrealbad.png',
+':rage1:' : 'rage1.png',
+':rage2:' : 'rage2.png',
+':rage3:' : 'rage3.png',
+':rage4:' : 'rage4.png',
+':suspect:' : 'suspect.png',
+':trollface:' : 'trollface.png'}},
+{'name': '', 'icons' : {':sunny:' : 'sunny.png',
+':umbrella:' : 'umbrella.png',
+':cloud:' : 'cloud.png',
+':snowflake:' : 'snowflake.png',
+':snowman:' : 'snowman.png',
+':zap:' : 'zap.png',
+':cyclone:' : 'cyclone.png',
+':foggy:' : 'foggy.png',
+':ocean:' : 'ocean.png',
+':cat:' : 'cat.png',
+':dog:' : 'dog.png',
+':mouse:' : 'mouse.png',
+':hamster:' : 'hamster.png',
+':rabbit:' : 'rabbit.png',
+':wolf:' : 'wolf.png',
+':frog:' : 'frog.png',
+':tiger:' : 'tiger.png',
+':koala:' : 'koala.png',
+':bear:' : 'bear.png',
+':pig:' : 'pig.png',
+':pig_nose:' : 'pig_nose.png',
+':cow:' : 'cow.png',
+':boar:' : 'boar.png',
+':monkey_face:' : 'monkey_face.png',
+':monkey:' : 'monkey.png',
+':horse:' : 'horse.png',
+':racehorse:' : 'racehorse.png',
+':camel:' : 'camel.png',
+':sheep:' : 'sheep.png',
+':elephant:' : 'elephant.png',
+':panda_face:' : 'panda_face.png',
+':snake:' : 'snake.png',
+':bird:' : 'bird.png',
+':baby_chick:' : 'baby_chick.png',
+':hatched_chick:' : 'hatched_chick.png',
+':hatching_chick:' : 'hatching_chick.png',
+':chicken:' : 'chicken.png',
+':penguin:' : 'penguin.png',
+':turtle:' : 'turtle.png',
+':bug:' : 'bug.png',
+':honeybee:' : 'honeybee.png',
+':ant:' : 'ant.png',
+':beetle:' : 'beetle.png',
+':snail:' : 'snail.png',
+':octopus:' : 'octopus.png',
+':tropical_fish:' : 'tropical_fish.png',
+':fish:' : 'fish.png',
+':whale:' : 'whale.png',
+':whale2:' : 'whale2.png',
+':dolphin:' : 'dolphin.png',
+':cow2:' : 'cow2.png',
+':ram:' : 'ram.png',
+':rat:' : 'rat.png',
+':water_buffalo:' : 'water_buffalo.png',
+':tiger2:' : 'tiger2.png',
+':rabbit2:' : 'rabbit2.png',
+':dragon:' : 'dragon.png',
+':goat:' : 'goat.png',
+':rooster:' : 'rooster.png',
+':dog2:' : 'dog2.png',
+':pig2:' : 'pig2.png',
+':mouse2:' : 'mouse2.png',
+':ox:' : 'ox.png',
+':dragon_face:' : 'dragon_face.png',
+':blowfish:' : 'blowfish.png',
+':crocodile:' : 'crocodile.png',
+':dromedary_camel:' : 'dromedary_camel.png',
+':leopard:' : 'leopard.png',
+':cat2:' : 'cat2.png',
+':poodle:' : 'poodle.png',
+':paw_prints:' : 'paw_prints.png',
+':bouquet:' : 'bouquet.png',
+':cherry_blossom:' : 'cherry_blossom.png',
+':tulip:' : 'tulip.png',
+':four_leaf_clover:' : 'four_leaf_clover.png',
+':rose:' : 'rose.png',
+':sunflower:' : 'sunflower.png',
+':hibiscus:' : 'hibiscus.png',
+':maple_leaf:' : 'maple_leaf.png',
+':leaves:' : 'leaves.png',
+':fallen_leaf:' : 'fallen_leaf.png',
+':herb:' : 'herb.png',
+':mushroom:' : 'mushroom.png',
+':cactus:' : 'cactus.png',
+':palm_tree:' : 'palm_tree.png',
+':evergreen_tree:' : 'evergreen_tree.png',
+':deciduous_tree:' : 'deciduous_tree.png',
+':chestnut:' : 'chestnut.png',
+':seedling:' : 'seedling.png',
+':blossom:' : 'blossom.png',
+':ear_of_rice:' : 'ear_of_rice.png',
+':shell:' : 'shell.png',
+':globe_with_meridians:' : 'globe_with_meridians.png',
+':sun_with_face:' : 'sun_with_face.png',
+':full_moon_with_face:' : 'full_moon_with_face.png',
+':new_moon_with_face:' : 'new_moon_with_face.png',
+':new_moon:' : 'new_moon.png',
+':waxing_crescent_moon:' : 'waxing_crescent_moon.png',
+':first_quarter_moon:' : 'first_quarter_moon.png',
+':waxing_gibbous_moon:' : 'waxing_gibbous_moon.png',
+':full_moon:' : 'full_moon.png',
+':waning_gibbous_moon:' : 'waning_gibbous_moon.png',
+':last_quarter_moon:' : 'last_quarter_moon.png',
+':waning_crescent_moon:' : 'waning_crescent_moon.png',
+':last_quarter_moon_with_face:' : 'last_quarter_moon_with_face.png',
+':first_quarter_moon_with_face:' : 'first_quarter_moon_with_face.png',
+':moon:' : 'moon.png',
+':earth_africa:' : 'earth_africa.png',
+':earth_americas:' : 'earth_americas.png',
+':earth_asia:' : 'earth_asia.png',
+':volcano:' : 'volcano.png',
+':milky_way:' : 'milky_way.png',
+':partly_sunny:' : 'partly_sunny.png',
+':octocat:' : 'octocat.png'}},
+{'name': '', 'icons' : {':squirrel:' : 'squirrel.png',
+':bamboo:' : 'bamboo.png',
+':gift_heart:' : 'gift_heart.png',
+':dolls:' : 'dolls.png',
+':school_satchel:' : 'school_satchel.png',
+':mortar_board:' : 'mortar_board.png',
+':flags:' : 'flags.png',
+':fireworks:' : 'fireworks.png',
+':sparkler:' : 'sparkler.png',
+':wind_chime:' : 'wind_chime.png',
+':rice_scene:' : 'rice_scene.png',
+':jack_o_lantern:' : 'jack_o_lantern.png',
+':ghost:' : 'ghost.png',
+':santa:' : 'santa.png',
+':christmas_tree:' : 'christmas_tree.png',
+':gift:' : 'gift.png',
+':bell:' : 'bell.png',
+':no_bell:' : 'no_bell.png',
+':tanabata_tree:' : 'tanabata_tree.png',
+':tada:' : 'tada.png',
+':confetti_ball:' : 'confetti_ball.png',
+':balloon:' : 'balloon.png',
+':crystal_ball:' : 'crystal_ball.png',
+':cd:' : 'cd.png',
+':dvd:' : 'dvd.png',
+':floppy_disk:' : 'floppy_disk.png',
+':camera:' : 'camera.png',
+':video_camera:' : 'video_camera.png',
+':movie_camera:' : 'movie_camera.png',
+':computer:' : 'computer.png',
+':tv:' : 'tv.png',
+':iphone:' : 'iphone.png',
+':phone:' : 'phone.png',
+':telephone:' : 'telephone.png',
+':telephone_receiver:' : 'telephone_receiver.png',
+':pager:' : 'pager.png',
+':fax:' : 'fax.png',
+':minidisc:' : 'minidisc.png',
+':vhs:' : 'vhs.png',
+':sound:' : 'sound.png',
+':speaker:' : 'speaker.png',
+':mute:' : 'mute.png',
+':loudspeaker:' : 'loudspeaker.png',
+':mega:' : 'mega.png',
+':hourglass:' : 'hourglass.png',
+':hourglass_flowing_sand:' : 'hourglass_flowing_sand.png',
+':alarm_clock:' : 'alarm_clock.png',
+':watch:' : 'watch.png',
+':radio:' : 'radio.png',
+':satellite:' : 'satellite.png',
+':loop:' : 'loop.png',
+':mag:' : 'mag.png',
+':mag_right:' : 'mag_right.png',
+':unlock:' : 'unlock.png',
+':lock:' : 'lock.png',
+':lock_with_ink_pen:' : 'lock_with_ink_pen.png',
+':closed_lock_with_key:' : 'closed_lock_with_key.png',
+':key:' : 'key.png',
+':bulb:' : 'bulb.png',
+':flashlight:' : 'flashlight.png',
+':high_brightness:' : 'high_brightness.png',
+':low_brightness:' : 'low_brightness.png',
+':electric_plug:' : 'electric_plug.png',
+':battery:' : 'battery.png',
+':calling:' : 'calling.png',
+':email:' : 'email.png',
+':mailbox:' : 'mailbox.png',
+':postbox:' : 'postbox.png',
+':bath:' : 'bath.png',
+':bathtub:' : 'bathtub.png',
+':shower:' : 'shower.png',
+':toilet:' : 'toilet.png',
+':wrench:' : 'wrench.png',
+':nut_and_bolt:' : 'nut_and_bolt.png',
+':hammer:' : 'hammer.png',
+':seat:' : 'seat.png',
+':moneybag:' : 'moneybag.png',
+':yen:' : 'yen.png',
+':dollar:' : 'dollar.png',
+':pound:' : 'pound.png',
+':euro:' : 'euro.png',
+':credit_card:' : 'credit_card.png',
+':money_with_wings:' : 'money_with_wings.png',
+':e-mail:' : 'e-mail.png',
+':inbox_tray:' : 'inbox_tray.png',
+':outbox_tray:' : 'outbox_tray.png',
+':envelope:' : 'envelope.png',
+':incoming_envelope:' : 'incoming_envelope.png',
+':postal_horn:' : 'postal_horn.png',
+':mailbox_closed:' : 'mailbox_closed.png',
+':mailbox_with_mail:' : 'mailbox_with_mail.png',
+':mailbox_with_no_mail:' : 'mailbox_with_no_mail.png',
+':door:' : 'door.png',
+':smoking:' : 'smoking.png',
+':bomb:' : 'bomb.png',
+':gun:' : 'gun.png',
+':hocho:' : 'hocho.png',
+':pill:' : 'pill.png',
+':syringe:' : 'syringe.png',
+':page_facing_up:' : 'page_facing_up.png',
+':page_with_curl:' : 'page_with_curl.png',
+':bookmark_tabs:' : 'bookmark_tabs.png',
+':bar_chart:' : 'bar_chart.png',
+':chart_with_upwards_trend:' : 'chart_with_upwards_trend.png',
+':chart_with_downwards_trend:' : 'chart_with_downwards_trend.png',
+':scroll:' : 'scroll.png',
+':clipboard:' : 'clipboard.png',
+':calendar:' : 'calendar.png',
+':date:' : 'date.png',
+':card_index:' : 'card_index.png',
+':file_folder:' : 'file_folder.png',
+':open_file_folder:' : 'open_file_folder.png',
+':scissors:' : 'scissors.png',
+':pushpin:' : 'pushpin.png',
+':paperclip:' : 'paperclip.png',
+':black_nib:' : 'black_nib.png',
+':pencil2:' : 'pencil2.png',
+':straight_ruler:' : 'straight_ruler.png',
+':triangular_ruler:' : 'triangular_ruler.png',
+':closed_book:' : 'closed_book.png',
+':green_book:' : 'green_book.png',
+':blue_book:' : 'blue_book.png',
+':orange_book:' : 'orange_book.png',
+':notebook:' : 'notebook.png',
+':notebook_with_decorative_cover:' : 'notebook_with_decorative_cover.png',
+':ledger:' : 'ledger.png',
+':books:' : 'books.png',
+':bookmark:' : 'bookmark.png',
+':name_badge:' : 'name_badge.png',
+':microscope:' : 'microscope.png',
+':telescope:' : 'telescope.png',
+':newspaper:' : 'newspaper.png',
+':football:' : 'football.png',
+':basketball:' : 'basketball.png',
+':soccer:' : 'soccer.png',
+':baseball:' : 'baseball.png',
+':tennis:' : 'tennis.png',
+':8ball:' : '8ball.png',
+':rugby_football:' : 'rugby_football.png',
+':bowling:' : 'bowling.png',
+':golf:' : 'golf.png',
+':mountain_bicyclist:' : 'mountain_bicyclist.png',
+':bicyclist:' : 'bicyclist.png',
+':horse_racing:' : 'horse_racing.png',
+':snowboarder:' : 'snowboarder.png',
+':swimmer:' : 'swimmer.png',
+':surfer:' : 'surfer.png',
+':ski:' : 'ski.png',
+':spades:' : 'spades.png',
+':hearts:' : 'hearts.png',
+':clubs:' : 'clubs.png',
+':diamonds:' : 'diamonds.png',
+':gem:' : 'gem.png',
+':ring:' : 'ring.png',
+':trophy:' : 'trophy.png',
+':musical_score:' : 'musical_score.png',
+':musical_keyboard:' : 'musical_keyboard.png',
+':violin:' : 'violin.png',
+':space_invader:' : 'space_invader.png',
+':video_game:' : 'video_game.png',
+':black_joker:' : 'black_joker.png',
+':flower_playing_cards:' : 'flower_playing_cards.png',
+':game_die:' : 'game_die.png',
+':dart:' : 'dart.png',
+':mahjong:' : 'mahjong.png',
+':clapper:' : 'clapper.png',
+':memo:' : 'memo.png',
+':pencil:' : 'pencil.png',
+':book:' : 'book.png',
+':art:' : 'art.png',
+':microphone:' : 'microphone.png',
+':headphones:' : 'headphones.png',
+':trumpet:' : 'trumpet.png',
+':saxophone:' : 'saxophone.png',
+':guitar:' : 'guitar.png',
+':shoe:' : 'shoe.png',
+':sandal:' : 'sandal.png',
+':high_heel:' : 'high_heel.png',
+':lipstick:' : 'lipstick.png',
+':boot:' : 'boot.png',
+':shirt:' : 'shirt.png',
+':tshirt:' : 'tshirt.png',
+':necktie:' : 'necktie.png',
+':womans_clothes:' : 'womans_clothes.png',
+':dress:' : 'dress.png',
+':running_shirt_with_sash:' : 'running_shirt_with_sash.png',
+':jeans:' : 'jeans.png',
+':kimono:' : 'kimono.png',
+':bikini:' : 'bikini.png',
+':ribbon:' : 'ribbon.png',
+':tophat:' : 'tophat.png',
+':crown:' : 'crown.png',
+':womans_hat:' : 'womans_hat.png',
+':mans_shoe:' : 'mans_shoe.png',
+':closed_umbrella:' : 'closed_umbrella.png',
+':briefcase:' : 'briefcase.png',
+':handbag:' : 'handbag.png',
+':pouch:' : 'pouch.png',
+':purse:' : 'purse.png',
+':eyeglasses:' : 'eyeglasses.png',
+':fishing_pole_and_fish:' : 'fishing_pole_and_fish.png',
+':coffee:' : 'coffee.png',
+':tea:' : 'tea.png',
+':sake:' : 'sake.png',
+':baby_bottle:' : 'baby_bottle.png',
+':beer:' : 'beer.png',
+':beers:' : 'beers.png',
+':cocktail:' : 'cocktail.png',
+':tropical_drink:' : 'tropical_drink.png',
+':wine_glass:' : 'wine_glass.png',
+':fork_and_knife:' : 'fork_and_knife.png',
+':pizza:' : 'pizza.png',
+':hamburger:' : 'hamburger.png',
+':fries:' : 'fries.png',
+':poultry_leg:' : 'poultry_leg.png',
+':meat_on_bone:' : 'meat_on_bone.png',
+':spaghetti:' : 'spaghetti.png',
+':curry:' : 'curry.png',
+':fried_shrimp:' : 'fried_shrimp.png',
+':bento:' : 'bento.png',
+':sushi:' : 'sushi.png',
+':fish_cake:' : 'fish_cake.png',
+':rice_ball:' : 'rice_ball.png',
+':rice_cracker:' : 'rice_cracker.png',
+':rice:' : 'rice.png',
+':ramen:' : 'ramen.png',
+':stew:' : 'stew.png',
+':oden:' : 'oden.png',
+':dango:' : 'dango.png',
+':egg:' : 'egg.png',
+':bread:' : 'bread.png',
+':doughnut:' : 'doughnut.png',
+':custard:' : 'custard.png',
+':icecream:' : 'icecream.png',
+':ice_cream:' : 'ice_cream.png',
+':shaved_ice:' : 'shaved_ice.png',
+':birthday:' : 'birthday.png',
+':cake:' : 'cake.png',
+':cookie:' : 'cookie.png',
+':chocolate_bar:' : 'chocolate_bar.png',
+':candy:' : 'candy.png',
+':lollipop:' : 'lollipop.png',
+':honey_pot:' : 'honey_pot.png',
+':apple:' : 'apple.png',
+':green_apple:' : 'green_apple.png',
+':tangerine:' : 'tangerine.png',
+':lemon:' : 'lemon.png',
+':cherries:' : 'cherries.png',
+':grapes:' : 'grapes.png',
+':watermelon:' : 'watermelon.png',
+':strawberry:' : 'strawberry.png',
+':peach:' : 'peach.png',
+':melon:' : 'melon.png',
+':banana:' : 'banana.png',
+':pear:' : 'pear.png',
+':pineapple:' : 'pineapple.png',
+':sweet_potato:' : 'sweet_potato.png',
+':eggplant:' : 'eggplant.png',
+':tomato:' : 'tomato.png'}},
+{'name': '', 'icons' : {':corn:' : 'corn.png',
+':house:' : 'house.png',
+':house_with_garden:' : 'house_with_garden.png',
+':school:' : 'school.png',
+':office:' : 'office.png',
+':post_office:' : 'post_office.png',
+':hospital:' : 'hospital.png',
+':bank:' : 'bank.png',
+':convenience_store:' : 'convenience_store.png',
+':love_hotel:' : 'love_hotel.png',
+':hotel:' : 'hotel.png',
+':wedding:' : 'wedding.png',
+':church:' : 'church.png',
+':department_store:' : 'department_store.png',
+':european_post_office:' : 'european_post_office.png',
+':city_sunrise:' : 'city_sunrise.png',
+':city_sunset:' : 'city_sunset.png',
+':japanese_castle:' : 'japanese_castle.png',
+':european_castle:' : 'european_castle.png',
+':tent:' : 'tent.png',
+':factory:' : 'factory.png',
+':tokyo_tower:' : 'tokyo_tower.png',
+':japan:' : 'japan.png',
+':mount_fuji:' : 'mount_fuji.png',
+':sunrise_over_mountains:' : 'sunrise_over_mountains.png',
+':sunrise:' : 'sunrise.png',
+':stars:' : 'stars.png',
+':statue_of_liberty:' : 'statue_of_liberty.png',
+':bridge_at_night:' : 'bridge_at_night.png',
+':carousel_horse:' : 'carousel_horse.png',
+':rainbow:' : 'rainbow.png',
+':ferris_wheel:' : 'ferris_wheel.png',
+':fountain:' : 'fountain.png',
+':roller_coaster:' : 'roller_coaster.png',
+':ship:' : 'ship.png',
+':speedboat:' : 'speedboat.png',
+':boat:' : 'boat.png',
+':sailboat:' : 'sailboat.png',
+':rowboat:' : 'rowboat.png',
+':anchor:' : 'anchor.png',
+':rocket:' : 'rocket.png',
+':airplane:' : 'airplane.png',
+':helicopter:' : 'helicopter.png',
+':steam_locomotive:' : 'steam_locomotive.png',
+':tram:' : 'tram.png',
+':mountain_railway:' : 'mountain_railway.png',
+':bike:' : 'bike.png',
+':aerial_tramway:' : 'aerial_tramway.png',
+':suspension_railway:' : 'suspension_railway.png',
+':mountain_cableway:' : 'mountain_cableway.png',
+':tractor:' : 'tractor.png',
+':blue_car:' : 'blue_car.png',
+':oncoming_automobile:' : 'oncoming_automobile.png',
+':car:' : 'car.png',
+':red_car:' : 'red_car.png',
+':taxi:' : 'taxi.png',
+':oncoming_taxi:' : 'oncoming_taxi.png',
+':articulated_lorry:' : 'articulated_lorry.png',
+':bus:' : 'bus.png',
+':oncoming_bus:' : 'oncoming_bus.png',
+':rotating_light:' : 'rotating_light.png',
+':police_car:' : 'police_car.png',
+':oncoming_police_car:' : 'oncoming_police_car.png',
+':fire_engine:' : 'fire_engine.png',
+':ambulance:' : 'ambulance.png',
+':minibus:' : 'minibus.png',
+':truck:' : 'truck.png',
+':train:' : 'train.png',
+':station:' : 'station.png',
+':train2:' : 'train2.png',
+':bullettrain_front:' : 'bullettrain_front.png',
+':bullettrain_side:' : 'bullettrain_side.png',
+':light_rail:' : 'light_rail.png',
+':monorail:' : 'monorail.png',
+':railway_car:' : 'railway_car.png',
+':trolleybus:' : 'trolleybus.png',
+':ticket:' : 'ticket.png',
+':fuelpump:' : 'fuelpump.png',
+':vertical_traffic_light:' : 'vertical_traffic_light.png',
+':traffic_light:' : 'traffic_light.png',
+':warning:' : 'warning.png',
+':construction:' : 'construction.png',
+':beginner:' : 'beginner.png',
+':atm:' : 'atm.png',
+':slot_machine:' : 'slot_machine.png',
+':busstop:' : 'busstop.png',
+':barber:' : 'barber.png',
+':hotsprings:' : 'hotsprings.png',
+':checkered_flag:' : 'checkered_flag.png',
+':crossed_flags:' : 'crossed_flags.png',
+':izakaya_lantern:' : 'izakaya_lantern.png',
+':moyai:' : 'moyai.png',
+':circus_tent:' : 'circus_tent.png',
+':performing_arts:' : 'performing_arts.png',
+':round_pushpin:' : 'round_pushpin.png',
+':triangular_flag_on_post:' : 'triangular_flag_on_post.png',
+':jp:' : 'jp.png',
+':kr:' : 'kr.png',
+':cn:' : 'cn.png',
+':us:' : 'us.png',
+':fr:' : 'fr.png',
+':es:' : 'es.png',
+':it:' : 'it.png',
+':ru:' : 'ru.png',
+':gb:' : 'gb.png',
+':uk:' : 'uk.png',
+':de:' : 'de.png'}},
+{'name': '', 'icons' : {':one:' : 'one.png',
+':two:' : 'two.png',
+':three:' : 'three.png',
+':four:' : 'four.png',
+':five:' : 'five.png',
+':six:' : 'six.png',
+':seven:' : 'seven.png',
+':eight:' : 'eight.png',
+':nine:' : 'nine.png',
+':keycap_ten:' : 'keycap_ten.png',
+':1234:' : '1234.png',
+':zero:' : 'zero.png',
+':hash:' : 'hash.png',
+':symbols:' : 'symbols.png',
+':arrow_backward:' : 'arrow_backward.png',
+':arrow_down:' : 'arrow_down.png',
+':arrow_forward:' : 'arrow_forward.png',
+':arrow_left:' : 'arrow_left.png',
+':capital_abcd:' : 'capital_abcd.png',
+':abcd:' : 'abcd.png',
+':abc:' : 'abc.png',
+':arrow_lower_left:' : 'arrow_lower_left.png',
+':arrow_lower_right:' : 'arrow_lower_right.png',
+':arrow_right:' : 'arrow_right.png',
+':arrow_up:' : 'arrow_up.png',
+':arrow_upper_left:' : 'arrow_upper_left.png',
+':arrow_upper_right:' : 'arrow_upper_right.png',
+':arrow_double_down:' : 'arrow_double_down.png',
+':arrow_double_up:' : 'arrow_double_up.png',
+':arrow_down_small:' : 'arrow_down_small.png',
+':arrow_heading_down:' : 'arrow_heading_down.png',
+':arrow_heading_up:' : 'arrow_heading_up.png',
+':leftwards_arrow_with_hook:' : 'leftwards_arrow_with_hook.png',
+':arrow_right_hook:' : 'arrow_right_hook.png',
+':left_right_arrow:' : 'left_right_arrow.png',
+':arrow_up_down:' : 'arrow_up_down.png',
+':arrow_up_small:' : 'arrow_up_small.png',
+':arrows_clockwise:' : 'arrows_clockwise.png',
+':arrows_counterclockwise:' : 'arrows_counterclockwise.png',
+':rewind:' : 'rewind.png',
+':fast_forward:' : 'fast_forward.png',
+':information_source:' : 'information_source.png',
+':ok:' : 'ok.png',
+':twisted_rightwards_arrows:' : 'twisted_rightwards_arrows.png',
+':repeat:' : 'repeat.png',
+':repeat_one:' : 'repeat_one.png',
+':new:' : 'new.png',
+':top:' : 'top.png',
+':up:' : 'up.png',
+':cool:' : 'cool.png',
+':free:' : 'free.png',
+':ng:' : 'ng.png',
+':cinema:' : 'cinema.png',
+':koko:' : 'koko.png',
+':signal_strength:' : 'signal_strength.png',
+':u5272:' : 'u5272.png',
+':u5408:' : 'u5408.png',
+':u55b6:' : 'u55b6.png',
+':u6307:' : 'u6307.png',
+':u6708:' : 'u6708.png',
+':u6709:' : 'u6709.png',
+':u6e80:' : 'u6e80.png',
+':u7121:' : 'u7121.png',
+':u7533:' : 'u7533.png',
+':u7a7a:' : 'u7a7a.png',
+':u7981:' : 'u7981.png',
+':sa:' : 'sa.png',
+':restroom:' : 'restroom.png',
+':mens:' : 'mens.png',
+':womens:' : 'womens.png',
+':baby_symbol:' : 'baby_symbol.png',
+':no_smoking:' : 'no_smoking.png',
+':parking:' : 'parking.png',
+':wheelchair:' : 'wheelchair.png',
+':metro:' : 'metro.png',
+':baggage_claim:' : 'baggage_claim.png',
+':accept:' : 'accept.png',
+':wc:' : 'wc.png',
+':potable_water:' : 'potable_water.png',
+':put_litter_in_its_place:' : 'put_litter_in_its_place.png',
+':secret:' : 'secret.png',
+':congratulations:' : 'congratulations.png',
+':m:' : 'm.png',
+':passport_control:' : 'passport_control.png',
+':left_luggage:' : 'left_luggage.png',
+':customs:' : 'customs.png',
+':ideograph_advantage:' : 'ideograph_advantage.png',
+':cl:' : 'cl.png',
+':sos:' : 'sos.png',
+':id:' : 'id.png',
+':no_entry_sign:' : 'no_entry_sign.png',
+':underage:' : 'underage.png',
+':no_mobile_phones:' : 'no_mobile_phones.png',
+':do_not_litter:' : 'do_not_litter.png',
+':non-potable_water:' : 'non-potable_water.png',
+':no_bicycles:' : 'no_bicycles.png',
+':no_pedestrians:' : 'no_pedestrians.png',
+':children_crossing:' : 'children_crossing.png',
+':no_entry:' : 'no_entry.png',
+':eight_spoked_asterisk:' : 'eight_spoked_asterisk.png',
+':eight_pointed_black_star:' : 'eight_pointed_black_star.png',
+':heart_decoration:' : 'heart_decoration.png',
+':vs:' : 'vs.png',
+':vibration_mode:' : 'vibration_mode.png',
+':mobile_phone_off:' : 'mobile_phone_off.png',
+':chart:' : 'chart.png',
+':currency_exchange:' : 'currency_exchange.png',
+':aries:' : 'aries.png',
+':taurus:' : 'taurus.png',
+':gemini:' : 'gemini.png',
+':cancer:' : 'cancer.png',
+':leo:' : 'leo.png',
+':virgo:' : 'virgo.png',
+':libra:' : 'libra.png',
+':scorpius:' : 'scorpius.png',
+':sagittarius:' : 'sagittarius.png',
+':capricorn:' : 'capricorn.png',
+':aquarius:' : 'aquarius.png',
+':pisces:' : 'pisces.png',
+':ophiuchus:' : 'ophiuchus.png',
+':six_pointed_star:' : 'six_pointed_star.png',
+':negative_squared_cross_mark:' : 'negative_squared_cross_mark.png',
+':a:' : 'a.png',
+':b:' : 'b.png',
+':ab:' : 'ab.png',
+':o2:' : 'o2.png',
+':diamond_shape_with_a_dot_inside:' : 'diamond_shape_with_a_dot_inside.png',
+':recycle:' : 'recycle.png',
+':end:' : 'end.png',
+':on:' : 'on.png',
+':soon:' : 'soon.png',
+':clock1:' : 'clock1.png',
+':clock130:' : 'clock130.png',
+':clock10:' : 'clock10.png',
+':clock1030:' : 'clock1030.png',
+':clock11:' : 'clock11.png',
+':clock1130:' : 'clock1130.png',
+':clock12:' : 'clock12.png',
+':clock1230:' : 'clock1230.png',
+':clock2:' : 'clock2.png',
+':clock230:' : 'clock230.png',
+':clock3:' : 'clock3.png',
+':clock330:' : 'clock330.png',
+':clock4:' : 'clock4.png',
+':clock430:' : 'clock430.png',
+':clock5:' : 'clock5.png',
+':clock530:' : 'clock530.png',
+':clock6:' : 'clock6.png',
+':clock630:' : 'clock630.png',
+':clock7:' : 'clock7.png',
+':clock730:' : 'clock730.png',
+':clock8:' : 'clock8.png',
+':clock830:' : 'clock830.png',
+':clock9:' : 'clock9.png',
+':clock930:' : 'clock930.png',
+':heavy_dollar_sign:' : 'heavy_dollar_sign.png',
+':copyright:' : 'copyright.png',
+':registered:' : 'registered.png',
+':tm:' : 'tm.png',
+':x:' : 'x.png',
+':heavy_exclamation_mark:' : 'heavy_exclamation_mark.png',
+':bangbang:' : 'bangbang.png',
+':interrobang:' : 'interrobang.png',
+':o:' : 'o.png',
+':heavy_multiplication_x:' : 'heavy_multiplication_x.png',
+':heavy_plus_sign:' : 'heavy_plus_sign.png',
+':heavy_minus_sign:' : 'heavy_minus_sign.png',
+':heavy_division_sign:' : 'heavy_division_sign.png',
+':white_flower:' : 'white_flower.png',
+':100:' : '100.png',
+':heavy_check_mark:' : 'heavy_check_mark.png',
+':ballot_box_with_check:' : 'ballot_box_with_check.png',
+':radio_button:' : 'radio_button.png',
+':link:' : 'link.png',
+':curly_loop:' : 'curly_loop.png',
+':wavy_dash:' : 'wavy_dash.png',
+':part_alternation_mark:' : 'part_alternation_mark.png',
+':trident:' : 'trident.png',
+':black_square:' : 'black_square.png',
+':white_check_mark:' : 'white_check_mark.png',
+':black_square_button:' : 'black_square_button.png',
+':white_square_button:' : 'white_square_button.png',
+':black_circle:' : 'black_circle.png',
+':white_circle:' : 'white_circle.png',
+':red_circle:' : 'red_circle.png',
+':large_blue_circle:' : 'large_blue_circle.png',
+':large_blue_diamond:' : 'large_blue_diamond.png',
+':large_orange_diamond:' : 'large_orange_diamond.png',
+':small_blue_diamond:' : 'small_blue_diamond.png',
+':small_orange_diamond:' : 'small_orange_diamond.png',
+':small_red_triangle:' : 'small_red_triangle.png',
+':small_red_triangle_down:' : 'small_red_triangle_down.png',
+':shipit:' : 'shipit.png',
+}}, ];
\ No newline at end of file
diff --git a/public/jquery-emojiarea/jquery.emojiarea.css b/public/jquery-emojiarea/jquery.emojiarea.css
new file mode 100644
index 00000000..d2bbff21
--- /dev/null
+++ b/public/jquery-emojiarea/jquery.emojiarea.css
@@ -0,0 +1,97 @@
+.emoji-wysiwyg-editor {
+ border: 1px solid #d0d0d0;
+ overflow: auto;
+ outline: none;
+}
+.emoji-wysiwyg-editor img {
+ width: 20px;
+ height: 20px;
+ vertical-align: middle;
+ margin: -3px 0 0 0;
+}
+.emoji-menu {
+ position: absolute;
+ z-index: 999;
+ width: 180px;
+ margin-left: -100px;
+ padding: 0;
+ overflow: hidden;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.emoji-menu > div {
+ max-height: 200px;
+ overflow: hidden;
+ background: #fff;
+ width: 200px;
+ -webkit-overflow-scrolling: touch;
+ overflow: auto;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-box-shadow: 0 1px 5px rgba(0,0,0,0.3);
+ -moz-box-shadow: 0 1px 5px rgba(0,0,0,0.3);
+ box-shadow: 0 1px 5px rgba(0,0,0,0.3);
+ padding-top: 40px;
+}
+.emoji-menu img {
+ width: 25px;
+ height: 25px;
+ vertical-align: middle;
+ border: 0 none;
+}
+.emoji-menu a {
+ margin: -1px 0 0 -1px;
+ border: 1px solid #f2f2f2;
+ padding: 5px;
+ display: block;
+ float: left;
+}
+.emoji-menu a:hover {
+ background-color: #fffae7;
+}
+.emoji-menu:after {
+ content: ' ';
+ display: block;
+ clear: left;
+}
+.emoji-menu a .label {
+ display: none;
+}
+
+.emoji-menu div {
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.emoji-menu .group-selector {
+ position: absolute;
+ list-style-type: none;
+ height: 40px;
+ top: 0;
+ left: 0;
+ width: 100%;
+ background-color: rgb(255,255,255);
+ background-color: rgba(255,255,255, .9);
+}
+.emoji-menu .group-selector li {
+ height: 15px;
+ width: 17px;
+ padding: 5px;
+}
+.emoji-menu .group-selector a:last-child li {
+ width: 15px;
+}
+.emoji-menu .group-selector a {
+ color: #EB7878;
+ text-decoration: none;
+ border: none;
+ background-color: transparent;
+}
+.emoji-menu .group-selector a:hover, .emoji-menu .group-selector a.active {
+ color:#000000;
+}
\ No newline at end of file
diff --git a/public/jquery-emojiarea/jquery.emojiarea.js b/public/jquery-emojiarea/jquery.emojiarea.js
new file mode 100644
index 00000000..c6485896
--- /dev/null
+++ b/public/jquery-emojiarea/jquery.emojiarea.js
@@ -0,0 +1,485 @@
+/**
+ * emojiarea - A rich textarea control that supports emojis, WYSIWYG-style.
+ * Copyright (c) 2012 DIY Co
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
+ * file except in compliance with the License. You may obtain a copy of the License at:
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+ * ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ *
+ * @author Brian Reavis
+ */
+
+(function($, window, document) {
+
+ var ELEMENT_NODE = 1;
+ var TEXT_NODE = 3;
+ var TAGS_BLOCK = ['p', 'div', 'pre', 'form'];
+ var KEY_ESC = 27;
+ var KEY_TAB = 9;
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ $.emojiarea = {
+ path: '',
+ icons: {},
+ defaults: {
+ button: null,
+ buttonLabel: 'Emojis',
+ buttonPosition: 'after'
+ }
+ };
+
+ $.fn.emojiarea = function(options) {
+ options = $.extend({}, $.emojiarea.defaults, options);
+ return this.each(function() {
+ var $textarea = $(this);
+ if ('contentEditable' in document.body && options.wysiwyg !== false) {
+ new EmojiArea_WYSIWYG($textarea, options);
+ } else {
+ new EmojiArea_Plain($textarea, options);
+ }
+ });
+ };
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ var util = {};
+
+ util.restoreSelection = (function() {
+ if (window.getSelection) {
+ return function(savedSelection) {
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ for (var i = 0, len = savedSelection.length; i < len; ++i) {
+ sel.addRange(savedSelection[i]);
+ }
+ };
+ } else if (document.selection && document.selection.createRange) {
+ return function(savedSelection) {
+ if (savedSelection) {
+ savedSelection.select();
+ }
+ };
+ }
+ })();
+
+ util.saveSelection = (function() {
+ if (window.getSelection) {
+ return function() {
+ var sel = window.getSelection(), ranges = [];
+ if (sel.rangeCount) {
+ for (var i = 0, len = sel.rangeCount; i < len; ++i) {
+ ranges.push(sel.getRangeAt(i));
+ }
+ }
+ return ranges;
+ };
+ } else if (document.selection && document.selection.createRange) {
+ return function() {
+ var sel = document.selection;
+ return (sel.type.toLowerCase() !== 'none') ? sel.createRange() : null;
+ };
+ }
+ })();
+
+ util.replaceSelection = (function() {
+ if (window.getSelection) {
+ return function(content) {
+ var range, sel = window.getSelection();
+ var node = typeof content === 'string' ? document.createTextNode(content) : content;
+ if (sel.getRangeAt && sel.rangeCount) {
+ range = sel.getRangeAt(0);
+ range.deleteContents();
+ range.insertNode(document.createTextNode(' '));
+ range.insertNode(node);
+ range.setStart(node, 0);
+
+ window.setTimeout(function() {
+ range = document.createRange();
+ range.setStartAfter(node);
+ range.collapse(true);
+ sel.removeAllRanges();
+ sel.addRange(range);
+ }, 0);
+ }
+ }
+ } else if (document.selection && document.selection.createRange) {
+ return function(content) {
+ var range = document.selection.createRange();
+ if (typeof content === 'string') {
+ range.text = content;
+ } else {
+ range.pasteHTML(content.outerHTML);
+ }
+ }
+ }
+ })();
+
+ util.insertAtCursor = function(text, el) {
+ text = ' ' + text;
+ var val = el.value, endIndex, startIndex, range;
+ if (typeof el.selectionStart != 'undefined' && typeof el.selectionEnd != 'undefined') {
+ startIndex = el.selectionStart;
+ endIndex = el.selectionEnd;
+ el.value = val.substring(0, startIndex) + text + val.substring(el.selectionEnd);
+ el.selectionStart = el.selectionEnd = startIndex + text.length;
+ } else if (typeof document.selection != 'undefined' && typeof document.selection.createRange != 'undefined') {
+ el.focus();
+ range = document.selection.createRange();
+ range.text = text;
+ range.select();
+ }
+ };
+
+ util.extend = function(a, b) {
+ if (typeof a === 'undefined' || !a) { a = {}; }
+ if (typeof b === 'object') {
+ for (var key in b) {
+ if (b.hasOwnProperty(key)) {
+ a[key] = b[key];
+ }
+ }
+ }
+ return a;
+ };
+
+ util.escapeRegex = function(str) {
+ return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
+ };
+
+ util.htmlEntities = function(str) {
+ return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
+ };
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ var EmojiArea = function() {};
+
+ EmojiArea.prototype.setup = function() {
+ var self = this;
+
+ this.$editor.on('focus', function() { self.hasFocus = true; });
+ this.$editor.on('blur', function() { self.hasFocus = false; });
+
+ this.setupButton();
+ };
+
+ EmojiArea.prototype.setupButton = function() {
+ var self = this;
+ var $button;
+
+ if (this.options.button) {
+ $button = $(this.options.button);
+ } else if (this.options.button !== false) {
+ $button = $('');
+ $button.html(this.options.buttonLabel);
+ $button.addClass('emoji-button');
+ $button.attr({title: this.options.buttonLabel});
+ this.$editor[this.options.buttonPosition]($button);
+ } else {
+ $button = $('');
+ }
+
+ $button.on('click', function(e) {
+ EmojiMenu.show(self);
+ e.stopPropagation();
+ });
+
+ this.$button = $button;
+ };
+
+ EmojiArea.createIcon = function(group, emoji) {
+ var filename = $.emojiarea.icons[group]['icons'][emoji];
+ var path = $.emojiarea.path || '';
+ if (path.length && path.charAt(path.length - 1) !== '/') {
+ path += '/';
+ }
+ return '';
+ };
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ /**
+ * Editor (plain-text)
+ *
+ * @constructor
+ * @param {object} $textarea
+ * @param {object} options
+ */
+
+ var EmojiArea_Plain = function($textarea, options) {
+ this.options = options;
+ this.$textarea = $textarea;
+ this.$editor = $textarea;
+ this.setup();
+ };
+
+ EmojiArea_Plain.prototype.insert = function(group, emoji) {
+ if (!$.emojiarea.icons[group]['icons'].hasOwnProperty(emoji)) return;
+ util.insertAtCursor(emoji, this.$textarea[0]);
+ this.$textarea.trigger('change');
+ };
+
+ EmojiArea_Plain.prototype.val = function() {
+ return this.$textarea.val();
+ };
+
+ util.extend(EmojiArea_Plain.prototype, EmojiArea.prototype);
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ /**
+ * Editor (rich)
+ *
+ * @constructor
+ * @param {object} $textarea
+ * @param {object} options
+ */
+
+ var EmojiArea_WYSIWYG = function($textarea, options) {
+ var self = this;
+
+ this.options = options;
+ this.$textarea = $textarea;
+ this.$editor = $('').addClass('emoji-wysiwyg-editor');
+ this.$editor.text($textarea.val());
+ this.$editor.attr({contenteditable: 'true'});
+ this.$editor.on('blur keyup paste', function() { return self.onChange.apply(self, arguments); });
+ this.$editor.on('mousedown focus', function() { document.execCommand('enableObjectResizing', false, false); });
+ this.$editor.on('blur', function() { document.execCommand('enableObjectResizing', true, true); });
+
+ var html = this.$editor.text();
+ var emojis = $.emojiarea.icons;
+ for (var group in emojis) {
+ for (var key in emojis[group]['icons']) {
+ if (emojis[group]['icons'].hasOwnProperty(key)) {
+ html = html.replace(new RegExp(util.escapeRegex(key), 'g'), EmojiArea.createIcon(group, key));
+ }
+ }
+ }
+ this.$editor.html(html);
+
+ $textarea.hide().after(this.$editor);
+
+ this.setup();
+
+ this.$button.on('mousedown', function() {
+ if (self.hasFocus) {
+ self.selection = util.saveSelection();
+ }
+ });
+ };
+
+ EmojiArea_WYSIWYG.prototype.onChange = function() {
+ this.$textarea.val(this.val()).trigger('change');
+ };
+
+ EmojiArea_WYSIWYG.prototype.insert = function(group, emoji) {
+ var content;
+ var $img = $(EmojiArea.createIcon(group, emoji));
+ if ($img[0].attachEvent) {
+ $img[0].attachEvent('onresizestart', function(e) { e.returnValue = false; }, false);
+ }
+
+ this.$editor.trigger('focus');
+ if (this.selection) {
+ util.restoreSelection(this.selection);
+ }
+ try { util.replaceSelection($img[0]); } catch (e) {}
+ this.onChange();
+ };
+
+ EmojiArea_WYSIWYG.prototype.val = function() {
+ var lines = [];
+ var line = [];
+
+ var flush = function() {
+ lines.push(line.join(''));
+ line = [];
+ };
+
+ var sanitizeNode = function(node) {
+ if (node.nodeType === TEXT_NODE) {
+ line.push(node.nodeValue);
+ } else if (node.nodeType === ELEMENT_NODE) {
+ var tagName = node.tagName.toLowerCase();
+ var isBlock = TAGS_BLOCK.indexOf(tagName) !== -1;
+
+ if (isBlock && line.length) flush();
+
+ if (tagName === 'img') {
+ var alt = node.getAttribute('alt') || '';
+ if (alt) line.push(alt);
+ return;
+ } else if (tagName === 'br') {
+ flush();
+ }
+
+ var children = node.childNodes;
+ for (var i = 0; i < children.length; i++) {
+ sanitizeNode(children[i]);
+ }
+
+ if (isBlock && line.length) flush();
+ }
+ };
+
+ var children = this.$editor[0].childNodes;
+ for (var i = 0; i < children.length; i++) {
+ sanitizeNode(children[i]);
+ }
+
+ if (line.length) flush();
+
+ return lines.join('\n');
+ };
+
+ util.extend(EmojiArea_WYSIWYG.prototype, EmojiArea.prototype);
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ /**
+ * Emoji Dropdown Menu
+ *
+ * @constructor
+ * @param {object} emojiarea
+ */
+ var EmojiMenu = function() {
+ var self = this;
+ var $body = $(document.body);
+ var $window = $(window);
+
+ this.visible = false;
+ this.emojiarea = null;
+ this.$menu = $('
');
+ this.$menu.addClass('emoji-menu');
+ this.$menu.hide();
+ this.$items = $('
').appendTo(this.$menu);
+
+ $body.append(this.$menu);
+
+ $body.on('keydown', function(e) {
+ if (e.keyCode === KEY_ESC || e.keyCode === KEY_TAB) {
+ self.hide();
+ }
+ });
+
+ $body.on('mouseup', function() {
+ self.hide();
+ });
+
+ $window.on('resize', function() {
+ if (self.visible) self.reposition();
+ });
+
+ this.$menu.on('mouseup', 'a', function(e) {
+ e.stopPropagation();
+ return false;
+ });
+
+ this.$menu.on('click', 'a', function(e) {
+ var emoji = $('.label', $(this)).text();
+ var group = $('.label', $(this)).parent().parent().attr('group');
+ if(group && emoji !== ''){
+ window.setTimeout(function() {
+ self.onItemSelected.apply(self, [group, emoji]);
+ }, 0);
+ e.stopPropagation();
+ return false;
+ }
+ });
+
+ this.load();
+ };
+
+ EmojiMenu.prototype.onItemSelected = function(group, emoji) {
+ this.emojiarea.insert(group, emoji);
+ this.hide();
+ };
+
+ EmojiMenu.prototype.load = function() {
+ var html = [];
+ var groups = [];
+ var options = $.emojiarea.icons;
+ var path = $.emojiarea.path;
+ if (path.length && path.charAt(path.length - 1) !== '/') {
+ path += '/';
+ }
+ groups.push('
');
+ this.$items.html(html.join(''));
+ this.$menu.prepend(groups.join(''));
+ this.$menu.find('.tab_switch').each(function(i) {
+ if (i != 0) {
+ var select = $(this).attr('href');
+ $(select).hide();
+ } else {
+ $(this).addClass('active');
+ }
+ $(this).click(function() {
+ $(this).addClass('active');
+ $(this).siblings().removeClass('active');
+ $('.select_group').hide();
+ var select = $(this).attr('href');
+ $(select).show();
+ });
+ });
+ };
+
+ EmojiMenu.prototype.reposition = function() {
+ var $button = this.emojiarea.$button;
+ var offset = $button.offset();
+ offset.top += $button.outerHeight();
+ offset.left += Math.round($button.outerWidth() / 2);
+
+ this.$menu.css({
+ top: offset.top,
+ left: offset.left
+ });
+ };
+
+ EmojiMenu.prototype.hide = function(callback) {
+ if (this.emojiarea) {
+ this.emojiarea.menu = null;
+ this.emojiarea.$button.removeClass('on');
+ this.emojiarea = null;
+ }
+ this.visible = false;
+ this.$menu.hide();
+ };
+
+ EmojiMenu.prototype.show = function(emojiarea) {
+ if (this.emojiarea && this.emojiarea === emojiarea) return;
+ this.emojiarea = emojiarea;
+ this.emojiarea.menu = this;
+
+ this.reposition();
+ this.$menu.show();
+ this.visible = true;
+ };
+
+ EmojiMenu.show = (function() {
+ var menu = null;
+ return function(emojiarea) {
+ menu = menu || new EmojiMenu();
+ menu.show(emojiarea);
+ };
+ })();
+
+})(jQuery, window, document);
\ No newline at end of file
diff --git a/public/jquery-emojiarea/jquery.emojiarea.min.js b/public/jquery-emojiarea/jquery.emojiarea.min.js
new file mode 100644
index 00000000..b8751466
--- /dev/null
+++ b/public/jquery-emojiarea/jquery.emojiarea.min.js
@@ -0,0 +1 @@
+(function(e,t,n){var r=1;var i=3;var s=["p","div","pre","form"];var o=27;var u=9;e.emojiarea={path:"",icons:{},defaults:{button:null,buttonLabel:"Emojis",buttonPosition:"after"}};e.fn.emojiarea=function(t){t=e.extend({},e.emojiarea.defaults,t);return this.each(function(){var r=e(this);if("contentEditable"in n.body&&t.wysiwyg!==false){new c(r,t)}else{new l(r,t)}})};var a={};a.restoreSelection=function(){if(t.getSelection){return function(e){var n=t.getSelection();n.removeAllRanges();for(var r=0,i=e.length;r
/g,">").replace(/"/g,""")};var f=function(){};f.prototype.setup=function(){var e=this;this.$editor.on("focus",function(){e.hasFocus=true});this.$editor.on("blur",function(){e.hasFocus=false});this.setupButton()};f.prototype.setupButton=function(){var t=this;var n;if(this.options.button){n=e(this.options.button)}else if(this.options.button!==false){n=e('
');n.html(this.options.buttonLabel);n.addClass("emoji-button");n.attr({title:this.options.buttonLabel});this.$editor[this.options.buttonPosition](n)}else{n=e("")}n.on("click",function(e){h.show(t);e.stopPropagation()});this.$button=n};f.createIcon=function(t,n){var r=e.emojiarea.icons[t]["icons"][n];var i=e.emojiarea.path||"";if(i.length&&i.charAt(i.length-1)!=="/"){i+="/"}return''};var l=function(e,t){this.options=t;this.$textarea=e;this.$editor=e;this.setup()};l.prototype.insert=function(t,n){if(!e.emojiarea.icons[t]["icons"].hasOwnProperty(n))return;a.insertAtCursor(n,this.$textarea[0]);this.$textarea.trigger("change")};l.prototype.val=function(){return this.$textarea.val()};a.extend(l.prototype,f.prototype);var c=function(t,r){var i=this;this.options=r;this.$textarea=t;this.$editor=e("").addClass("emoji-wysiwyg-editor");this.$editor.text(t.val());this.$editor.attr({contenteditable:"true"});this.$editor.on("blur keyup paste",function(){return i.onChange.apply(i,arguments)});this.$editor.on("mousedown focus",function(){n.execCommand("enableObjectResizing",false,false)});this.$editor.on("blur",function(){n.execCommand("enableObjectResizing",true,true)});var s=this.$editor.text();var o=e.emojiarea.icons;for(var u in o){for(var l in o[u]["icons"]){if(o[u]["icons"].hasOwnProperty(l)){s=s.replace(new RegExp(a.escapeRegex(l),"g"),f.createIcon(u,l))}}}this.$editor.html(s);t.hide().after(this.$editor);this.setup();this.$button.on("mousedown",function(){if(i.hasFocus){i.selection=a.saveSelection()}})};c.prototype.onChange=function(){this.$textarea.val(this.val()).trigger("change")};c.prototype.insert=function(t,n){var r;var i=e(f.createIcon(t,n));if(i[0].attachEvent){i[0].attachEvent("onresizestart",function(e){e.returnValue=false},false)}this.$editor.trigger("focus");if(this.selection){a.restoreSelection(this.selection)}try{a.replaceSelection(i[0])}catch(s){}this.onChange()};c.prototype.val=function(){var e=[];var t=[];var n=function(){e.push(t.join(""));t=[]};var o=function(e){if(e.nodeType===i){t.push(e.nodeValue)}else if(e.nodeType===r){var u=e.tagName.toLowerCase();var a=s.indexOf(u)!==-1;if(a&&t.length)n();if(u==="img"){var f=e.getAttribute("alt")||"";if(f)t.push(f);return}else if(u==="br"){n()}var l=e.childNodes;for(var c=0;c
");this.$menu.addClass("emoji-menu");this.$menu.hide();this.$items=e("").appendTo(this.$menu);i.append(this.$menu);i.on("keydown",function(e){if(e.keyCode===o||e.keyCode===u){r.hide()}});i.on("mouseup",function(){r.hide()});s.on("resize",function(){if(r.visible)r.reposition()});this.$menu.on("mouseup","a",function(e){e.stopPropagation();return false});this.$menu.on("click","a",function(n){var i=e(".label",e(this)).text();var s=e(".label",e(this)).parent().parent().attr("group");if(s&&i!==""){t.setTimeout(function(){r.onItemSelected.apply(r,[s,i])},0);n.stopPropagation();return false}});this.load()};h.prototype.onItemSelected=function(e,t){this.emojiarea.insert(e,t);this.hide()};h.prototype.load=function(){var t=[];var n=[];var r=e.emojiarea.icons;var i=e.emojiarea.path;if(i.length&&i.charAt(i.length-1)!=="/"){i+="/"}n.push('
");this.$items.html(t.join(""));this.$menu.prepend(n.join(""));this.$menu.find(".tab_switch").each(function(t){if(t!=0){var n=e(this).attr("href");e(n).hide()}else{e(this).addClass("active")}e(this).click(function(){e(this).addClass("active");e(this).siblings().removeClass("active");e(".select_group").hide();var t=e(this).attr("href");e(t).show()})})};h.prototype.reposition=function(){var e=this.emojiarea.$button;var t=e.offset();t.top+=e.outerHeight();t.left+=Math.round(e.outerWidth()/2);this.$menu.css({top:t.top,left:t.left})};h.prototype.hide=function(e){if(this.emojiarea){this.emojiarea.menu=null;this.emojiarea.$button.removeClass("on");this.emojiarea=null}this.visible=false;this.$menu.hide()};h.prototype.show=function(e){if(this.emojiarea&&this.emojiarea===e)return;this.emojiarea=e;this.emojiarea.menu=this;this.reposition();this.$menu.show();this.visible=true};h.show=function(){var e=null;return function(t){e=e||new h;e.show(t)}}()})(jQuery,window,document);
\ No newline at end of file
diff --git a/public/main.css b/public/main.css
index a8e7eaad..46ecd2ce 100644
--- a/public/main.css
+++ b/public/main.css
@@ -9,6 +9,25 @@ body
font-family: arial;
}
+@font-face {
+ font-family: 'EmojiFont';
+ src: url('https://github.com/Ranks/emojione/raw/master/assets/fonts/emojione-svg.woff2') format('woff2'),
+ url('https://github.com/Ranks/emojione/raw/master/assets/fonts/emojione-svg.woff') format('woff'), local("arial");
+}
+
+@supports (-ms-ime-align:auto) {
+.user_content
+{
+ font-family: EmojiFont, arial;
+}
+}
+@-moz-document url-prefix() {
+.user_content
+{
+ font-family: EmojiFont, arial;
+}
+}
+
ul
{
border: 1px solid #ccc;
diff --git a/routes.go b/routes.go
index bc6af420..a617bcab 100644
--- a/routes.go
+++ b/routes.go
@@ -54,11 +54,9 @@ func route_custom_page(w http.ResponseWriter, r *http.Request){
user := SessionCheck(w,r)
name := r.URL.Path[len("/pages/"):]
- val, ok := custom_pages[name];
- if ok {
- pi := Page{"Page","page",user,tList,val}
- templates.ExecuteTemplate(w,"custom_page.html", pi)
- } else {
+ pi := Page{"Page","page",user,tList,0}
+ err := custom_pages.ExecuteTemplate(w,name, pi)
+ if err != nil {
NotFound(w,r,user)
}
}
@@ -407,6 +405,10 @@ func route_profile(w http.ResponseWriter, r *http.Request){
puser.Is_Admin = puser.Is_Super_Admin || groups[puser.Group].Is_Admin
puser.Is_Super_Mod = puser.Is_Admin || groups[puser.Group].Is_Mod
puser.Is_Mod = puser.Is_Super_Mod
+ puser.Is_Banned = groups[puser.Group].Is_Banned
+ if puser.Is_Banned && puser.Is_Super_Mod {
+ puser.Is_Banned = false
+ }
}
if puser.Avatar != "" {
@@ -500,7 +502,7 @@ func route_create_topic(w http.ResponseWriter, r *http.Request) {
success := 1
topic_name := html.EscapeString(r.PostFormValue("topic-name"))
- res, err := create_topic_stmt.Exec(topic_name,html.EscapeString(r.PostFormValue("topic-content")),parse_message(html.EscapeString(r.PostFormValue("topic-content"))),user.ID)
+ res, err := create_topic_stmt.Exec(topic_name,html.EscapeString(preparse_message(r.PostFormValue("topic-content"))),parse_message(html.EscapeString(preparse_message(r.PostFormValue("topic-content")))),user.ID)
if err != nil {
log.Print(err)
success = 0
@@ -566,7 +568,9 @@ func route_create_reply(w http.ResponseWriter, r *http.Request) {
return
}
- _, err = create_reply_stmt.Exec(tid,html.EscapeString(r.PostFormValue("reply-content")),parse_message(html.EscapeString(r.PostFormValue("reply-content"))),user.ID)
+ content := preparse_message(html.EscapeString(r.PostFormValue("reply-content")))
+ log.Print(content)
+ _, err = create_reply_stmt.Exec(tid,content,parse_message(content),user.ID)
if err != nil {
log.Print(err)
success = 0
@@ -636,7 +640,7 @@ func route_profile_reply_create(w http.ResponseWriter, r *http.Request) {
return
}
- _, err = create_profile_reply_stmt.Exec(uid,html.EscapeString(r.PostFormValue("reply-content")),parse_message(html.EscapeString(r.PostFormValue("reply-content"))),user.ID)
+ _, err = create_profile_reply_stmt.Exec(uid,html.EscapeString(preparse_message(r.PostFormValue("reply-content"))),parse_message(html.EscapeString(preparse_message(r.PostFormValue("reply-content")))),user.ID)
if err != nil {
log.Print(err)
success = 0
diff --git a/templates/profile.html b/templates/profile.html
index 03c190df..a7a8e21c 100644
--- a/templates/profile.html
+++ b/templates/profile.html
@@ -4,7 +4,9 @@
{{.Something.Name}}
Add Friend
- {{if (.CurrentUser.Is_Super_Mod) and not (.Something.Is_Super_Mod) }}
Ban{{end}}
+ {{if (.CurrentUser.Is_Super_Mod) and not (.Something.Is_Super_Mod) }}
+ {{if .Something.Is_Banned }}
Unban{{else}}
Ban{{end}}
+ {{end}}
Report
@@ -14,7 +16,7 @@
{{range $index, $element := .ItemList}}
+
diff --git a/templates/topic.html b/templates/topic.html
index 764116ea..e2786637 100644
--- a/templates/topic.html
+++ b/templates/topic.html
@@ -1,7 +1,7 @@
{{template "header.html" . }}