diff --git a/application/config/autoload.php b/application/config/autoload.php
new file mode 100644
index 0000000..4750f7d
--- /dev/null
+++ b/application/config/autoload.php
@@ -0,0 +1,116 @@
+meta = "";
+ $this->head_js = "";
+ $this->foot_js = "";
+ $this->css = "";
+ $this->title = "";
+ $this->head_tags = "";
+ $this->body_class = "";
+ $this->body_id = "";
+ $this->base = "";
+ $this->CI =& get_instance();
+
+ //Define some constants for formatting
+ define('NL', "\n");
+ define('T1', "\t");
+ define('T2', T1.T1);
+ define('T3', T2.T1);
+ define('T4', T2.T2);
+ define('T5', T3.T2);
+ define('T6', T3.T3);
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets server headers and doctype
+ *
+ * Also sets page mime type, based on if sent as
+ * html or xhtml, and what the target browser
+ * supports
+ *
+ * @param bool $xhtml
+ * @param bool $html5
+ * @return Page
+ */
+ private function _headers($xhtml, $html5)
+ {
+ $this->CI->output->set_header("Cache-Control: must-revalidate, public");
+
+ $this->CI->output->set_header("Vary: Accept");
+ $mime = "";
+
+ //Variable for accept keyword
+ $accept = (!empty($_SERVER['HTTP_ACCEPT'])) ? $_SERVER['HTTP_ACCEPT'] : "";
+
+ //Predefine doctype
+ $doctype_string = ($html5 == TRUE) ? doctype('html5') : doctype('xhtml11');
+
+ //Predefine charset
+ $charset = "UTF-8";
+
+ //If xhtml flag is false, set html4 header
+ if($xhtml == TRUE)
+ {
+ //Check that the user agent accepts application/xhtml+xml, or if it's the W3C Validator
+ if(stristr($accept,"application/xhtml+xml") || stristr($_SERVER["HTTP_USER_AGENT"],"W3C_Validator"))
+ {
+ $mime = "application/xhtml+xml";
+ }
+ //Or if it supports application/xml
+ else if(stristr($accept,"application/xml"))
+ {
+ $mime = "application/xml";
+ }
+ //Or if it supports text/xml
+ else if(stristr($accept,"text/xml"))
+ {
+ $mime = "text/xml";
+ }
+ else //Otherwise, it's tag soup
+ {
+ $mime = "text/html";
+
+ if($html5 == FALSE) //If it's not HTML5, it's HTML4
+ {
+ $doctype_string = doctype('html4-strict');
+ }
+ }
+ }
+ else
+ {
+ $mime = "text/html";
+
+ if($html5 == FALSE)
+ {
+ $doctype_string = doctype('html4-strict');
+ }
+ }
+
+ // set the doctype according to the mime type which was determined
+ if($mime == "application/xhtml+xml" || $mime == "text/xml" || $mime == "application/xml")
+ {
+ if($html5 == TRUE)
+ {
+ $doctype_string = '';
+ }
+
+ $doctype_string = "\n" .
+ $doctype_string . "\n";
+ }
+ else
+ {
+ $doctype_string .= "\n";
+ }
+
+ // finally, output the mime type and prolog type
+ $this->CI->output->set_header("Content-Type: $mime;charset=$charset");
+ $this->CI->output->set_header("X-UA-Compatible: chrome=1, IE=edge");
+ $this->CI->output->set_output($doctype_string);
+
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Set Meta
+ *
+ * Sets meta tags, with codeigniter native meta tag helper
+ *
+ * @param array $meta
+ * @return Page
+ */
+ public function set_meta($meta)
+ {
+ $this->meta .= T1.meta($meta).NL;
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets minified javascript group in header
+ * @param string $group
+ * @param bool $debug
+ * @return Page
+ */
+ public function set_head_js_group($group, $debug=FALSE)
+ {
+ if($group === FALSE)
+ {
+ return $this;
+ }
+
+ $file = $this->CI->config->item('group_js_path') . $group;
+ $file .= ($debug == TRUE) ? "/debug/1" : "";
+ $this->head_js .= $this->script_tag($file, FALSE);
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Set an individual js file in header
+ * @param string $js
+ * @param bool $domain
+ * @return Page
+ */
+ public function set_head_js($js, $domain=TRUE)
+ {
+ $this->head_js .= $this->script_tag($js, $domain);
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets a minified css group
+ * @param string $group
+ * @return Page
+ */
+ public function set_css_group($group)
+ {
+ $link = array(
+ 'href' => $this->CI->config->item('group_style_path') . $group,
+ 'rel' => 'stylesheet',
+ 'type' => 'text/css',
+ );
+ $this->css .= T1.link_tag($link).NL;
+
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets a minified javascript group for the page footer
+ * @param string $group
+ * @return Page
+ */
+ public function set_foot_js_group($group, $debug=FALSE)
+ {
+ $file = $this->CI->config->item('group_js_path') . $group;
+ $file .= ($debug == TRUE) ? "?debug=1" : "";
+ $this->foot_js .= $this->script_tag($file, FALSE);
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets js in footer; multiple files are combined and minified.
+ * @param array $args
+ * @return Page
+ */
+ public function set_foot_js($js, $domain)
+ {
+ $this->foot_js .= $this->script_tag($js, $domain);
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets html title string
+ * @param string $title
+ * @return Page
+ */
+ public function set_title($title="")
+ {
+ $title = ($title == "") ?
+ $this->CI->config->item('default_title') : $title;
+
+ $this->title = $title;
+
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets custom body class
+ * @param string $class
+ * @return Page
+ */
+ public function set_body_class($class="")
+ {
+ $this->body_class = $class;
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets custom body id
+ * @param string $id
+ * @return Page
+ */
+ public function set_body_id($id="")
+ {
+ $this->body_id = $id;
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets custom base href
+ * @param string href
+ * @return Page
+ */
+ public function set_base($href)
+ {
+ $this->base = $href;
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets custom css tags
+ * @param string $name
+ * @param string $media
+ * @return Page
+ */
+ public function set_css_tag($name, $domain=TRUE, $media="all")
+ {
+ $path = $this->CI->config->item('content_domain');
+ $css_file = $path . "/css/" . $name . ".css";
+
+ if ($domain == FALSE)
+ $css_file = $name;
+
+ $this->css_tags .= T1.link_tag($name, "stylesheet", "text/css", "", $media).NL;
+
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets uncompressed js file in footer
+ * @param string $name
+ * @param bool $domain
+ * @return Page
+ */
+ public function set_foot_js_tag($name, $domain=TRUE)
+ {
+ $this->foot_js .= $this->script_tag($name, $domain);
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets a custom tag in the header
+ * @param string $tag
+ * @return Page
+ */
+ public function set_head_tag($tag)
+ {
+ $this->head_tags .= $tag . "\n";
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Sets custom page header
+ * @param mixed $xhtml
+ * @param bool $html5
+ * @param bool $fbml
+ * @return $this
+ */
+ public function build_header($xhtml = FALSE, $html5 = TRUE)
+ {
+ $data = array();
+
+ //Set Meta Tags
+ $this->meta = ($html5 == TRUE) ?
+ T1.''.NL. $this->meta :
+ T1.meta('content-type', 'text/html; charset=utf-8', 'equiv').NL.$this->meta;
+ $data['meta'] = $this->meta;
+
+ //Set CSS
+ if ($this->css != "")
+ {
+ $data['css'] = $this->css;
+ }
+ else
+ {
+ //Set default CSS group
+ $this->set_css_group($this->CI->config->item('default_css_group'));
+ $data['css'] = $this->css;
+ }
+
+ //Set head javascript
+ if($this->head_js != "")
+ {
+ $data['head_js'] = $this->head_js;
+ }
+ else
+ {
+ $this->set_head_js_group($this->CI->config->item('default_head_js_group'));
+ $data['head_js'] = $this->head_js;
+ }
+
+ //Set Page Title
+ $data['title'] = ($this->title != '') ? $this->title : $this->CI->config->item('default_title');
+
+ //Set Body Class
+ $data['body_class'] = $this->body_class;
+
+ //Set Body Id
+ $data['body_id'] = $this->body_id;
+
+ //Set Base HREF
+ $data['base'] = $this->base;
+
+ //Set individual head tags
+ $data['head_tags'] = $this->head_tags;
+
+ //Set Server Headers and Doctype
+ $this->_headers($xhtml, $html5);
+
+ //Output Header
+ $this->CI->load->view('header', $data);
+
+ flush();
+
+ return $this;
+ }
+
+ // --------------------------------------------------------------------------
+
+
+ /**
+ * Builds common footer with any additional js
+ */
+ public function build_footer()
+ {
+ $data = array();
+
+ $data['foot_js'] = ($this->foot_js != "") ?
+ $this->foot_js : '';
+
+ $this->CI->load->view('footer', $data);
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Script Tag
+ *
+ * Helper function for making script tags
+ *
+ * @param string $js
+ * @param bool $domain
+ * @return string
+ */
+ private function script_tag($js, $domain=TRUE)
+ {
+ $path = $this->CI->config->item('content_domain');
+ $js_file = $path . "/js/" . $js . ".js";
+
+ if ($domain == FALSE)
+ $js_file = $js;
+
+ $tag = T1.''.NL;
+
+ return $tag;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Quick Build
+ *
+ * A function to make building pages faster
+ * @param mixed $view
+ * @param mixed $data
+ * @param bool $xhtml
+ * @param bool $html5
+ */
+ public function quick_build($view, $data, $xhtml=TRUE, $html5=TRUE)
+ {
+ //Set up header
+ if ($title != '')
+ {
+ $this->set_title($title);
+ }
+ else
+ {
+ $this->set_title($this->CI->config->item('default_title'));
+ }
+
+ $this->build_header($xhtml, $html5);
+
+ //Load view(s)
+ if (is_array($view))
+ {
+ foreach ($view as $v)
+ {
+ $this->CI->load->view($v, $data);
+ }
+ }
+ else
+ {
+ $this->CI->load->view($view, $data);
+ }
+
+ //Create footer
+ $this->build_footer();
+ }
+
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Num Queries
+ *
+ * Returns number of queries run on a page
+ *
+ * @return int
+ */
+ public function num_queries()
+ {
+ return (isset($this->CI->db)) ? count($this->CI->db->queries) : 0;
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Set Message
+ *
+ * Adds a message to the page
+ * @param string $type
+ * @param string $message
+ * @return void
+ */
+ public function set_message($type, $message)
+ {
+ $data['stat_class'] = $type;
+ $data['message'] = $message;
+
+ $this->CI->load->view('message', $data);
+ }
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Redirect 303
+ *
+ * Shortcut function for 303 redirect
+ * @param string $url
+ */
+ function redirect_303($url)
+ {
+ $this->CI->output->set_header("HTTP/1.1 303 See Other");
+ $this->CI->output->set_header("Location:" . $url);
+ }
+}
\ No newline at end of file
diff --git a/application/views/footer.php b/application/views/footer.php
new file mode 100644
index 0000000..23b9ae9
--- /dev/null
+++ b/application/views/footer.php
@@ -0,0 +1,9 @@
+ $q_num = $this->page->num_queries() ?>
+
+
+
\ No newline at end of file
diff --git a/application/views/header.php b/application/views/header.php
new file mode 100644
index 0000000..3724fe5
--- /dev/null
+++ b/application/views/header.php
@@ -0,0 +1,11 @@
+
+
+= $meta ?>
+= $css ?>
+= $head_tags ?>
+= $title ?>
+ if(!empty($base)) { ?> } ?>
+= $head_tags ?>
+= $head_js ?>
+
+= (!empty($body_id)) ? " id=\"" . $body_id . "\"" : ""; ?>>
\ No newline at end of file
diff --git a/application/views/message.php b/application/views/message.php
new file mode 100644
index 0000000..fba8c99
--- /dev/null
+++ b/application/views/message.php
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/assets/css/message.css b/assets/css/message.css
new file mode 100644
index 0000000..0cb8f8d
--- /dev/null
+++ b/assets/css/message.css
@@ -0,0 +1,36 @@
+.message{
+ position:relative;
+ margin:0.5em auto;
+ padding:0.5em;
+ width:95%;
+}
+
+.message .close{
+ width:1em;
+ height:1em;
+ border:1px solid #000;
+ position:absolute;
+ right:0.5em;
+ top:0.5em;
+}
+
+.message .icon{
+ left:0.5em;
+ top:0.5em;
+ margin-right:1em;
+}
+
+.error{
+ border:1px solid #924949;
+ background: #f3e6e6;
+}
+
+.success{
+ border:1px solid #1f8454;
+ background: #70dda9;
+}
+
+.info{
+ border:1px solid #bfbe3a;
+ background: #d6d578;
+}
diff --git a/assets/js/kis-all.js b/assets/js/kis-all.js
new file mode 100644
index 0000000..905d650
--- /dev/null
+++ b/assets/js/kis-all.js
@@ -0,0 +1,926 @@
+/**
+ Kis JS Keep It Simple JS Library
+ Copyright Timothy J. Warren
+ License Public Domain
+ Version 0.2.0
+ */
+(function (){
+
+ "use strict";
+
+ //Browser requirements check
+ if (!document.querySelectorAll)
+ {
+ return;
+ }
+
+ var $_, $, dcopy, sel;
+
+ /**
+ * $
+ *
+ * Simple DOM selector function
+ */
+ $ = function (a)
+ {
+ var x;
+ if (typeof a !== "string" || typeof a === "undefined"){ return a;}
+
+ //Pick the quickest method for each kind of selector
+ if(a.match(/^#([\w\-]+$)/))
+ {
+ return document.getElementById(a.split('#')[1]);
+ }
+ else if(a.match(/^([\w\-]+)$/))
+ {
+ x = document.getElementsByTagName(a);
+ }
+ else
+ {
+ x = document.querySelectorAll(a);
+ }
+
+ //Return the single object if applicable
+ return (x.length === 1) ? x[0] : x;
+ };
+
+ /**
+ * $_
+ *
+ * Constructor function
+ */
+ $_ = function(s)
+ {
+ //Have documentElement be default selector, just in case
+ if(typeof s == "undefined")
+ {
+ sel = (typeof $_.el !== "undefined")
+ ? $_.el
+ : document.documentElement;
+ }
+ else
+ {
+ sel = $(s);
+ }
+
+ // Make a copy before adding properties
+ var self = dcopy($_);
+
+ // Give sel to each extension.
+ for(var i in self)
+ {
+ if(typeof self[i] === "object")
+ {
+ self[i].el = sel;
+ }
+ }
+
+ self.el = sel;
+
+ return self;
+ };
+
+ /**
+ * Deep copy/prototypical constructor function
+ */
+ dcopy = function(obj)
+ {
+ var type, F;
+
+ if(typeof obj === "undefined")
+ {
+ return;
+ }
+
+ if(typeof Object.create !== "undefined")
+ {
+ return Object.create(obj);
+ }
+
+ type = typeof obj;
+
+ if(type !== "object" && type !== "function")
+ {
+ return;
+ }
+
+ F = function(){};
+
+ F.prototype = obj;
+
+ return new F();
+
+ };
+
+ //Function to add to $_ object, and get sel
+ $_.ext = function(name, obj)
+ {
+ $_[name] = obj;
+ obj.el = sel;
+ };
+
+ //Selector iteration
+ $_.ext('each', function (callback)
+ {
+ if(typeof sel.length !== "undefined" && sel !== window)
+ {
+ var len = sel.length;
+
+ if (len === 0)
+ {
+ return;
+ }
+
+ var selx;
+ for (var x = 0; x < len; x++)
+ {
+ selx = (sel.item(x)) ? sel.item(x) : sel[x];
+ callback(selx);
+ }
+ }
+ else
+ {
+ callback(sel);
+ }
+ });
+
+ //Set global variables
+ $_ = window.$_ = window.$_ || $_;
+ $_.$ = $;
+
+ //console.log polyfill
+ if(typeof window.console === "undefined")
+ {
+ window.console = {
+ log:function(){}
+ };
+ }
+
+ /**
+ * String trim function polyfill
+ */
+ if(typeof String.prototype.trim === "undefined")
+ {
+ String.prototype.trim = function(){
+ return this.replace(/^\s+|\s+$/g, "");
+ };
+ }
+
+}());
+
+// --------------------------------------------------------------------------
+
+//Function to maintain module scope
+(function(){
+
+ "use strict";
+
+ //Fix $_ is not defined errors
+ var $_ = $_ || window.$_;
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Dom manipulation object
+ *
+ */
+ (function (){
+ var d;
+
+ /*
+ * classList.js: Cross-browser full element.classList implementation.
+ * 2011-06-15
+ *
+ * By Eli Grey, http://eligrey.com
+ * Public Domain.
+ * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+ */
+
+ if (typeof document !== "undefined" && !("classList" in document.createElement("a")))
+ {
+ (function (view){
+
+ var classListProp = "classList",
+ protoProp = "prototype",
+ elemCtrProto = (view.HTMLElement || view.Element)[protoProp],
+ objCtr = Object,
+ strTrim = String[protoProp].trim ||
+ function ()
+ {
+ return this.replace(/^\s+|\s+$/g, "");
+ },
+ arrIndexOf = Array[protoProp].indexOf ||
+ function (item)
+ {
+ var
+ i = 0,
+ len = this.length;
+ for (; i < len; i++)
+ {
+ if (i in this && this[i] === item)
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+ // Vendors: please allow content code to instantiate DOMExceptions
+ ,
+ DOMEx = function (type, message)
+ {
+ this.name = type;
+ this.code = DOMException[type];
+ this.message = message;
+ },
+ checkTokenAndGetIndex = function (classList, token)
+ {
+ if (token === "")
+ {
+ throw new DOMEx("SYNTAX_ERR", "An invalid or illegal string was specified");
+ }
+ if (/\s/.test(token))
+ {
+ throw new DOMEx("INVALID_CHARACTER_ERR", "String contains an invalid character");
+ }
+ return arrIndexOf.call(classList, token);
+ },
+ ClassList = function (elem)
+ {
+ var
+ trimmedClasses = strTrim.call(elem.className),
+ classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [],
+ i = 0,
+ len = classes.length;
+ for (; i < len; i++)
+ {
+ this.push(classes[i]);
+ }
+ this._updateClassName = function ()
+ {
+ elem.className = this.toString();
+ };
+ },
+ classListProto = ClassList[protoProp] = [],
+ classListGetter = function ()
+ {
+ return new ClassList(this);
+ };
+ // Most DOMException implementations don't allow calling DOMException's toString()
+ // on non-DOMExceptions. Error's toString() is sufficient here.
+ DOMEx[protoProp] = Error[protoProp];
+ classListProto.item = function (i)
+ {
+ return this[i] || null;
+ };
+ classListProto.contains = function (token)
+ {
+ token += "";
+ return checkTokenAndGetIndex(this, token) !== -1;
+ };
+ classListProto.add = function (token)
+ {
+ token += "";
+ if (checkTokenAndGetIndex(this, token) === -1)
+ {
+ this.push(token);
+ this._updateClassName();
+ }
+ };
+ classListProto.remove = function (token)
+ {
+ token += "";
+ var index = checkTokenAndGetIndex(this, token);
+ if (index !== -1)
+ {
+ this.splice(index, 1);
+ this._updateClassName();
+ }
+ };
+ classListProto.toggle = function (token)
+ {
+ token += "";
+ if (checkTokenAndGetIndex(this, token) === -1)
+ {
+ this.add(token);
+ }
+ else
+ {
+ this.remove(token);
+ }
+ };
+ classListProto.toString = function ()
+ {
+ return this.join(" ");
+ };
+
+ if (objCtr.defineProperty)
+ {
+ var classListPropDesc = {
+ get: classListGetter,
+ enumerable: true,
+ configurable: true
+ };
+ try
+ {
+ objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
+ }
+ catch (ex)
+ { // IE 8 doesn't support enumerable:true
+ if (ex.number === -0x7FF5EC54)
+ {
+ classListPropDesc.enumerable = false;
+ objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
+ }
+ }
+ }
+ else if (objCtr[protoProp].__defineGetter__)
+ {
+ elemCtrProto.__defineGetter__(classListProp, classListGetter);
+ }
+
+ }(self));
+ }
+
+ // --------------------------------------------------------------------------
+
+ //Private function for getting/setting attributes
+ function _attr(sel, name, value)
+ {
+ var oldVal, doAttr;
+
+ //Get the value of the attribute, if it exists
+ if (typeof sel.hasAttribute !== "undefined")
+ {
+ if (sel.hasAttribute(name))
+ {
+ oldVal = sel.getAttribute(name);
+ }
+
+ doAttr = true;
+ }
+ else if (typeof sel[name] !== "undefined")
+ {
+ oldVal = sel[name];
+ doAttr = false;
+ }
+ else if (name === "class" && typeof sel.className !== "undefined") //className attribute
+ {
+ name = "className";
+ oldVal = sel.className;
+ doAttr = false;
+ }
+
+ //Well, I guess that attribute doesn't exist
+ if (typeof oldVal === "undefined" && (typeof value === "undefined" || value === null))
+ {
+ console.log(value);
+ console.log(sel);
+ console.log("Element does not have the selected attribute");
+ return;
+ }
+
+ //No value to set? Return the current value
+ if (typeof value === "undefined")
+ {
+ return oldVal;
+ }
+
+ //Determine what to do with the attribute
+ if (typeof value !== "undefined" && value !== null)
+ {
+ if(doAttr === true)
+ {
+ sel.setAttribute(name, value);
+ }
+ else
+ {
+ sel[name] = value;
+ }
+ }
+ else if (value === null)
+ {
+ if(doAttr === true)
+ {
+ sel.removeAttribute(name);
+ }
+ else
+ {
+ delete sel[name];
+ }
+ }
+
+ return (typeof value !== "undefined") ? value : oldVal;
+ }
+
+ function _toCamel(s)
+ {
+ return s.replace(/(\-[a-z])/g, function($1){
+ return $1.toUpperCase().replace('-','');
+ });
+ }
+
+ function _css(sel, prop, val)
+ {
+ var equi;
+
+ //Camel-case
+ prop = _toCamel(prop);
+
+ //Equivalent properties for 'special' browsers
+ equi = {
+ outerHeight: "offsetHeight",
+ outerWidth: "offsetWidth",
+ top: "posTop"
+ }
+
+
+ //If you don't define a value, try returning the existing value
+ if(typeof val === "undefined" && sel.style[prop] !== "undefined")
+ {
+ return sel.style[prop];
+ }
+ else if(typeof val === "undefined" && sel.style[equi[prop]] !== "undefined")
+ {
+ return sel.style[equi[prop]];
+ }
+
+ //Let's try the easy way first
+ if(typeof sel.style[prop] !== "undefined")
+ {
+ sel.style[prop] = val;
+
+ //Short circuit
+ return;
+ }
+ else if(sel.style[equi[prop]])
+ {
+ sel.style[equi[prop]] = val;
+ return;
+ }
+
+ //No matches? Well, lets log it for now
+ console.log("Property " + prop + " nor an equivalent seems to exist");
+ }
+
+ // --------------------------------------------------------------------------
+
+ d = {
+ addClass: function (c)
+ {
+ $_.each(function (e){
+ e.classList.add(c);
+ });
+ },
+ removeClass: function (c)
+ {
+ $_.each(function (e){
+ e.classList.remove(c);
+ });
+ },
+ hide: function ()
+ {
+ this.css('display', 'none');
+ },
+ show: function (type)
+ {
+ if (typeof type === "undefined")
+ {
+ type = "block";
+ }
+
+ this.css("display", type);
+ },
+ attr: function (name, value)
+ {
+ var sel = this.el;
+
+ //Make sure you don't try to get a bunch of elements
+ if (sel.length > 1 && typeof value === "undefined")
+ {
+ console.log(sel);
+ console.log("Must be a singular element");
+ return;
+ }
+ else if (sel.length > 1 && typeof value !== "undefined") //You can set a bunch, though
+ {
+ $_.each(function (e){
+ return _attr(e, name, value);
+ });
+ }
+ else //Normal behavior
+ {
+ return _attr(sel, name, value);
+ }
+ },
+ text: function (value)
+ {
+ var oldValue, set, type, sel;
+
+ sel = this.el;
+
+ set = (typeof value !== "undefined") ? true : false;
+
+ type = (typeof sel.innerText !== "undefined")
+ ? "innerText"
+ : (typeof sel.textContent !== "undefined")
+ ? "textContent"
+ : "innerHTML";
+
+ oldValue = sel[type];
+
+ if(set)
+ {
+ sel[type] = value;
+ return value;
+ }
+ else
+ {
+ return oldValue;
+ }
+ },
+ css: function (prop, val)
+ {
+ if(typeof val === "undefined")
+ {
+ return _css(this.el, prop);
+ }
+
+ $_.each(function (e){
+ _css(e, prop, val);
+ });
+ }
+ };
+
+ $_.ext('dom', d);
+
+ }());
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Store object
+ *
+ * Wrapper for localstorage data serialization
+ */
+ (function (){
+ var store = {
+ get: function (key)
+ {
+ return JSON.parse(localStorage.getItem(key));
+ },
+ set: function (key, value)
+ {
+ if (typeof value !== "string")
+ {
+ value = JSON.stringify(value);
+ }
+ localStorage.setItem(key, value);
+ },
+ getAll: function ()
+ {
+ var i,
+ len,
+ data;
+ len = localStorage.length;
+ data = {};
+
+ for (i = 0; i < len; i++)
+ {
+ var name = localStorage.key(i);
+ var value = localStorage.getItem(name);
+ data[name] = value;
+ }
+
+ return data;
+ }
+ };
+
+ $_.ext('store', store);
+ }());
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Qs
+ *
+ * Object for encoding and decoding querystrings and hashbang strings
+ */
+ (function (){
+
+ $_.hb = (history.pushState) ? false : true;
+
+ var qs = {
+ parse: function (hb)
+ {
+ hb = hb || $_.hb;
+
+ var h, i, hString, pairs, pLen, data, y;
+
+ data = {};
+
+ if (hb === true)
+ {
+ h = location.hash.split('#!/');
+ hString = (h.length > 1) ? h[1] : '';
+ }
+ else if (hb === false || hb === undefined)
+ {
+ hString = window.location.search.substring(1);
+ }
+ else
+ {
+ return false;
+ }
+
+ pairs = hString.split('&');
+
+ pLen = pairs.length;
+
+ for (i = 0; i < pLen; i++)
+ {
+ y = pairs[i].split('=');
+
+ if (y.length < 2)
+ {
+ return data;
+ }
+
+ data[y[0]] = y[1];
+ }
+
+ return data;
+ },
+ set: function (key, value, hb)
+ {
+ hb = hb || $_.hb;
+ var pairs = this.parse(hb);
+
+ if (key !== undefined && value !== undefined)
+ {
+ pairs[key] = value;
+ }
+
+ var vars = [];
+
+ for (var x in pairs)
+ {
+ if (pairs.hasOwnProperty(x))
+ {
+ vars.push(x + '=' + pairs[x]);
+ }
+ }
+
+ var qs = vars.join('&');
+
+ if (hb === true)
+ {
+ qs = '!/' + qs;
+ location.hash = qs;
+ }
+
+ return qs;
+ },
+ get: function (key, hb)
+ {
+ hb = hb || $_.hb;
+ var pairs = this.parse(hb);
+ return (pairs[key]) ? pairs[key] : '';
+ }
+ };
+
+ $_.ext('qs', qs);
+
+ }());
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Ajax
+ *
+ * Object for making ajax requests
+ */
+ (function (){
+
+ var ajax = {
+ _do: function (url, data, callback, isPost)
+ {
+ if (typeof callback === "undefined")
+ {
+ callback = function (){};
+ }
+
+ var request = (typeof window.XMLHttpRequest !== "undefined")
+ ? new XMLHttpRequest()
+ : false;
+
+ var type = (isPost) ? "POST" : "GET";
+
+ url += (type === "GET") ? "?"+this._serialize(data, true) : '';
+
+ request.open(type, url);
+
+ request.onreadystatechange = function ()
+ {
+ if (request.readyState === 4)
+ {
+ callback(request.responseText);
+ }
+ };
+
+ if (type === "POST")
+ {
+ request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ request.send(this._serialize(data));
+ }
+ else
+ {
+ request.send(null);
+ }
+ },
+ _serialize: function (data, encode)
+ {
+ var pairs = [];
+
+ for (var name in data)
+ {
+ if (!data.hasOwnProperty(name))
+ {
+ continue;
+ }
+ if (typeof data[name] === "function")
+ {
+ continue;
+ }
+
+ var value = data[name].toString();
+
+ if (encode === true)
+ {
+ name = encodeURIComponent(name.replace(" ", "+"));
+ value = encodeURIComponent(value.replace(" ", "+"));
+ }
+
+ pairs.push(name + "=" + value);
+ }
+
+ return pairs.join("&");
+ }
+ };
+
+ $_.ext('get', function (url, data, callback){
+ ajax._do(url, data, callback, false);
+ });
+
+ $_.ext('post', function (url, data, callback){
+ ajax._do(url, data, callback, true);
+ });
+ }());
+
+ // --------------------------------------------------------------------------
+
+ /**
+ * Event object
+ *
+ * Event api wrapper
+ */
+ (function (){
+
+ // Property name for expandos on DOM objects
+ var kis_expando = "KIS_0_2_0";
+
+ var attach, remove, add_remove, e;
+
+ // Define the proper attach and remove functions
+ // based on browser support
+ if(typeof document.addEventListener !== "undefined")
+ {
+ attach = function (sel, event, callback)
+ {
+ if (typeof sel.addEventListener !== "undefined")
+ {
+ sel.addEventListener(event, callback, false);
+ }
+ };
+ remove = function (sel, event, callback)
+ {
+ if (typeof sel.removeEventListener !== "undefined")
+ {
+ sel.removeEventListener(event, callback, false);
+ }
+ };
+ }
+ //typeof function doesn't work in IE where attachEvent is available: brute force it
+ else if(typeof document.attachEvent !== "undefined")
+ {
+ attach = function (sel, event, callback)
+ {
+ function listener () {
+ // Internet Explorer fails to correctly set the 'this' object
+ // for event listeners, so we need to set it ourselves.
+ callback.apply(arguments);
+ }
+
+ if (typeof sel.attachEvent !== "undefined")
+ {
+ remove(event, callback); // Make sure we don't have duplicate listeners
+
+ sel.attachEvent("on" + event, listener);
+ // Store our listener so we can remove it later
+ var expando = sel[kis_expando] = sel[kis_expando] || {};
+ expando.listeners = expando.listeners || {};
+ expando.listeners[event] = expando.listeners[event] || [];
+ expando.listeners[event].push({
+ callback: callback,
+ listener: listener
+ });
+ }
+ else
+ {
+ console.log("Failed to attach event:"+event+" on "+sel);
+ }
+ };
+ remove = function (sel, event, callback)
+ {
+ if(typeof sel.detachEvent !== "undefined")
+ {
+ var expando = sel[kis_expando];
+ if (expando && expando.listeners
+ && expando.listeners[event])
+ {
+ var listeners = expando.listeners[event];
+ var len = listeners.length;
+ for (var i=0; i array(
+ 'path/to/css/file1.css',
+ 'path/to/css/file2.css'
+ ),
+ */
+
+ 'css' => array(
+ 'assets/css/message.css',
+ ),
+);
\ No newline at end of file
diff --git a/config/js_groups.php b/config/js_groups.php
new file mode 100644
index 0000000..5fc4020
--- /dev/null
+++ b/config/js_groups.php
@@ -0,0 +1,16 @@
+ array(
+ 'path/to/css/file1.css',
+ 'path/to/css/file2.css'
+ ),
+ */
+
+ 'js' => array(
+ 'assets/js/kis-all.js',
+ 'assets/js/message.js',
+ )
+ );
\ No newline at end of file
diff --git a/config/jshrink.php b/config/jshrink.php
new file mode 100644
index 0000000..c9afe4f
--- /dev/null
+++ b/config/jshrink.php
@@ -0,0 +1,333 @@
+ nor the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+/**
+ * JShrink
+ *
+ * Usage - JShrink::minify($js);
+ * Usage - JShrink::minify($js, $options);
+ * Usage - JShrink::minify($js, array('flaggedComments' => false));
+ *
+ * @version 0.2
+ * @package JShrink
+ * @author Robert Hafner
+ * @license http://www.opensource.org/licenses/bsd-license.php
+ */
+class JShrink
+{
+ protected $input;
+ protected $index = 0;
+
+ protected $a = '';
+ protected $b = '';
+ protected $c;
+
+ protected $options;
+
+ static protected $defaultOptions = array('flaggedComments' => true);
+
+ static public function minify($js, $options = array())
+ {
+ try{
+ $currentOptions = array_merge(self::$defaultOptions, $options);
+
+ ob_start();
+ $currentOptions = array_merge(self::$defaultOptions, $options);
+ $me = new JShrink();
+ $me->breakdownScript($js, $currentOptions);
+ $output = ob_get_clean();
+ return $output;
+
+ }catch(Exception $e){
+ ob_end_clean();
+ throw $e;
+ }
+ }
+
+ protected function breakdownScript($js, $currentOptions)
+ {
+ $this->options = $currentOptions;
+
+ $js = str_replace("\r\n", "\n", $js);
+ $this->input = str_replace("\r", "\n", $js);
+
+ $this->a = $this->getReal();
+
+ // the only time the length can be higher than 1 is if a conditional comment needs to be displayed
+ // and the only time that can happen for $a is on the very first run
+ while(strlen($this->a) > 1)
+ {
+ echo $this->a;
+ $this->a = $this->getReal();
+ }
+
+ $this->b = $this->getReal();
+
+ while($this->a !== false && !is_null($this->a) && $this->a !== '')
+ {
+
+ // now we give $b the same check for conditional comments we gave $a before we began looping
+ if(strlen($this->b) > 1)
+ {
+ echo $this->a . $this->b;
+ $this->a = $this->getReal();
+ $this->b = $this->getReal();
+ continue;
+ }
+
+ switch($this->a)
+ {
+ // new lines
+ case "\n":
+ // if the next line is something that can't stand alone preserver the newline
+ if(strpos('(-+{[@', $this->b) !== false)
+ {
+ echo $this->a;
+ $this->saveString();
+ break;
+ }
+
+ // if its a space we move down to the string test below
+ if($this->b === ' ')
+ break;
+
+ // otherwise we treat the newline like a space
+
+ case ' ':
+ if(self::isAlphaNumeric($this->b))
+ echo $this->a;
+
+ $this->saveString();
+ break;
+
+ default:
+ switch($this->b)
+ {
+ case "\n":
+ if(strpos('}])+-"\'', $this->a) !== false)
+ {
+ echo $this->a;
+ $this->saveString();
+ break;
+ }else{
+ if(self::isAlphaNumeric($this->a))
+ {
+ echo $this->a;
+ $this->saveString();
+ }
+ }
+ break;
+
+ case ' ':
+ if(!self::isAlphaNumeric($this->a))
+ break;
+
+ default:
+ // check for some regex that breaks stuff
+ if($this->a == '/' && ($this->b == '\'' || $this->b == '"'))
+ {
+ $this->saveRegex();
+ continue;
+ }
+
+ echo $this->a;
+ $this->saveString();
+ break;
+ }
+ }
+
+ // do reg check of doom
+ $this->b = $this->getReal();
+
+ if(($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false))
+ $this->saveRegex();
+ }
+ }
+
+ protected function getChar()
+ {
+ if(isset($this->c))
+ {
+ $char = $this->c;
+ unset($this->c);
+ }else{
+ if(isset($this->input[$this->index]))
+ {
+ $char = $this->input[$this->index];
+ $this->index++;
+ }else{
+ return false;
+ }
+ }
+
+ if($char === "\n" || ord($char) >= 32)
+ return $char;
+
+ return ' ';
+ }
+
+ protected function getReal()
+ {
+ $startIndex = $this->index;
+ $char = $this->getChar();
+
+ if($char == '/')
+ {
+ $this->c = $this->getChar();
+
+ if($this->c == '/')
+ {
+ $thirdCommentString = $this->input[$this->index];
+
+ // kill rest of line
+ $char = $this->getNext("\n");
+
+ if($thirdCommentString == '@')
+ {
+ $endPoint = ($this->index) - $startIndex;
+ unset($this->c);
+ $char = "\n" . substr($this->input, $startIndex, $endPoint);// . "\n";
+ }else{
+ $char = $this->getChar();
+ $char = $this->getChar();
+ }
+
+ }elseif($this->c == '*'){
+
+ $this->getChar(); // current C
+ $thirdCommentString = $this->getChar();
+
+ if($thirdCommentString == '@')
+ {
+ // we're gonna back up a bit and and send the comment back, where the first
+ // char will be echoed and the rest will be treated like a string
+ $this->index = $this->index-2;
+ return '/';
+
+ }elseif($this->getNext('*/')){
+ // kill everything up to the next */
+
+ $this->getChar(); // get *
+ $this->getChar(); // get /
+
+ $char = $this->getChar(); // get next real charactor
+
+ // if YUI-style comments are enabled we reinsert it into the stream
+ if($this->options['flaggedComments'] && $thirdCommentString == '!')
+ {
+ $endPoint = ($this->index - 1) - $startIndex;
+ echo "\n" . substr($this->input, $startIndex, $endPoint) . "\n";
+ }
+
+ }else{
+ $char = false;
+ }
+
+ if($char === false)
+ throw new JShrinkException('Stray comment. ' . $this->index);
+
+ // if we're here c is part of the comment and therefore tossed
+ if(isset($this->c))
+ unset($this->c);
+ }
+ }
+ return $char;
+ }
+
+ protected function getNext($string)
+ {
+ $pos = strpos($this->input, $string, $this->index);
+
+ if($pos === false)
+ return false;
+
+ $this->index = $pos ;
+ return $this->input[$this->index];
+ }
+
+ protected function saveString()
+ {
+ $this->a = $this->b;
+ if($this->a == '\'' || $this->a == '"')
+ {
+ // save literal string
+ $stringType = $this->a;
+
+ while(1)
+ {
+ echo $this->a;
+ $this->a = $this->getChar();
+
+ switch($this->a)
+ {
+ case $stringType:
+ break 2;
+
+ case "\n":
+ throw new JShrinkException('Unclosed string. ' . $this->index);
+ break;
+
+ case '\\':
+ echo $this->a;
+ $this->a = $this->getChar();
+ }
+ }
+ }
+ }
+
+ protected function saveRegex()
+ {
+ echo $this->a . $this->b;
+
+ while(($this->a = $this->getChar()) !== false)
+ {
+ if($this->a == '/')
+ break;
+
+ if($this->a == '\\')
+ {
+ echo $this->a;
+ $this->a = $this->getChar();
+ }
+
+ if($this->a == "\n")
+ throw new JShrinkException('Stray regex pattern. ' . $this->index);
+
+ echo $this->a;
+ }
+ $this->b = $this->getReal();
+ }
+
+ static protected function isAlphaNumeric($char)
+ {
+ return preg_match('/^[\w\$]$/', $char) === 1 || $char == '/';
+ }
+
+}
+
+// Adding a custom exception handler for your own projects just means changing this line
+class JShrinkException extends Exception {}
+?>
\ No newline at end of file
diff --git a/css.php b/css.php
new file mode 100644
index 0000000..51fd478
--- /dev/null
+++ b/css.php
@@ -0,0 +1,82 @@
+ false));
+}
+
+$requested_time=(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
+ ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])
+ : time();
+
+if($last_modified === $requested_time)
+{
+ header("HTTP/1.1 304 Not Modified");
+ exit();
+}
+
+header("Content-Type: application/x-javascript; charset=utf8");
+header("Cache-control: public, max-age=691200, must-revalidate");
+header("Last-Modified: ".gmdate('D, d M Y H:i:s', $last_modified)." GMT");
+header("Expires: ".gmdate('D, d M Y H:i:s', (filemtime($base_path.'js.php') + 691200))." GMT");
+
+echo $js;
+
+ob_end_flush();
+//end of js.php