├── .gitignore ├── public ├── images │ └── close.png ├── index.html ├── js │ ├── util.js │ ├── bootstrap │ │ └── bootstrap-modal.js │ ├── underscore-min.js │ ├── mustache.js │ └── backbone.js ├── app.css └── app.js ├── package.json ├── test ├── index.html ├── qunit.css └── qunit.js ├── LICENSE ├── README.md └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /public/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akavlie/web-irc/HEAD/public/images/close.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-irc", 3 | "description": "In-browser IRC client", 4 | "author": "Aaron Kavlie", 5 | "version": "0.0.1", 6 | "dependencies": { 7 | "express": "2.5.x", 8 | "irc": "0.3.x", 9 | "socket.io": "0.8.x" 10 | }, 11 | "bin": { 12 | "web-irc": "./server.js" 13 | }, 14 | "devDependencies": { 15 | }, 16 | "engine": "node >= 0.4.x" 17 | } 18 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

wIRC Test Suite

17 |

18 |
19 |

20 |
    21 |
    test markup, will be hidden
    22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2011 Aaron Kavlie 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Web-IRC 2 | ======= 3 | 4 | Note 5 | ---- 6 | 7 | I have stopped development on Web-IRC in favor of [Subway](https://github.com/thedjpetersen/subway), 8 | a joint effort with another githubber. If you are interested in a web-based IRC client, look there. 9 | 10 | https://github.com/thedjpetersen/subway 11 | 12 | ----- 13 | 14 | 15 | ### A web IRC client 16 | 17 | The goal for this project is to become the best in-browser IRC client available, 18 | and bring the best ideas from modern web applications to IRC. It was inspired by a [request for improvements to qwebirc](https://github.com/paulirish/lazyweb-requests/issues/31) 19 | by Paul Irish. 20 | 21 | Web-IRC is based on [node.js](http://nodejs.org/) and 22 | Martyn Smith's [node-irc](https://github.com/martynsmith/node-irc) on the backend, 23 | and [Backbone.js](http://documentcloud.github.com/backbone/) and 24 | [jQuery](http://jquery.com/) on the frontend. 25 | 26 | 27 | Status 28 | ------ 29 | 30 | The app is still in its early stages. Potential contributors should find plenty to do. 31 | 32 | Here's what works: 33 | 34 | - Choose nick/network/channel(s) to use at login 35 | - Join channels 36 | - Send messages to channels 37 | - Switch between channel tabs, see chat output 38 | - Leave channels 39 | - Private messages 40 | - Channel topics 41 | 42 | Here's (a partial list of) what doesn't work yet: 43 | 44 | - Status messages 45 | - Listing channels 46 | 47 | Design/UI/UX help also **desperately needed**. 48 | 49 | Installation 50 | ------------ 51 | 52 | 1. Assuming you already have node.js & npm, run: 53 | 54 | $ npm install -g web-irc 55 | 56 | 2. Launch the web server 57 | 58 | $ web-irc 59 | 60 | 3. Point your browser at `http://localhost:8337/` 61 | 62 | Development 63 | ----------- 64 | 65 | Replace step 1 above with this: 66 | 67 | $ git clone https://github.com/akavlie/web-irc 68 | $ cd web-irc 69 | $ npm link 70 | 71 | this should install dependencies, and link the git checkout to your global 72 | node_modules directory. 73 | 74 | Rationale 75 | --------- 76 | 77 | Web-based IRC clients are quite popular, particularly as an in-page embed for 78 | various open source projects and live shows. The ubiquitous choice at this time 79 | is the aforementioned [qwebirc](http://qwebirc.org/). 80 | 81 | Here are some popular sites that use (or link to) a web IRC client: 82 | 83 | - [jQuery](http://docs.jquery.com/Discussion) 84 | - [freenode](http://webchat.freenode.net/) 85 | - [TWiT](http://twit.tv/) 86 | 87 | 88 | License 89 | ------- 90 | 91 | MIT licensed. See `LICENSE`. 92 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var express = require('express'), 4 | app = express.createServer(), 5 | io = require('socket.io').listen(app), 6 | irc = require('irc'); 7 | 8 | app.configure(function() { 9 | app.use(app.router); 10 | app.use(express.static(__dirname + '/public')); 11 | }); 12 | 13 | app.configure('development', function() { 14 | app.listen(8337); 15 | }); 16 | 17 | app.configure('production', function() { 18 | app.listen(12445); 19 | }); 20 | 21 | app.get('/', function(req, res, next) { 22 | next(); 23 | }); 24 | 25 | console.log('web-irc started on port %s', app.address().port); 26 | 27 | 28 | // Socket.IO 29 | io.sockets.on('connection', function(socket) { 30 | 31 | // Events to signal TO the front-end 32 | var events = { 33 | 'join': ['channel', 'nick'], 34 | 'part': ['channel', 'nick'], 35 | 'topic': ['channel', 'topic', 'nick'], 36 | 'nick': ['oldNick', 'newNick', 'channels'], 37 | 'names': ['channel', 'nicks'], 38 | 'message': ['from', 'to', 'text'], 39 | 'pm': ['nick', 'text'], 40 | 'motd': ['motd'], 41 | 'error': ['message'] 42 | }; 43 | 44 | socket.on('connect', function(data) { 45 | var client = new irc.Client(data.server, data.nick, { 46 | debug: true, 47 | showErrors: true, 48 | channels: data.channels 49 | }); 50 | 51 | // Socket events sent FROM the front-end 52 | socket.on('join', function(name) { client.join(name); }); 53 | socket.on('part', function(name) { client.part(name); }); 54 | socket.on('say', function(data) { client.say(data.target, data.message); }); 55 | socket.on('command', function(text) { console.log(text); client.send(text); }); 56 | socket.on('disconnect', function() { client.disconnect(); }); 57 | 58 | 59 | // Add a listener on client for the given event & argument names 60 | var activateListener = function(event, argNames) { 61 | client.addListener(event, function() { 62 | console.log('Event ' + event + ' sent'); 63 | // Associate specified names with callback arguments 64 | // to avoid getting tripped up on the other side 65 | var callbackArgs = arguments; 66 | args = {}; 67 | argNames.forEach(function(arg, index) { 68 | args[arg] = callbackArgs[index]; 69 | }); 70 | console.log(args); 71 | socket.emit(event, args); 72 | }); 73 | }; 74 | 75 | for (var event in events) { activateListener(event, events[event]); } 76 | console.log('Starting IRC client; wiring up socket events.') 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | web-irc 5 | 6 | 7 | 8 | 9 | 10 | 11 |
    12 |
    13 | 14 |
    15 | 16 |
    17 | 21 |
    22 |
    23 |
    channel topic here
    24 |
    25 |
    26 |
    27 |
    28 | 29 |
    30 |
    31 | 32 | 63 | 64 | 65 | 69 | 70 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /public/js/util.js: -------------------------------------------------------------------------------- 1 | // make it safe to use console.log always 2 | (function(b){function c(){}for(var d="assert,clear,count,debug,dir,dirxml,error,exception,firebug,group,groupCollapsed,groupEnd,info,log,memoryProfile,memoryProfileEnd,profile,profileEnd,table,time,timeEnd,timeStamp,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try 3 | {console.log();return window.console;}catch(err){return window.console={};}})()); 4 | 5 | 6 | // POLYFILLS 7 | // ========= 8 | 9 | if(!String.prototype.trim) { 10 | String.prototype.trim = function () { 11 | return this.replace(/^\s+|\s+$/g,''); 12 | }; 13 | } 14 | 15 | // Production steps of ECMA-262, Edition 5, 15.4.4.18 16 | // Reference: http://es5.github.com/#x15.4.4.18 17 | if ( !Array.prototype.forEach ) { 18 | 19 | Array.prototype.forEach = function( callback, thisArg ) { 20 | 21 | var T, k; 22 | 23 | if ( this == null ) { 24 | throw new TypeError( " this is null or not defined" ); 25 | } 26 | 27 | // 1. Let O be the result of calling ToObject passing the |this| value as the argument. 28 | var O = Object(this); 29 | 30 | // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". 31 | // 3. Let len be ToUint32(lenValue). 32 | var len = O.length >>> 0; // Hack to convert O.length to a UInt32 33 | 34 | // 4. If IsCallable(callback) is false, throw a TypeError exception. 35 | // See: http://es5.github.com/#x9.11 36 | if ( {}.toString.call(callback) != "[object Function]" ) { 37 | throw new TypeError( callback + " is not a function" ); 38 | } 39 | 40 | // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. 41 | if ( thisArg ) { 42 | T = thisArg; 43 | } 44 | 45 | // 6. Let k be 0 46 | k = 0; 47 | 48 | // 7. Repeat, while k < len 49 | while( k < len ) { 50 | 51 | var kValue; 52 | 53 | // a. Let Pk be ToString(k). 54 | // This is implicit for LHS operands of the in operator 55 | // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. 56 | // This step can be combined with c 57 | // c. If kPresent is true, then 58 | if ( k in O ) { 59 | 60 | // i. Let kValue be the result of calling the Get internal method of O with argument Pk. 61 | kValue = O[ k ]; 62 | 63 | // ii. Call the Call internal method of callback with T as the this value and 64 | // argument list containing kValue, k, and O. 65 | callback.call( T, kValue, k, O ); 66 | } 67 | // d. Increase k by 1. 68 | k++; 69 | } 70 | // 8. return undefined 71 | }; 72 | } 73 | 74 | // UTILITY FUNCTIONS 75 | // ================= 76 | 77 | window.irc = (function(module) { 78 | module.util = { 79 | // Replaces oldString with newString at beginning of text 80 | swapCommand: function(oldString, newString, text) { 81 | if (text.substring(0, oldString.length) === oldString) 82 | return newString + text.substring(oldString.length, text.length); 83 | else 84 | throw 'String "' + oldString + '" not found at beginning of text'; 85 | }, 86 | escapeHTML: function(text) { 87 | return text.replace(/&/g,'&').replace(//g,'>'); 88 | } 89 | 90 | } 91 | 92 | return module 93 | })(window.irc || {}); 94 | -------------------------------------------------------------------------------- /public/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | padding: 0; 4 | margin: 0; 5 | overflow-y: hidden; 6 | } 7 | 8 | /* 9 | body { 10 | color: #444; 11 | font-family: arial; 12 | } 13 | */ 14 | 15 | ul { 16 | list-style-type: none; 17 | padding: 0; 18 | } 19 | 20 | #content { 21 | padding: 5px; 22 | display: none; 23 | } 24 | 25 | header { 26 | margin-bottom: 3px; 27 | } 28 | 29 | .frames { 30 | margin: 0; 31 | } 32 | 33 | #frame { 34 | position: relative; 35 | } 36 | 37 | #frame .wrapper { 38 | position: relative; 39 | } 40 | 41 | #output { 42 | font-size: 14px; 43 | position: relative; 44 | margin-bottom: 5px; 45 | border: 1px solid #888; 46 | } 47 | 48 | #output #topic { 49 | position: relative; 50 | min-height: 40px; 51 | padding: 5px; 52 | background-color: #e2e2e2; 53 | border-bottom: 1px solid #444; 54 | display: none; 55 | z-index: 2; 56 | } 57 | 58 | #output #messages { 59 | position: absolute; 60 | padding: 5px; 61 | overflow: auto; 62 | z-index: 10; 63 | top: 0; 64 | bottom: 0; 65 | left: 0; 66 | right: 0; 67 | } 68 | 69 | #frame .gutter { 70 | position: absolute; 71 | background-color: #e2e2e2; 72 | border-right: 1px solid #888; 73 | width: 110px; 74 | left: 0; 75 | top: 0; 76 | height: 100%; 77 | } 78 | 79 | #frame.status .gutter { 80 | display: none; 81 | } 82 | 83 | /* Special message types */ 84 | #output .error { 85 | background-color: red; 86 | } 87 | 88 | #output .sender { 89 | color: #444; 90 | display: block; 91 | float: left; 92 | width: 100px; 93 | text-align: right; 94 | z-index: 10; 95 | } 96 | 97 | #output .text { 98 | color: #222; 99 | display: block; 100 | margin-left: 115px; 101 | margin-bottom: 4px; 102 | } 103 | 104 | #frame.status .text.no-sender { 105 | margin-left: 0; 106 | } 107 | 108 | #sidebar { 109 | width: 200px; 110 | margin: 0 0 5px 5px; 111 | border: 1px solid #888; 112 | float: right; 113 | position: relative; 114 | display: none; 115 | } 116 | 117 | #sidebar .stats { 118 | position: relative; 119 | top: 0; 120 | height: 40px; 121 | background-color: #e2e2e2; 122 | border-bottom: 1px solid #888; 123 | display: none; 124 | } 125 | 126 | #sidebar .nicks { 127 | position: absolute; 128 | overflow: auto; 129 | padding: 5px; 130 | top: 0; 131 | /*top: 40px;*/ 132 | bottom: 0; 133 | left: 0; 134 | right: 0; 135 | } 136 | 137 | #prime-input { 138 | font-size: 15px; 139 | position: relative; 140 | width: 99%; 141 | bottom: 0; 142 | } 143 | 144 | /* Twitter Bootstrap overrides 145 | ----------------------------------------------------------------------*/ 146 | 147 | .pills > li { margin-right: 6px; } 148 | .pills > li > * { float: left; } 149 | 150 | .pills > li > a { 151 | cursor: pointer; 152 | background: #e2e2e2; 153 | padding: 0 12px; 154 | margin: 0 1px 0 0; 155 | border-radius: 3px; 156 | line-height: 26px; 157 | } 158 | .pills > li > a.channel, 159 | .pills > li > a.pm { 160 | border-radius: 3px 0 0 3px; 161 | } 162 | 163 | .pills > li > .close-frame { 164 | cursor: pointer; 165 | background: #e2e2e2 url(images/close.png) no-repeat center center; 166 | width: 18px; 167 | height: 26px; 168 | border-radius: 0 3px 3px 0; 169 | } 170 | 171 | .pills .active .close-frame { 172 | background-color: #0069d6; 173 | } 174 | 175 | .pills > li:hover > a, 176 | .pills > li:hover > .close-frame { 177 | color: white; 178 | text-shadow: 0; 179 | background-color: #00438a; 180 | } 181 | -------------------------------------------------------------------------------- /test/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit 1.2.0pre - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 15px 15px 0 0; 42 | -moz-border-radius: 15px 15px 0 0; 43 | -webkit-border-top-right-radius: 15px; 44 | -webkit-border-top-left-radius: 15px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-banner { 58 | height: 5px; 59 | } 60 | 61 | #qunit-testrunner-toolbar { 62 | padding: 0.5em 0 0.5em 2em; 63 | color: #5E740B; 64 | background-color: #eee; 65 | } 66 | 67 | #qunit-userAgent { 68 | padding: 0.5em 0 0.5em 2.5em; 69 | background-color: #2b81af; 70 | color: #fff; 71 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 72 | } 73 | 74 | 75 | /** Tests: Pass/Fail */ 76 | 77 | #qunit-tests { 78 | list-style-position: inside; 79 | } 80 | 81 | #qunit-tests li { 82 | padding: 0.4em 0.5em 0.4em 2.5em; 83 | border-bottom: 1px solid #fff; 84 | list-style-position: inside; 85 | } 86 | 87 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 88 | display: none; 89 | } 90 | 91 | #qunit-tests li strong { 92 | cursor: pointer; 93 | } 94 | 95 | #qunit-tests li a { 96 | padding: 0.5em; 97 | color: #c2ccd1; 98 | text-decoration: none; 99 | } 100 | #qunit-tests li a:hover, 101 | #qunit-tests li a:focus { 102 | color: #000; 103 | } 104 | 105 | #qunit-tests ol { 106 | margin-top: 0.5em; 107 | padding: 0.5em; 108 | 109 | background-color: #fff; 110 | 111 | border-radius: 15px; 112 | -moz-border-radius: 15px; 113 | -webkit-border-radius: 15px; 114 | 115 | box-shadow: inset 0px 2px 13px #999; 116 | -moz-box-shadow: inset 0px 2px 13px #999; 117 | -webkit-box-shadow: inset 0px 2px 13px #999; 118 | } 119 | 120 | #qunit-tests table { 121 | border-collapse: collapse; 122 | margin-top: .2em; 123 | } 124 | 125 | #qunit-tests th { 126 | text-align: right; 127 | vertical-align: top; 128 | padding: 0 .5em 0 0; 129 | } 130 | 131 | #qunit-tests td { 132 | vertical-align: top; 133 | } 134 | 135 | #qunit-tests pre { 136 | margin: 0; 137 | white-space: pre-wrap; 138 | word-wrap: break-word; 139 | } 140 | 141 | #qunit-tests del { 142 | background-color: #e0f2be; 143 | color: #374e0c; 144 | text-decoration: none; 145 | } 146 | 147 | #qunit-tests ins { 148 | background-color: #ffcaca; 149 | color: #500; 150 | text-decoration: none; 151 | } 152 | 153 | /*** Test Counts */ 154 | 155 | #qunit-tests b.counts { color: black; } 156 | #qunit-tests b.passed { color: #5E740B; } 157 | #qunit-tests b.failed { color: #710909; } 158 | 159 | #qunit-tests li li { 160 | margin: 0.5em; 161 | padding: 0.4em 0.5em 0.4em 0.5em; 162 | background-color: #fff; 163 | border-bottom: none; 164 | list-style-position: inside; 165 | } 166 | 167 | /*** Passing Styles */ 168 | 169 | #qunit-tests li li.pass { 170 | color: #5E740B; 171 | background-color: #fff; 172 | border-left: 26px solid #C6E746; 173 | } 174 | 175 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 176 | #qunit-tests .pass .test-name { color: #366097; } 177 | 178 | #qunit-tests .pass .test-actual, 179 | #qunit-tests .pass .test-expected { color: #999999; } 180 | 181 | #qunit-banner.qunit-pass { background-color: #C6E746; } 182 | 183 | /*** Failing Styles */ 184 | 185 | #qunit-tests li li.fail { 186 | color: #710909; 187 | background-color: #fff; 188 | border-left: 26px solid #EE5757; 189 | white-space: pre; 190 | } 191 | 192 | #qunit-tests > li:last-child { 193 | border-radius: 0 0 15px 15px; 194 | -moz-border-radius: 0 0 15px 15px; 195 | -webkit-border-bottom-right-radius: 15px; 196 | -webkit-border-bottom-left-radius: 15px; 197 | } 198 | 199 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 200 | #qunit-tests .fail .test-name, 201 | #qunit-tests .fail .module-name { color: #000000; } 202 | 203 | #qunit-tests .fail .test-actual { color: #EE5757; } 204 | #qunit-tests .fail .test-expected { color: green; } 205 | 206 | #qunit-banner.qunit-fail { background-color: #EE5757; } 207 | 208 | 209 | /** Result */ 210 | 211 | #qunit-testresult { 212 | padding: 0.5em 0.5em 0.5em 2.5em; 213 | 214 | color: #2b81af; 215 | background-color: #D2E0E6; 216 | 217 | border-bottom: 1px solid white; 218 | } 219 | 220 | /** Fixture */ 221 | 222 | #qunit-fixture { 223 | position: absolute; 224 | top: -10000px; 225 | left: -10000px; 226 | } 227 | -------------------------------------------------------------------------------- /public/js/bootstrap/bootstrap-modal.js: -------------------------------------------------------------------------------- 1 | /* ========================================================= 2 | * bootstrap-modal.js v1.4.0 3 | * http://twitter.github.com/bootstrap/javascript.html#modal 4 | * ========================================================= 5 | * Copyright 2011 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================= */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* CSS TRANSITION SUPPORT (https://gist.github.com/373874) 26 | * ======================================================= */ 27 | 28 | var transitionEnd 29 | 30 | $(document).ready(function () { 31 | 32 | $.support.transition = (function () { 33 | var thisBody = document.body || document.documentElement 34 | , thisStyle = thisBody.style 35 | , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined 36 | return support 37 | })() 38 | 39 | // set CSS transition event type 40 | if ( $.support.transition ) { 41 | transitionEnd = "TransitionEnd" 42 | if ( $.browser.webkit ) { 43 | transitionEnd = "webkitTransitionEnd" 44 | } else if ( $.browser.mozilla ) { 45 | transitionEnd = "transitionend" 46 | } else if ( $.browser.opera ) { 47 | transitionEnd = "oTransitionEnd" 48 | } 49 | } 50 | 51 | }) 52 | 53 | 54 | /* MODAL PUBLIC CLASS DEFINITION 55 | * ============================= */ 56 | 57 | var Modal = function ( content, options ) { 58 | this.settings = $.extend({}, $.fn.modal.defaults, options) 59 | this.$element = $(content) 60 | .delegate('.close', 'click.modal', $.proxy(this.hide, this)) 61 | 62 | if ( this.settings.show ) { 63 | this.show() 64 | } 65 | 66 | return this 67 | } 68 | 69 | Modal.prototype = { 70 | 71 | toggle: function () { 72 | return this[!this.isShown ? 'show' : 'hide']() 73 | } 74 | 75 | , show: function () { 76 | var that = this 77 | this.isShown = true 78 | this.$element.trigger('show') 79 | 80 | escape.call(this) 81 | backdrop.call(this, function () { 82 | var transition = $.support.transition && that.$element.hasClass('fade') 83 | 84 | that.$element 85 | .appendTo(document.body) 86 | .show() 87 | 88 | if (transition) { 89 | that.$element[0].offsetWidth // force reflow 90 | } 91 | 92 | that.$element.addClass('in') 93 | 94 | transition ? 95 | that.$element.one(transitionEnd, function () { that.$element.trigger('shown') }) : 96 | that.$element.trigger('shown') 97 | 98 | }) 99 | 100 | return this 101 | } 102 | 103 | , hide: function (e) { 104 | e && e.preventDefault() 105 | 106 | if ( !this.isShown ) { 107 | return this 108 | } 109 | 110 | var that = this 111 | this.isShown = false 112 | 113 | escape.call(this) 114 | 115 | this.$element 116 | .trigger('hide') 117 | .removeClass('in') 118 | 119 | $.support.transition && this.$element.hasClass('fade') ? 120 | hideWithTransition.call(this) : 121 | hideModal.call(this) 122 | 123 | return this 124 | } 125 | 126 | } 127 | 128 | 129 | /* MODAL PRIVATE METHODS 130 | * ===================== */ 131 | 132 | function hideWithTransition() { 133 | // firefox drops transitionEnd events :{o 134 | var that = this 135 | , timeout = setTimeout(function () { 136 | that.$element.unbind(transitionEnd) 137 | hideModal.call(that) 138 | }, 500) 139 | 140 | this.$element.one(transitionEnd, function () { 141 | clearTimeout(timeout) 142 | hideModal.call(that) 143 | }) 144 | } 145 | 146 | function hideModal (that) { 147 | this.$element 148 | .hide() 149 | .trigger('hidden') 150 | 151 | backdrop.call(this) 152 | } 153 | 154 | function backdrop ( callback ) { 155 | var that = this 156 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 157 | if ( this.isShown && this.settings.backdrop ) { 158 | var doAnimate = $.support.transition && animate 159 | 160 | this.$backdrop = $('