diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index 92a3816..0000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,97 +0,0 @@
-module.exports = function(grunt) {
-
- "use strict";
-
- // Initializes the Grunt tasks with the following settings
- grunt.initConfig({
-
- // A list of files which will be syntax-checked by JSHint.
- jshint: {
- files: ['src/js/shims.js', 'src/js/firechat.js', 'src/js/firechat-ui.js'],
- options: {
- regexdash: false
- }
- },
-
- // Precompile templates and strip whitespace with 'processContent'.
- jst: {
- compile: {
- options: {
- path: 'templates',
- namespace: 'FirechatDefaultTemplates',
- prettify: true,
- processContent: function(src) {
- return src.replace(/(^\s+|\s+$)/gm, '');
- }
- },
- files: {
- 'compiled/templates.js': ['templates/*.html']
- }
- }
- },
-
- // Compile and minify LESS CSS for production.
- less: {
- development: {
- files: {
- "build/firechat-default.css": "src/less/styles.less"
- }
- },
- production: {
- options: {
- yuicompress: true
- },
- files: {
- "build/firechat-default.min.css": "src/less/styles.less"
- }
- }
- },
-
- // Concatenate files in a specific order.
- concat: {
- js: {
- src: [
- 'src/js/libs/underscore-1.4.4.min.js',
- 'compiled/templates.js',
- 'src/js/shims.js',
- 'src/js/firechat.js',
- 'src/js/firechat-ui.js'
- ],
- dest: 'build/firechat-default.js'
- }
- },
-
- // Minify concatenated files.
- uglify: {
- dist: {
- src: ['<%= concat.js.dest %>'],
- dest: 'build/firechat-default.min.js'
- }
- },
-
- // Clean up temporary files.
- clean: ['compiled/'],
-
- // Tasks to execute upon file change when using `grunt watch`.
- watch: {
- src: {
- files: ['src/**/*.*', 'templates/**/*.*'],
- tasks: ['default']
- }
- }
- });
-
- // Load specific plugins, which have been installed and specified in package.json.
- grunt.loadNpmTasks('grunt-contrib-clean');
- grunt.loadNpmTasks('grunt-contrib-concat');
- grunt.loadNpmTasks('grunt-contrib-jshint');
- grunt.loadNpmTasks('grunt-contrib-jst');
- grunt.loadNpmTasks('grunt-contrib-less');
- grunt.loadNpmTasks('grunt-contrib-uglify');
- grunt.loadNpmTasks('grunt-contrib-watch');
- grunt.loadNpmTasks('grunt-docco');
-
- // Default task operations if simply calling `grunt` without options.
- grunt.registerTask('default', ['jshint', 'jst', 'less', 'concat', 'uglify', 'clean']);
-
-};
diff --git a/README.md b/README.md
index 94bd835..4e836fd 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-FireChat.js
+Firechat.js
===========
-Simple, extensible chat build on [Firebase](https://firebase.com).
+Simple, extensible chat built on [Firebase](https://firebase.com).
-For demos, documentation, and integration instructions, see [http://firebase.github.io/firechat/](http://firebase.github.io/firechat/).
\ No newline at end of file
+For demos, documentation, and integration instructions, see [http://firebase.github.io/firechat/](http://firebase.github.io/firechat).
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..b30ad58
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1,4 @@
+safe: true
+lsi: false
+pygments: true
+markdown: rdiscount
diff --git a/_layouts/docs.html b/_layouts/docs.html
new file mode 100644
index 0000000..8c8a862
--- /dev/null
+++ b/_layouts/docs.html
@@ -0,0 +1,96 @@
+
+
+
+
+
+ Firechat - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ content }}
+ Build something cool using Firechat? We'd
+ love to
hear from you !
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/img/icons.png b/assets/img/icons.png
deleted file mode 100644
index 79bec78..0000000
Binary files a/assets/img/icons.png and /dev/null differ
diff --git a/css/pygments-borland.css b/css/pygments-borland.css
new file mode 100644
index 0000000..b4999e7
--- /dev/null
+++ b/css/pygments-borland.css
@@ -0,0 +1,46 @@
+.hll { background-color: #ffffcc }
+.c { color: #008800; font-style: italic } /* Comment */
+.err { color: #a61717; background-color: #e3d2d2 } /* Error */
+.k { color: #000080; font-weight: bold } /* Keyword */
+.cm { color: #008800; font-style: italic } /* Comment.Multiline */
+.cp { color: #008080 } /* Comment.Preproc */
+.c1 { color: #008800; font-style: italic } /* Comment.Single */
+.cs { color: #008800; font-weight: bold } /* Comment.Special */
+.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
+.ge { font-style: italic } /* Generic.Emph */
+.gr { color: #aa0000 } /* Generic.Error */
+.gh { color: #999999 } /* Generic.Heading */
+.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
+.go { color: #888888 } /* Generic.Output */
+.gp { color: #555555 } /* Generic.Prompt */
+.gs { font-weight: bold } /* Generic.Strong */
+.gu { color: #aaaaaa } /* Generic.Subheading */
+.gt { color: #aa0000 } /* Generic.Traceback */
+.kc { color: #000080; font-weight: bold } /* Keyword.Constant */
+.kd { color: #000080; font-weight: bold } /* Keyword.Declaration */
+.kn { color: #000080; font-weight: bold } /* Keyword.Namespace */
+.kp { color: #000080; font-weight: bold } /* Keyword.Pseudo */
+.kr { color: #000080; font-weight: bold } /* Keyword.Reserved */
+.kt { color: #000080; font-weight: bold } /* Keyword.Type */
+.m { color: #0000FF } /* Literal.Number */
+.s { color: #0000FF } /* Literal.String */
+.na { color: #FF0000 } /* Name.Attribute */
+.nt { color: #000080; font-weight: bold } /* Name.Tag */
+.ow { font-weight: bold } /* Operator.Word */
+.w { color: #bbbbbb } /* Text.Whitespace */
+.mf { color: #0000FF } /* Literal.Number.Float */
+.mh { color: #0000FF } /* Literal.Number.Hex */
+.mi { color: #0000FF } /* Literal.Number.Integer */
+.mo { color: #0000FF } /* Literal.Number.Oct */
+.sb { color: #0000FF } /* Literal.String.Backtick */
+.sc { color: #800080 } /* Literal.String.Char */
+.sd { color: #0000FF } /* Literal.String.Doc */
+.s2 { color: #0000FF } /* Literal.String.Double */
+.se { color: #0000FF } /* Literal.String.Escape */
+.sh { color: #0000FF } /* Literal.String.Heredoc */
+.si { color: #0000FF } /* Literal.String.Interpol */
+.sx { color: #0000FF } /* Literal.String.Other */
+.sr { color: #0000FF } /* Literal.String.Regex */
+.s1 { color: #0000FF } /* Literal.String.Single */
+.ss { color: #0000FF } /* Literal.String.Symbol */
+.il { color: #0000FF } /* Literal.Number.Integer.Long */
diff --git a/css/styles.css b/css/styles.css
new file mode 100644
index 0000000..5d5b800
--- /dev/null
+++ b/css/styles.css
@@ -0,0 +1,565 @@
+@import url(http://fonts.googleapis.com/css?family=Satisfy|Lato:300,700,300italic,700italic);
+
+/* Global
+-------------------------------------------------- */
+body {
+ font: 14.5px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif;
+ color:#777;
+ font-weight:300;
+}
+
+a {
+ color: #08c;
+}
+
+a:hover {
+ text-decoration: underline;
+ color: #004f77;
+}
+
+img.fork-on-github {
+ position: absolute;
+ top: 0;
+ left: 0;
+ border: 0;
+ width: 149px;
+ height: 149px;
+ z-index: 1;
+}
+
+.strong { font-weight: bold; }
+
+.satisfy { font-family: 'Satisfy', cursive; }
+
+/* Global: Header
+-------------------------------------------------- */
+header {
+ position: relative;
+ min-height: 150px;
+ width: 100%;
+ color: white;
+ background-color: #ff8b00;
+}
+
+.header-title {
+ background-color: #d7690d;
+}
+
+.header-content {
+ background-color: #ff8b00;
+}
+
+.header-links {
+ padding-top: 15px;
+ text-align: right;
+ font-size: 15px;
+}
+
+.header-links a {
+ display: inline-block;
+ margin: 0 15px;
+ text-decoration: none;
+ color: #fff;
+}
+
+.header-links a:hover {
+ text-decoration: underline;
+ color: #feffff;
+}
+
+.header-links a.selected {
+ text-decoration: underline;
+ /* color: #fff; */
+ color: #feffff;
+ cursor: default;
+}
+
+#download-on-github {
+ float: right;
+ font-size: 24px;
+ padding: 10px 40px;
+ margin: 35px 0;
+ border-bottom: 6px solid #ccc;
+}
+
+#top-shadow {
+ position: absolute;
+ width: 100%;
+ bottom: 0;
+ height: 60px;
+ background: url('../images/top-shadow.png') no-repeat center;
+ -webkit-background-size: cover;
+ -moz-background-size: cover;
+ -o-background-size: cover;
+ background-size: cover;
+}
+
+#title-small {
+ color: #fff;
+ position: absolute;
+ left: 150px;
+ top: 15px;
+ width: 126px;
+ height: 42px;
+ font-size: 34px;
+}
+
+#page-title {
+ position: absolute;
+ left: 150px;
+ top: 60px;
+ font-size: 60px;
+ font-weight: bold;
+ line-height: 1.2em;
+}
+
+#page-links {
+ position: absolute;
+ left: 90px;
+ top: 155px;
+ font-size: 18px;
+}
+
+#page-links a {
+ color: #fff;
+}
+
+/* Firechat Demo
+-------------------------------------------------- */
+#firechat-container {
+ height: 475px;
+ max-width: 325px;
+ padding: 10px;
+ border: 1px solid #ccc;
+ background-color: #fff;
+ margin: auto auto;
+ text-align: center;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 5px 25px #666;
+ -moz-box-shadow: 0 5px 25px #666;
+ box-shadow: 0 5px 25px #666;
+}
+
+#auth-modal {
+ width: 30%;
+ margin-left: -15%;
+ left: 50%;
+}
+
+#user-info {
+ display: block;
+ margin-top: 10px;
+}
+
+#user-info a {
+ color: #fff;
+ text-decoration: underline;
+}
+
+/* Social Buttons
+-------------------------------------------------- */
+.social-container {
+ padding: 10px 0;
+ color: #5e5e5e;
+ text-align: center;
+ border-bottom: 1px solid #e0e0e0;
+ background-color: #f7f7f7;
+}
+.social-buttons {
+ margin-top: 2px;
+ margin-left: 0;
+ margin-bottom: 0;
+ padding-left: 0;
+ list-style: none;
+}
+.social-buttons li {
+ display: inline-block;
+ padding: 6px 8px 5px;
+ line-height: 1;
+ *display: inline;
+ *zoom: 1;
+}
+
+/************************************************/
+/* Stuff below-the-fold (the white part) */
+/************************************************/
+#bottom-container {
+ border: 1px solid #e2e2e2;
+ border-top: 0;
+ background-color: white;
+ margin: 0 auto;
+ z-index: 1;
+ -webkit-box-shadow: 0 0 6px 1px #bbb;
+ -moz-box-shadow: 0 0 6px 1px #bbb;
+ box-shadow: 0 0 6px 1px #bbb;
+}
+
+.bottom-content {
+ padding: 30px 0px 0;
+ color: #5e5e5e;
+ margin-bottom: 40px;
+}
+
+.bottom-content.separator {
+ margin-top: 40px;
+ border-top: 1px solid #e0e0e0;
+}
+
+.build-something {
+ margin: auto;
+ margin-top: 30px;
+ width: 500px;
+ font-style: italic;
+}
+
+/* Footer
+-------------------------------------------------- */
+footer {
+ border-top: 1px solid #ccc;
+ background-color: #f4f4f4;
+ margin-top: 50px;
+ height: 200px;
+ margin: 0 auto;
+ position: relative;
+}
+
+#footer-links {
+ padding-top: 24px;
+ text-align: center;
+ margin-bottom: 20px;
+}
+
+#powered-by-firebase {
+ display: inline-block;
+ height: 34px;
+ width: 129px;
+ background: url('../images/powered-by-firebase-dim.png') no-repeat center;
+}
+
+#powered-by-firebase:hover {
+ background: url('../images/powered-by-firebase.png') no-repeat center;
+}
+
+#footer-links a {
+ font-size: 14px;
+ color: #999;
+ text-decoration: none;
+ margin: 0 20px;
+ display: inline-block;
+}
+
+#footer-links a:hover {
+ text-decoration: underline;
+ color: #333;
+}
+
+
+/* index.html
+-------------------------------------------------- */
+.home-page {
+ background-color: #f4f4f4;
+}
+
+.home-page #top-content {
+ height: 925px;
+ padding-bottom: 35px;
+}
+
+.home-page footer {
+ border-top: none;
+}
+
+#home-title {
+ margin: 45px auto -15px auto;
+ font-size: 72px;
+ text-align: center;
+}
+
+#home-subtitle {
+ font-size: 36px;
+ text-align: center;
+ margin-bottom: 60px;
+ font-weight: 100;
+}
+
+#home-download-on-github {
+ font-size: 24px;
+ padding: 25px 55px 19px;
+ margin: -40px auto 35px;
+ border-bottom: 6px solid #ccc;
+}
+
+#code-container {
+ width: 600px;
+ margin: 22px auto;
+}
+
+#code-heading {
+ text-align: left;
+ font-size: 22px;
+ margin-bottom: 2px;
+}
+
+#code-heading a {
+ color: #f9e701;
+ text-decoration: underline;
+}
+
+#code-container pre {
+ background-color: rgba(255, 255, 255, 0.3);
+ border: 1px solid rgba(0, 0, 0, 0.3);
+ margin: 0;
+ padding: 20px;
+ text-align: left;
+ font-size: 14px;
+ color: #000;
+ line-height: 20px;
+ font-family: monospace;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+#code-examples-link {
+ display: block;
+ float: right;
+ margin-top: 5px;
+ font-size: 17px;
+ color: #fff;
+}
+
+.bottom-title {
+ font-size: 42px;
+ color: #333;
+ text-align: center;
+ padding: 30px 0 0;
+}
+
+/* Docs
+-------------------------------------------------- */
+.docs-page {
+ background: white;
+}
+.docs-content {
+ color: #5e5e5e;
+ margin-bottom: 40px;
+}
+.docs-page pre {
+ margin: 20px 10px;
+ padding: 15px 10px;
+}
+.docs-separator {
+ height: 0;
+ margin-top: 40px;
+ margin-bottom: 30px;
+ border-top: 1px solid #e0e0e0;
+}
+.docs-content p {
+ margin: 0 0 10px;
+}
+
+.docs-content h1 {
+ margin: 20px 0 10px;
+}
+.docs-content h2 {
+ margin: 20px 0 10px;
+}
+.docs-content h3 {
+ margin: 20px 0 10px;
+}
+.docs-content h4 {
+ margin: 20px 0 10px;
+}
+.docs-content a {
+ color: #eb8717;
+}
+.docs-content .emphasis-box {
+ border: 1px solid #e5a165;
+ background: #ffbf86;
+ padding: 10px 90px;
+ color: #000;
+ font-size: 16px;
+}
+.docs-content .emphasis-box a {
+ color: #dc4700;
+}
+.error-page #top {
+ height: 200px;
+}
+
+/* Docs: Annotated Source
+-------------------------------------------------- */
+.docs-annotated-source-page iframe {
+ width: 100%;
+ min-height: 100%;
+ border: none;
+}
+
+/* Sidenav for Docs
+-------------------------------------------------- */
+.sidenav {
+ width: 220px;
+ margin: 35px 0 0;
+ padding: 0;
+ background-color: #fcfcfc;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ -webkit-box-shadow: 0 1px 4px rgba(0,0,0,.065);
+ -moz-box-shadow: 0 1px 4px rgba(0,0,0,.065);
+ box-shadow: 0 1px 4px rgba(0,0,0,.065);
+}
+.sidenav > li > a {
+ display: block;
+ width: 190px \9;
+ margin: 0 0 -1px;
+ padding: 8px 14px;
+ border: 1px solid #e5e5e5;
+}
+.sidenav > li:first-child > a {
+ -webkit-border-radius: 6px 6px 0 0;
+ -moz-border-radius: 6px 6px 0 0;
+ border-radius: 6px 6px 0 0;
+}
+.sidenav > li:last-child > a {
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+}
+.sidenav > .active > a {
+ position: relative;
+ z-index: 2;
+ padding: 9px 15px;
+ border: 0;
+ text-shadow: 0 1px 0 rgba(0,0,0,.15);
+ -webkit-box-shadow: inset 1px 0 0 rgba(0,0,0,.1), inset -1px 0 0 rgba(0,0,0,.1);
+ -moz-box-shadow: inset 1px 0 0 rgba(0,0,0,.1), inset -1px 0 0 rgba(0,0,0,.1);
+ box-shadow: inset 1px 0 0 rgba(0,0,0,.1), inset -1px 0 0 rgba(0,0,0,.1);
+}
+.sidenav .icon-chevron-right {
+ float: right;
+ margin-top: 2px;
+ margin-right: -6px;
+ opacity: .25;
+}
+.sidenav > li > a:hover {
+ background-color: #f5f5f5;
+}
+.sidenav a:hover .icon-chevron-right {
+ opacity: .5;
+}
+.sidenav .active .icon-chevron-right,
+.sidenav .active a:hover .icon-chevron-right {
+ background-image: url(//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.3.2/css/bootstrap.css);
+ opacity: 1;
+}
+.sidenav.affix {
+ top: 40px;
+}
+.sidenav.affix-bottom {
+ position: absolute;
+ top: auto;
+ bottom: 270px;
+}
+
+
+/* Responsive
+-------------------------------------------------- */
+
+/* Desktop large
+------------------------- */
+@media (min-width: 1200px) {
+ .sidenav {
+ width: 258px;
+ }
+ .sidenav > li > a {
+ width: 230px \9;
+ }
+}
+
+/* Desktop
+------------------------- */
+@media (max-width: 980px) {
+ .sidenav {
+ top: 0;
+ width: 218px;
+ margin-right: 0;
+ }
+}
+
+/* Tablet to desktop
+------------------------- */
+@media (min-width: 768px) and (max-width: 979px) {
+ .home-page #top-content {
+ height: 925px;
+ }
+ .sidenav {
+ width: 166px;
+ }
+ .sidenav.affix {
+ top: 0;
+ }
+}
+
+/* Tablet
+------------------------- */
+@media (min-width: 481px) and (max-width: 767px) {
+ body {
+ padding: 0;
+ }
+ .home-page #top-content {
+ height: 975px;
+ }
+ .sidenav {
+ width: auto;
+ margin-bottom: 20px;
+ }
+ .sidenav.affix {
+ position: static;
+ width: auto;
+ top: 0;
+ }
+ #docs-container {
+ padding: 0 15px;
+ }
+ .bottom-content {
+ padding: 0 15px;
+ }
+}
+
+/* Landscape phones
+------------------------- */
+@media (max-width: 480px) {
+ body {
+ padding: 0;
+ }
+ .home-page #top-content {
+ height: 985px;
+ }
+ .header-links {
+ text-align: center;
+ }
+ img.fork-on-github {
+ display: none;
+ }
+ .sidenav {
+ width: auto;
+ margin-bottom: 20px;
+ }
+ .sidenav.affix {
+ position: static;
+ width: auto;
+ top: 0;
+ }
+ #docs-container {
+ padding: 0 15px;
+ }
+ .social-buttons li {
+ padding: 6px 0px 5px;
+ }
+ .bottom-content {
+ padding: 0 15px;
+ }
+}
diff --git a/docs/annotated-source.html b/docs/annotated-source.html
new file mode 100644
index 0000000..7b2e482
--- /dev/null
+++ b/docs/annotated-source.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+ Firechat - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/docco.css b/docs/docco.css
new file mode 100644
index 0000000..f690a07
--- /dev/null
+++ b/docs/docco.css
@@ -0,0 +1,500 @@
+/*--------------------- Typography ----------------------------*/
+
+@font-face {
+ font-family: 'aller-light';
+ src: url('public/fonts/aller-light.eot');
+ src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'),
+ url('public/fonts/aller-light.woff') format('woff'),
+ url('public/fonts/aller-light.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'aller-bold';
+ src: url('public/fonts/aller-bold.eot');
+ src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'),
+ url('public/fonts/aller-bold.woff') format('woff'),
+ url('public/fonts/aller-bold.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'novecento-bold';
+ src: url('public/fonts/novecento-bold.eot');
+ src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'),
+ url('public/fonts/novecento-bold.woff') format('woff'),
+ url('public/fonts/novecento-bold.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+}
+
+/*--------------------- Layout ----------------------------*/
+html { height: 100%; }
+body {
+ font-family: "aller-light";
+ font-size: 14px;
+ line-height: 18px;
+ color: #30404f;
+ margin: 0; padding: 0;
+ height:100%;
+}
+#container { min-height: 100%; }
+
+a {
+ color: #000;
+}
+
+b, strong {
+ font-weight: normal;
+ font-family: "aller-bold";
+}
+
+p, ul, ol {
+ margin: 15px 0 0px;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #112233;
+ line-height: 1em;
+ font-weight: normal;
+ font-family: "novecento-bold";
+ text-transform: uppercase;
+ margin: 30px 0 15px 0;
+}
+
+h1 {
+ margin-top: 40px;
+}
+
+hr {
+ border: 0;
+ background: 1px solid #ddd;
+ height: 1px;
+ margin: 20px 0;
+}
+
+pre, tt, code {
+ font-size: 12px; line-height: 16px;
+ font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
+ margin: 0; padding: 0;
+}
+ .annotation pre {
+ display: block;
+ margin: 0;
+ padding: 7px 10px;
+ background: #fcfcfc;
+ -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
+ -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
+ box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
+ overflow-x: auto;
+ }
+ .annotation pre code {
+ border: 0;
+ padding: 0;
+ background: transparent;
+ }
+
+
+blockquote {
+ border-left: 5px solid #ccc;
+ margin: 0;
+ padding: 1px 0 1px 1em;
+}
+ .sections blockquote p {
+ font-family: Menlo, Consolas, Monaco, monospace;
+ font-size: 12px; line-height: 16px;
+ color: #999;
+ margin: 10px 0 0;
+ white-space: pre-wrap;
+ }
+
+ul.sections {
+ list-style: none;
+ padding:0 0 5px 0;;
+ margin:0;
+}
+
+/*
+ Force border-box so that % widths fit the parent
+ container without overlap because of margin/padding.
+
+ More Info : http://www.quirksmode.org/css/box.html
+*/
+ul.sections > li > div {
+ -moz-box-sizing: border-box; /* firefox */
+ -ms-box-sizing: border-box; /* ie */
+ -webkit-box-sizing: border-box; /* webkit */
+ -khtml-box-sizing: border-box; /* konqueror */
+ box-sizing: border-box; /* css3 */
+}
+
+
+/*---------------------- Jump Page -----------------------------*/
+#jump_to, #jump_page {
+ margin: 0;
+ background: white;
+ -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
+ -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
+ font: 16px Arial;
+ cursor: pointer;
+ text-align: right;
+ list-style: none;
+}
+
+#jump_to a {
+ text-decoration: none;
+}
+
+#jump_to a.large {
+ display: none;
+}
+#jump_to a.small {
+ font-size: 22px;
+ font-weight: bold;
+ color: #676767;
+}
+
+#jump_to, #jump_wrapper {
+ position: fixed;
+ right: 0; top: 0;
+ padding: 10px 15px;
+ margin:0;
+}
+
+#jump_wrapper {
+ display: none;
+ padding:0;
+}
+
+#jump_to:hover #jump_wrapper {
+ display: block;
+}
+
+#jump_page {
+ padding: 5px 0 3px;
+ margin: 0 0 25px 25px;
+}
+
+#jump_page .source {
+ display: block;
+ padding: 15px;
+ text-decoration: none;
+ border-top: 1px solid #eee;
+}
+
+#jump_page .source:hover {
+ background: #f5f5ff;
+}
+
+#jump_page .source:first-child {
+}
+
+/*---------------------- Low resolutions (> 320px) ---------------------*/
+@media only screen and (min-width: 320px) {
+ .pilwrap { display: none; }
+
+ ul.sections > li > div {
+ display: block;
+ padding:5px 10px 0 10px;
+ }
+
+ ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol {
+ padding-left: 30px;
+ }
+
+ ul.sections > li > div.content {
+ background: #f5f5ff;
+ overflow-x:auto;
+ -webkit-box-shadow: inset 0 0 5px #e5e5ee;
+ box-shadow: inset 0 0 5px #e5e5ee;
+ border: 1px solid #dedede;
+ margin:5px 10px 5px 10px;
+ padding-bottom: 5px;
+ }
+
+ ul.sections > li > div.annotation pre {
+ margin: 7px 0 7px;
+ padding-left: 15px;
+ }
+
+ ul.sections > li > div.annotation p tt, .annotation code {
+ background: #f8f8ff;
+ border: 1px solid #dedede;
+ font-size: 12px;
+ padding: 0 0.2em;
+ }
+}
+
+/*---------------------- (> 481px) ---------------------*/
+@media only screen and (min-width: 481px) {
+ #container {
+ position: relative;
+ }
+ body {
+ background-color: #F5F5FF;
+ font-size: 15px;
+ line-height: 21px;
+ }
+ pre, tt, code {
+ line-height: 18px;
+ }
+ p, ul, ol {
+ margin: 0 0 15px;
+ }
+
+
+ #jump_to {
+ padding: 5px 10px;
+ }
+ #jump_wrapper {
+ padding: 0;
+ }
+ #jump_to, #jump_page {
+ font: 10px Arial;
+ text-transform: uppercase;
+ }
+ #jump_page .source {
+ padding: 5px 10px;
+ }
+ #jump_to a.large {
+ display: inline-block;
+ }
+ #jump_to a.small {
+ display: none;
+ }
+
+
+
+ #background {
+ position: absolute;
+ top: 0; bottom: 0;
+ width: 350px;
+ background: #fff;
+ border-right: 1px solid #e5e5ee;
+ z-index: -1;
+ }
+
+ ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol {
+ padding-left: 40px;
+ }
+
+ ul.sections > li {
+ white-space: nowrap;
+ }
+
+ ul.sections > li > div {
+ display: inline-block;
+ }
+
+ ul.sections > li > div.annotation {
+ max-width: 350px;
+ min-width: 350px;
+ min-height: 5px;
+ padding: 13px;
+ overflow-x: hidden;
+ white-space: normal;
+ vertical-align: top;
+ text-align: left;
+ }
+ ul.sections > li > div.annotation pre {
+ margin: 15px 0 15px;
+ padding-left: 15px;
+ }
+
+ ul.sections > li > div.content {
+ padding: 13px;
+ vertical-align: top;
+ background: #f5f5ff;
+ border: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+
+ .pilwrap {
+ position: relative;
+ display: inline;
+ }
+
+ .pilcrow {
+ font: 12px Arial;
+ text-decoration: none;
+ color: #454545;
+ position: absolute;
+ top: 3px; left: -20px;
+ padding: 1px 2px;
+ opacity: 0;
+ -webkit-transition: opacity 0.2s linear;
+ }
+ .for-h1 .pilcrow {
+ top: 47px;
+ }
+ .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow {
+ top: 35px;
+ }
+
+ ul.sections > li > div.annotation:hover .pilcrow {
+ opacity: 1;
+ }
+}
+
+/*---------------------- (> 1025px) ---------------------*/
+@media only screen and (min-width: 1025px) {
+
+ body {
+ font-size: 16px;
+ line-height: 24px;
+ }
+
+ #background {
+ width: 525px;
+ }
+ ul.sections > li > div.annotation {
+ max-width: 525px;
+ min-width: 525px;
+ padding: 10px 25px 1px 50px;
+ }
+ ul.sections > li > div.content {
+ padding: 9px 15px 16px 25px;
+ }
+}
+
+/*---------------------- Syntax Highlighting -----------------------------*/
+
+td.linenos { background-color: #f0f0f0; padding-right: 10px; }
+span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
+/*
+
+github.com style (c) Vasily Polovnyov
+
+*/
+
+pre code {
+ display: block; padding: 0.5em;
+ color: #000;
+ background: #f8f8ff
+}
+
+pre .comment,
+pre .template_comment,
+pre .diff .header,
+pre .javadoc {
+ color: #408080;
+ font-style: italic
+}
+
+pre .keyword,
+pre .assignment,
+pre .literal,
+pre .css .rule .keyword,
+pre .winutils,
+pre .javascript .title,
+pre .lisp .title,
+pre .subst {
+ color: #954121;
+ /*font-weight: bold*/
+}
+
+pre .number,
+pre .hexcolor {
+ color: #40a070
+}
+
+pre .string,
+pre .tag .value,
+pre .phpdoc,
+pre .tex .formula {
+ color: #219161;
+}
+
+pre .title,
+pre .id {
+ color: #19469D;
+}
+pre .params {
+ color: #00F;
+}
+
+pre .javascript .title,
+pre .lisp .title,
+pre .subst {
+ font-weight: normal
+}
+
+pre .class .title,
+pre .haskell .label,
+pre .tex .command {
+ color: #458;
+ font-weight: bold
+}
+
+pre .tag,
+pre .tag .title,
+pre .rules .property,
+pre .django .tag .keyword {
+ color: #000080;
+ font-weight: normal
+}
+
+pre .attribute,
+pre .variable,
+pre .instancevar,
+pre .lisp .body {
+ color: #008080
+}
+
+pre .regexp {
+ color: #B68
+}
+
+pre .class {
+ color: #458;
+ font-weight: bold
+}
+
+pre .symbol,
+pre .ruby .symbol .string,
+pre .ruby .symbol .keyword,
+pre .ruby .symbol .keymethods,
+pre .lisp .keyword,
+pre .tex .special,
+pre .input_number {
+ color: #990073
+}
+
+pre .builtin,
+pre .constructor,
+pre .built_in,
+pre .lisp .title {
+ color: #0086b3
+}
+
+pre .preprocessor,
+pre .pi,
+pre .doctype,
+pre .shebang,
+pre .cdata {
+ color: #999;
+ font-weight: bold
+}
+
+pre .deletion {
+ background: #fdd
+}
+
+pre .addition {
+ background: #dfd
+}
+
+pre .diff .change {
+ background: #0086b3
+}
+
+pre .chunk {
+ color: #aaa
+}
+
+pre .tex .formula {
+ opacity: 0.5;
+}
diff --git a/docs/firechat.html b/docs/firechat.html
new file mode 100644
index 0000000..12dbcb4
--- /dev/null
+++ b/docs/firechat.html
@@ -0,0 +1,1395 @@
+
+
+
+
+ firechat.js
+
+
+
+
+
+
+
+
+
+
+
+
+
firechat.js
+
+
+
+
+
+
+
+
+
+
Firechat is a simple, easily-extensible data layer for multi-user,
+multi-room chat, built entirely on Firebase .
+
The Firechat object is the primary conduit for all underlying data events.
+It exposes a number of methods for binding event listeners, creating,
+entering, or leaving chat rooms, initiating chats, sending messages,
+and moderator actions such as warning, kicking, or suspending users.
+
Firechat.js 0.1.0
+https://firebase.com
+(c) 2013 Firebase
+License: MIT
+
Setup
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Establish a reference to the window object, and save the previous value
+of the Firechat variable.
+
+
+
+ var root = this ,
+ previousFirechat = root.Firechat;
+
+ function Firechat (firebaseRef, options) {
+
+
+
+
+
+
+
+
+
Instantiate a new connection to Firebase.
+
+
+
+ this ._firebase = firebaseRef;
+
+
+
+
+
+
+
+
+
User-specific instance variables.
+
+
+
+ this ._user = null ;
+ this ._userId = null ;
+ this ._userName = null ;
+ this ._isModerator = false ;
+
+
+
+
+
+
+
+
+
A unique id generated for each session.
+
+
+
+
+
+
+
+
+
+
+
+
+
A mapping of event IDs to an array of callbacks.
+
+
+
+
+
+
+
+
+
+
+
+
+
A mapping of room IDs to a boolean indicating presence.
+
+
+
+
+
+
+
+
+
+
+
+
+
A mapping of operations to re-queue on disconnect.
+
+
+
+
+
+
+
+
+
+
+
+
+
Commonly-used Firebase references.
+
+
+
+ this ._userRef = null ;
+ this ._messageRef = this ._firebase.child('room-messages' );
+ this ._roomRef = this ._firebase.child('room-metadata' );
+ this ._privateRoomRef = this ._firebase.child('room-private-metadata' );
+ this ._moderatorsRef = this ._firebase.child('moderators' );
+ this ._suspensionsRef = this ._firebase.child('suspensions' );
+ this ._usersOnlineRef = this ._firebase.child('user-names-online' );
+
+
+
+
+
+
+
+
+
Setup and establish default options.
+
+
+
+ this ._options = options || {};
+
+
+
+
+
+
+
+
+
The number of historical messages to load per room.
+
+
+
+ this ._options.numMaxMessages = this ._options.numMaxMessages || 50 ;
+ }
+
+
+
+
+
+
+
+
+
Run Firechat in noConflict mode, returning the Firechat variable to
+its previous owner, and returning a reference to the Firechat object.
+
+
+
+ Firechat.noConflict = function noConflict () {
+ root.Firechat = previousFirechat;
+ return Firechat;
+ };
+
+
+
+
+
+
+
+
+
Export the Firechat object as a global.
+
+
+
+ root.Firechat = Firechat;
+
+
+
+
+
+
+
+
+
Firechat Internal Methods
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Load the initial metadata for the user's account and set initial state.
+
+
+
+ _loadUserMetadata: function (onComplete) {
+ var self = this ;
+
+
+
+
+
+
+
+
+
Update the user record with a default name on user's first visit.
+
+
+
+ this ._userRef.transaction(function (current) {
+ if (!current || !current.id || !current.name) {
+ return {
+ id: self._userId,
+ name: self._userName
+ };
+ }
+ }, function (error, committed, snapshot) {
+ self._user = snapshot.val();
+ self._moderatorsRef.child(self._userId).once('value' , function (snapshot) {
+ self._isModerator = !!snapshot.val();
+ root.setTimeout(onComplete, 0 );
+ });
+ });
+ },
+
+
+
+
+
+
+
+
+
Initialize Firebase listeners and callbacks for the supported bindings.
+
+
+
+ _setupDataEvents: function () {
+
+
+
+
+
+
+
+
+
Monitor connection state so we can requeue disconnect operations if need be.
+
+
+
+ this ._firebase.root().child('.info/connected' ).on('value' , function (snapshot) {
+ if (snapshot.val() === true ) {
+
+
+
+
+
+
+
+
+
We're connected (or reconnected)! Set up our presence state.
+
+
+
+ for (var i = 0 ; i < this ._presenceBits; i++) {
+ var op = this ._presenceBits[i],
+ ref = this ._firebase.root().child(op.ref);
+
+ ref.onDisconnect().set(op.offlineValue);
+ ref.set(op.onlineValue);
+ }
+ }
+ }, this );
+
+
+
+
+
+
+
+
+
Generate a unique session id for the visit.
+
+
+
+ var sessionRef = this ._userRef.child('sessions' ).push();
+ this ._sessionId = sessionRef.name();
+ this ._queuePresenceOperation(sessionRef, true , null );
+
+
+
+
+
+
+
+
+
Register our username in the public user listing.
+
+
+
+ var usernameRef = this ._usersOnlineRef.child(this ._userName.toLowerCase());
+ var usernameSessionRef = usernameRef.child(this ._sessionId);
+ this ._queuePresenceOperation(usernameSessionRef, {
+ id: this ._userId,
+ name: this ._userName
+ }, null );
+
+
+
+
+
+
+
+
+
Listen for state changes for the given user.
+
+
+
+ this ._userRef.on('value' , this ._onUpdateUser, this );
+
+
+
+
+
+
+
+
+
Listen for chat invitations from other users.
+
+
+
+ this ._userRef.child('invites' ).on('child_added' , this ._onFirechatInvite, this );
+
+
+
+
+
+
+
+
+
Listen for messages from moderators and adminstrators.
+
+
+
+ this ._userRef.child('notifications' ).on('child_added' , this ._onNotification, this );
+ },
+
+
+
+
+
+
+
+
+
Append the new callback to our list of event handlers.
+
+
+
+ _addEventCallback: function (eventId, callback) {
+ this ._events[eventId] = this ._events[eventId] || [];
+ this ._events[eventId].push(callback);
+ },
+
+
+
+
+
+
+
+
+
Retrieve the list of event handlers for a given event id.
+
+
+
+ _getEventCallbacks: function (eventId) {
+ if (this ._events.hasOwnProperty(eventId)) {
+ return this ._events[eventId];
+ }
+ return [];
+ },
+
+
+
+
+
+
+
+
+
Invoke each of the event handlers for a given event id with specified data.
+
+
+
+ _invokeEventCallbacks: function (eventId) {
+ var args = [],
+ callbacks = this ._getEventCallbacks(eventId);
+
+ Array.prototype.push.apply(args, arguments);
+ args = args.slice(1 );
+
+ for (var i = 0 ; i < callbacks.length; i += 1 ) {
+ callbacks[i].apply(null , args);
+ }
+ },
+
+
+
+
+
+
+
+
+
Keep track of on-disconnect events so they can be requeued if we disconnect the reconnect.
+
+
+
+ _queuePresenceOperation: function (ref, onlineValue, offlineValue) {
+ ref.onDisconnect().set(offlineValue);
+ ref.set(onlineValue);
+ this ._presenceBits[ref.toString()] = {
+ ref: ref,
+ onlineValue: onlineValue,
+ offlineValue: offlineValue
+ };
+ },
+
+
+
+
+
+
+
+
+
Remove an on-disconnect event from firing upon future disconnect and reconnect.
+
+
+
+ _removePresenceOperation: function (path, value) {
+ var ref = new Firebase(path);
+ ref.onDisconnect().cancel();
+ ref.set(value);
+ delete this ._presenceBits[path];
+ },
+
+
+
+
+
+
+
+
+
Event to monitor user current user state.
+
+
+
+ _onUpdateUser: function (snapshot) {
+ this ._user = snapshot.val();
+ this ._invokeEventCallbacks('user-update' , this ._user);
+ },
+
+
+
+
+
+
+
+
+
Events to monitor room entry / exit and messages additional / removal.
+
+
+
+ _onEnterRoom: function (room) {
+ this ._invokeEventCallbacks('room-enter' , room);
+ },
+ _onNewMessage: function (roomId, snapshot) {
+ var message = snapshot.val();
+ message.id = snapshot.name();
+ this ._invokeEventCallbacks('message-add' , roomId, message);
+ },
+ _onRemoveMessage: function (roomId, snapshot) {
+ var messageId = snapshot.name();
+ this ._invokeEventCallbacks('message-remove' , roomId, messageId);
+ },
+ _onLeaveRoom: function (roomId) {
+ this ._invokeEventCallbacks('room-exit' , roomId);
+ },
+
+
+
+
+
+
+
+
+
Event to listen for notifications from administrators and moderators.
+
+
+
+ _onNotification: function (snapshot) {
+ var notification = snapshot.val();
+ if (!notification.read) {
+ if (notification.notificationType !== 'suspension' || notification.data.suspendedUntil < Firebase.ServerValue.TIMESTAMP) {
+ snapshot.ref().child('read' ).set(true );
+ }
+ this ._invokeEventCallbacks('notification' , notification);
+ }
+ },
+
+
+
+
+
+
+
+
+
Events to monitor chat invitations and invitation replies.
+
+
+
+ _onFirechatInvite: function (snapshot) {
+ var self = this ,
+ invite = snapshot.val();
+
+
+
+
+
+
+
+
+
Skip invites we've already responded to.
+
+
+
+ if (invite.status) {
+ return ;
+ }
+
+ invite.id = invite.id || snapshot.name();
+ self.getRoom(invite.roomId, function (room) {
+ invite.toRoomName = room.name;
+ self._invokeEventCallbacks('room-invite' , invite);
+ });
+ },
+ _onFirechatInviteResponse: function (snapshot) {
+ var self = this ,
+ invite = snapshot.val();
+
+ invite.id = invite.id || snapshot.name();
+ this ._invokeEventCallbacks('room-invite-response' , invite);
+ }
+ };
+
+
+
+
+
+
+
+
+
Firechat External Methods
+
+
+
+
+
+
+
+
+
+
+
Initialize the library and setup data listeners.
+
+
+
+ Firechat.prototype.initWithUser = function (userId, userName, callback) {
+ var self = this ;
+
+ self._firebase.root().child('.info/authenticated' ).on('value' , function (snapshot) {
+ if (snapshot.val() === true ) {
+ self._firebase.root().child('.info/authenticated' ).off();
+
+ self._userId = userId.toString();
+ self._userName = userName.toString();
+ self._userRef = self._firebase.child('users' ).child(self._userId);
+ self._loadUserMetadata(function () {
+ root.setTimeout(function () {
+ callback(self._user);
+ self._setupDataEvents();
+ }, 0 );
+ });
+ }
+ });
+ };
+
+
+
+
+
+
+
+
+
Resumes the previous session by automatically entering rooms.
+
+
+
+ Firechat.prototype.resumeSession = function () {
+ this ._userRef.child('rooms' ).once('value' , function (snapshot) {
+ var rooms = snapshot.val();
+ for (var roomId in rooms) {
+ this .enterRoom(rooms[roomId].id);
+ }
+ }, function (){}, this );
+ };
+
+
+
+
+
+
+
+
+
Callback registration. Supports each of the following events:
+
+
+
+ Firechat.prototype.on = function (eventType, cb) {
+ this ._addEventCallback(eventType, cb);
+ };
+
+
+
+
+
+
+
+
+
Create and automatically enter a new chat room.
+
+
+
+ Firechat.prototype.createRoom = function (roomName, roomType, callback) {
+ var self = this ,
+ newRoomRef = this ._roomRef.push();
+
+ var newRoom = {
+ id: newRoomRef.name(),
+ name: roomName,
+ type: roomType || 'public' ,
+ createdByUserId: this ._userId,
+ createdAt: Firebase.ServerValue.TIMESTAMP
+ };
+
+ if (roomType === 'private' ) {
+ newRoom.authorizedUsers = {};
+ newRoom.authorizedUsers[this ._userId] = true ;
+ }
+
+ newRoomRef.set(newRoom, function (error) {
+ if (!error) {
+ self.enterRoom(newRoomRef.name());
+ }
+ if (callback) {
+ callback(newRoomRef.name());
+ }
+ });
+ };
+
+
+
+
+
+
+
+
+
Enter a chat room.
+
+
+
+ Firechat.prototype.enterRoom = function (roomId) {
+ var self = this ;
+ self.getRoom(roomId, function (room) {
+ var roomName = room.name;
+
+ if (!roomId || !roomName) return ;
+
+
+
+
+
+
+
+
+
Skip if we're already in this room.
+
+
+
+ if (self._rooms[roomId]) {
+ return ;
+ }
+
+ self._rooms[roomId] = true ;
+
+
+
+
+
+
+
+
+
Save entering this room to resume the session again later.
+
+
+
+ self._userRef.child('rooms' ).child(roomId).set({
+ id: roomId,
+ name: roomName,
+ active: true
+ });
+
+
+
+
+
+
+
+
+
Set presence bit for the room and queue it for removal on disconnect.
+
+
+
+ var presenceRef = self._firebase.child('room-users' ).child(roomId).child(self._userId).child(self._sessionId);
+ self._queuePresenceOperation(presenceRef, {
+ id: self._userId,
+ name: self._userName
+ }, null );
+
+
+
+
+
+
+
+
+
Invoke our callbacks before we start listening for new messages.
+
+
+
+ self._onEnterRoom({ id: roomId, name: roomName });
+
+
+
+
+
+
+
+
+
Setup message listeners
+
+
+
+ self._roomRef.child(roomId).once('value' , function (snapshot) {
+ self._messageRef.child(roomId).limit(self._options.numMaxMessages).on('child_added' , function (snapshot) {
+ self._onNewMessage(roomId, snapshot);
+ }, function () {
+
+
+
+
+
+
+
+
+
Turns out we don't have permission to access these messages.
+
+
+
+ self.leaveRoom(roomId);
+ }, self);
+
+ self._messageRef.child(roomId).limit(self._options.numMaxMessages).on('child_removed' , function (snapshot) {
+ self._onRemoveMessage(roomId, snapshot);
+ }, function (){}, self);
+ }, function (){}, self);
+ });
+ };
+
+
+
+
+
+
+
+
+
Leave a chat room.
+
+
+
+ Firechat.prototype.leaveRoom = function (roomId) {
+ var self = this ,
+ userRoomRef = self._firebase.child('room-users' ).child(roomId),
+ presenceRef = userRoomRef.child(self._userId).child(self._sessionId);
+
+
+
+
+
+
+
+
+
Remove listener for new messages to this room.
+
+
+
+ self._messageRef.child(roomId).off();
+
+
+
+
+
+
+
+
+
Remove presence bit for the room and cancel on-disconnect removal.
+
+
+
+ self._removePresenceOperation(presenceRef.toString(), null );
+
+
+
+
+
+
+
+
+
Remove session bit for the room.
+
+
+
+ self._userRef.child('rooms' ).child(roomId).remove();
+
+ delete self._rooms[roomId];
+
+
+
+
+
+
+
+
+
Invoke event callbacks for the room-exit event.
+
+
+
+ self._onLeaveRoom(roomId);
+ };
+
+ Firechat.prototype.sendMessage = function (roomId, messageContent, messageType, cb) {
+ var self = this ,
+ message = {
+ userId: self._userId,
+ name: self._userName,
+ timestamp: Firebase.ServerValue.TIMESTAMP,
+ message: messageContent,
+ type: messageType || 'default'
+ },
+ newMessageRef = self._messageRef.child(roomId).push();
+
+ newMessageRef.setWithPriority(message, Firebase.ServerValue.TIMESTAMP, cb);
+ };
+
+ Firechat.prototype.deleteMessage = function (roomId, messageId, cb) {
+ var self = this ;
+
+ self._messageRef.child(roomId).child(messageId).remove(cb);
+ };
+
+
+
+
+
+
+
+
+
Mute or unmute a given user by id. This list will be stored internally and
+all messages from the muted clients will be filtered client-side after
+receipt of each new message.
+
+
+
+ Firechat.prototype.toggleUserMute = function (userId, cb) {
+ var self = this ;
+
+ self._userRef.child('muted' ).child(userId).transaction(function (isMuted) {
+ return (isMuted) ? null : true ;
+ }, cb);
+ };
+
+
+
+
+
+
+
+
+
Send a moderator notification to a specific user.
+
+
+
+ Firechat.prototype.sendSuperuserNotification = function (userId, notificationType, data, cb) {
+ var self = this ,
+ userNotificationsRef = self._firebase.child('users' ).child(userId).child('notifications' );
+
+ userNotificationsRef.push({
+ fromUserId: self._userId,
+ timestamp: Firebase.ServerValue.TIMESTAMP,
+ notificationType: notificationType,
+ data: data || {}
+ }, cb);
+ };
+
+
+
+
+
+
+
+
+
Warn a user for violating the terms of service or being abusive.
+
+
+
+ Firechat.prototype.warnUser = function (userId) {
+ var self = this ;
+
+ self.sendSuperuserNotification(userId, 'warning' );
+ };
+
+
+
+
+
+
+
+
+
Suspend a user by putting the user into read-only mode for a period.
+
+
+
+ Firechat.prototype.suspendUser = function (userId, timeLengthSeconds, cb) {
+ var self = this ,
+ suspendedUntil = Firebase.ServerValue.TIMESTAMP + 1000 *timeLengthSeconds;
+
+ self._suspensionsRef.child(userId).set(suspendedUntil, function (error) {
+ if (error && cb) {
+ return cb(error);
+ } else {
+ self.sendSuperuserNotification(userId, 'suspension' , {
+ suspendedUntil: suspendedUntil
+ });
+ return cb(null );
+ }
+ });
+ };
+
+
+
+
+
+
+
+
+
Invite a user to a specific chat room.
+
+
+
+ Firechat.prototype.inviteUser = function (userId, roomId) {
+ var self = this ,
+ sendInvite = function () {
+ var inviteRef = self._firebase.child('users' ).child(userId).child('invites' ).push();
+ inviteRef.set({
+ id: inviteRef.name(),
+ fromUserId: self._userId,
+ fromUserName: self._userName,
+ toRoomId: roomId
+ });
+
+
+
+
+
+
+
+
+
Handle listen unauth / failure in case we're kicked.
+
+
+
+ inviteRef.on('value' , self._onFirechatInviteResponse, function (){}, self);
+ };
+
+ self.getRoom(roomId, function (room) {
+ if (room.type === 'private' ) {
+ var authorizedUserRef = self._roomRef.child(roomId).child('authorizedUsers' );
+ authorizedUserRef.child(userId).set(true , function (error) {
+ if (!error) {
+ sendInvite();
+ }
+ });
+ } else {
+ sendInvite();
+ }
+ });
+ };
+
+ Firechat.prototype.acceptInvite = function (inviteId, cb) {
+ var self = this ;
+
+ self._userRef.child('invites' ).child(inviteId).once('value' , function (snapshot) {
+ var invite = snapshot.val();
+ if (invite === null && cb) {
+ return cb(new Error('acceptInvite(' + inviteId + '): invalid invite id' ));
+ } else {
+ self.enterRoom(invite.toRoomId);
+ self._userRef.child('invites' ).child(inviteId).update({
+ 'status' : 'accepted' ,
+ 'toUserName' : self._userName
+ }, cb);
+ }
+ }, self);
+ };
+
+ Firechat.prototype.declineInvite = function (inviteId, cb) {
+ var self = this ,
+ updates = {
+ 'status' : 'declined' ,
+ 'toUserName' : self._userName
+ };
+
+ self._userRef.child('invites' ).child(inviteId).update(updates, cb);
+ };
+
+ Firechat.prototype.getRoomList = function (cb) {
+ var self = this ;
+
+ self._roomRef.once('value' , function (snapshot) {
+ cb(snapshot.val());
+ });
+ };
+
+ Firechat.prototype.getUsersByRoom = function () {
+ var self = this ,
+ roomId = arguments[0 ],
+ query = self._firebase.child('room-users' ).child(roomId),
+ cb = arguments[arguments.length - 1 ],
+ limit = null ;
+
+ if (arguments.length > 2 ) {
+ limit = arguments[1 ];
+ }
+
+ query = (limit) ? query.limit(limit) : query;
+
+ query.once('value' , function (snapshot) {
+ var usernames = snapshot.val() || {},
+ usernamesUnique = {};
+
+ for (var username in usernames) {
+ for (var session in usernames[username]) {
+
+
+
+
+
+
+
+
+
Skip all other sessions for this user as we only need one.
+
+
+
+ usernamesUnique[username] = usernames[username][session];
+ break ;
+ }
+ }
+
+ root.setTimeout(function () {
+ cb(usernamesUnique);
+ }, 0 );
+ });
+ };
+
+ Firechat.prototype.getUsersByPrefix = function (prefix, startAt, endAt, limit, cb) {
+ var self = this ,
+ query = this ._usersOnlineRef,
+ prefixLower = prefix.toLowerCase();
+
+ if (startAt) {
+ query = query.startAt(null , startAt);
+ } else if (endAt) {
+ query = query.endAt(null , endAt);
+ } else {
+ query = (prefixLower) ? query.startAt(null , prefixLower) : query.startAt();
+ }
+
+ query = (limit) ? query.limit(limit) : query;
+
+ query.once('value' , function (snapshot) {
+ var usernames = snapshot.val() || {},
+ usernamesFiltered = {};
+
+ for (var userNameKey in usernames) {
+ var sessions = usernames[userNameKey],
+ userName, userId, usernameClean;
+
+
+
+
+
+
+
+
+
Grab the user data from the first registered active session.
+
+
+
+ for (var sessionId in sessions) {
+ userName = sessions[sessionId].name;
+ userId = sessions[sessionId].id;
+
+
+
+
+
+
+
+
+
Skip all other sessions for this user as we only need one.
+
+
+
+
+
+
+
+
+
+
+
+
+
Filter out any usernames that don't match our prefix and break.
+
+
+
+ if ((prefix.length > 0 ) && (userName.toLowerCase().indexOf(prefixLower) !== 0 ))
+ continue ;
+
+ usernamesFiltered[userName] = {
+ name: userName,
+ id: userId
+ };
+ }
+
+ root.setTimeout(function () {
+ cb(usernamesFiltered);
+ }, 0 );
+ });
+ };
+
+
+
+
+
+
+
+
+
Miscellaneous helper methods.
+
+
+
+ Firechat.prototype.getRoom = function (roomId, callback) {
+ this ._roomRef.child(roomId).once('value' , function (snapshot) {
+ callback(snapshot.val());
+ });
+ };
+
+ Firechat.prototype.userIsModerator = function () {
+ return this ._isModerator;
+ };
+
+ Firechat.prototype.sessionIdGet = function () {
+ return this ._sessionId;
+ };
+})(Firebase);
+
+
+
+
+
+
+
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..f7ac3d3
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,271 @@
+---
+layout: docs
+permalink: index.html
+---
+
+
+### Overview
+
+Firechat is a simple, extensible chat widget powered by [Firebase](https://firebase.com/?utm_source=docs&utm_medium=site&utm_campaign=firechat).
+
+It is intended to serve as a concise, documented foundation for chat products built on Firebase. It works out of the box, and is easily extended. Fork the repo to start extending and customizing!
+
+
+
+### Getting Started
+
+Firechat works out of the box, provided that you include Firebase and Firechat in your application, and configure it to use your Firebase account.
+
+#### Prerequisites
+
+Before getting started, you'll need to:
+
+- [Download Firechat](https://github.com/firebase/firechat/archive/master.zip)
+- Create a Firebase account (it's free)
+
+#### Adding Dependencies
+
+To get up and running using the default interface, include the following before the `