2013-02-13 22:04:35 +01:00
var tutorial = ( function ( ) {
var exports = { } ;
2013-02-13 22:15:13 +01:00
function go ( fn , arg ) {
return function ( ) { return fn ( arg ) ; } ;
}
function go2 ( fn , arg1 , arg2 ) {
return function ( ) { return fn ( arg1 , arg2 ) ; } ;
}
// Inspired by
// http://www.intridea.com/blog/2011/2/8/fun-with-jquery-deferred
//
// Basically, we make a new Deferred object that gets resolved
// when our setTimeout returns. Which means we effectively sleep
// for some amount of time, but can also chain it with .thens
// with our other deferred objects.
function sleep ( time _in _ms ) {
return $ . Deferred ( function ( deferred ) {
setTimeout ( deferred . resolve , time _in _ms ) ;
} ) ;
}
2013-03-05 21:04:02 +01:00
function pm ( message ) {
2013-02-13 22:15:13 +01:00
return $ . ajax ( {
dataType : 'json' ,
url : '/json/tutorial_send_message' ,
type : 'POST' ,
2013-03-05 19:57:38 +01:00
data : { 'type' : 'private' ,
'content' : message }
2013-02-13 22:15:13 +01:00
} ) ;
}
2013-03-05 21:04:02 +01:00
function stream _message ( subject , message ) {
return $ . ajax ( {
dataType : 'json' ,
url : '/json/tutorial_send_message' ,
type : 'POST' ,
data : { 'type' : 'stream' ,
'subject' : subject ,
'content' : message }
} ) ;
}
// populated later -- should be e.g. tutorial-wdaher
var my _tutorial _stream ;
function stream _to _me ( message ) {
return message . type === 'stream' && message . to === my _tutorial _stream ;
}
function pm _to _me ( message ) {
return message . type === 'private' && message . to [ 0 ] === 'humbug+tutorial@humbughq.com' ;
}
function any _message _to _me ( message ) {
return pm _to _me ( message ) || stream _to _me ( message ) ;
}
2013-02-13 22:15:13 +01:00
var received _messages = [ ] ;
exports . message _was _sent = function ( message ) {
var trimmed _content = message . content . trim ( ) . toLowerCase ( ) ;
2013-03-05 21:04:02 +01:00
if ( any _message _to _me ( message ) &&
( trimmed _content === 'exit' || trimmed _content === 'stop' ) ) {
2013-03-06 22:41:27 +01:00
sleep ( 1000 ) . then ( function ( ) {
var text = "OK, cool, we'll stop the tutorial here. If you have any questions, you can always email support@humbughq.com!" ;
if ( pm _to _me ( message ) ) {
pm ( text ) ;
} else {
stream _message ( message . subject , text ) ;
}
exports . stop ( ) ;
} ) ;
2013-02-13 22:15:13 +01:00
return ;
}
received _messages . push ( message ) ;
} ;
function wait _for _message ( time _to _wait _sec , condition ) {
var POLL _INTERVAL _MS = 500 ;
received _messages = [ ] ;
return $ . Deferred ( function ( deferred ) {
var numCalls = 0 ;
var intervalId = setInterval ( function ( ) {
numCalls += 1 ;
2013-03-07 21:14:44 +01:00
if ( numCalls > time _to _wait _sec * ( 1000 / POLL _INTERVAL _MS ) ) {
2013-02-13 22:15:13 +01:00
clearInterval ( intervalId ) ;
2013-03-04 04:06:36 +01:00
// We didn't get an answer; end the tutorial.
2013-03-07 21:14:44 +01:00
var signoff _message = "**I didn't hear from you, so I stopped waiting** :broken_heart:\n\nSince we're done, I've also removed you from `" + my _tutorial _stream + "`.\n\nEnjoy using Humbug, and let us know if you have any questions -- if you click the gear at the top-right, there's an option labeled 'Feedback', which is a great way to reach us." ;
stream _message ( "tutorial" , signoff _message ) . then ( function ( ) {
// This needs to be in a 'then' because otherwise we unsub
// before the message arrives!
exports . stop ( ) ;
deferred . fail ( ) ;
} ) ;
2013-02-13 22:15:13 +01:00
}
if ( received _messages . length > 0 ) {
var success = true ;
if ( condition ) {
success = false ;
$ . each ( received _messages , function ( idx , val ) {
if ( condition ( val ) ) success = true ;
} ) ;
}
received _messages = [ ] ;
if ( success ) {
deferred . resolve ( ) ;
clearInterval ( intervalId ) ;
}
}
} , POLL _INTERVAL _MS ) ;
} ) ;
}
var script = [ ] ;
function make _script ( ) {
2013-03-25 23:26:14 +01:00
my _tutorial _stream = 'tutorial-' + page _params . email . split ( '@' ) [ 0 ] ;
2013-03-20 01:16:41 +01:00
my _tutorial _stream = my _tutorial _stream . substring ( 0 , 30 ) ;
2013-03-05 21:04:02 +01:00
2013-02-13 22:15:13 +01:00
// Try to guess at one of your main streams.
// This is problematic because it might end up being 'commits' or something.
2013-03-25 23:26:14 +01:00
var main _stream _name = page _params . domain . split ( '.' ) [ 0 ] ;
2013-02-13 22:15:13 +01:00
var my _streams = subs . subscribed _streams ( ) ;
if ( my _streams . length <= 2 ) {
// What?? Add some more examples, I guess.
if ( $ . inArray ( 'social' , my _streams ) === - 1 ) my _streams . push ( 'social' ) ;
if ( $ . inArray ( 'commits' , my _streams ) === - 1 ) my _streams . push ( 'commits' ) ;
if ( $ . inArray ( 'jenkins' , my _streams ) === - 1 ) my _streams . push ( 'jenkins' ) ;
}
if ( $ . inArray ( main _stream _name , my _streams ) === - 1 ) {
main _stream _name = my _streams [ 0 ] ;
}
2013-03-05 21:04:02 +01:00
// Special hack for CUSTOMER18 -- if there's a stream named customer18stream1, well, then
// that's definitely the one to pick
if ( $ . inArray ( 'customer18stream1' , my _streams ) !== - 1 ) {
main _stream _name = 'customer18stream1' ;
}
2013-02-13 22:15:13 +01:00
script = [
go ( sleep , 1000 ) , // The first message seems to sometimes get eaten in Chrome otherwise.
2013-03-25 23:12:45 +01:00
go2 ( stream _message , "tutorial" , "Hello " + page _params . fullname + ", and welcome to Humbug!" ) ,
2013-02-13 22:15:13 +01:00
go ( sleep , 2000 ) ,
2013-03-05 21:04:02 +01:00
go2 ( stream _message , "tutorial" , "I'm the Humbug tutorial bot and I'll be showing you around." ) ,
2013-02-13 22:15:13 +01:00
go ( sleep , 2000 ) ,
2013-03-05 21:04:02 +01:00
go2 ( stream _message , "tutorial" , 'At any time, you can stop this tutorial by replying to me with the word "exit".' ) ,
2013-02-13 22:15:13 +01:00
go ( sleep , 3000 ) ,
2013-03-05 21:04:02 +01:00
go2 ( stream _message , "tutorial" , "Why don't you **reply to this message and say hello?** "
2013-03-25 23:12:45 +01:00
+ "(Click on this message to reply.)" ) ,
2013-03-05 21:04:02 +01:00
go2 ( wait _for _message , 300 , any _message _to _me ) ,
2013-02-13 22:15:13 +01:00
go ( sleep , 1000 ) ,
2013-03-05 21:04:02 +01:00
go2 ( stream _message , "tutorial" , 'Great, thanks! After you\'ve typed your reply, you can send it by clicking "Send", but you can also send from the keyboard by pressing `Tab` and then `Enter`.' ) ,
2013-02-13 22:15:13 +01:00
go ( sleep , 4000 ) ,
2013-03-05 21:04:02 +01:00
go2 ( stream _message , "tutorial" , "Give it a shot!\n**Reply to me again, and use Tab, then Enter to send it.**" ) ,
go2 ( wait _for _message , 300 , any _message _to _me ) ,
2013-02-13 22:15:13 +01:00
go ( sleep , 1000 ) ,
2013-03-25 23:12:45 +01:00
go2 ( stream _message , "tutorial" , "Nice work. In Humbug, time flows down and your new messages will always appear at the very bottom of the screen. We're always receiving messages for you—even when you're logged out." ) ,
2013-03-05 21:04:02 +01:00
go ( sleep , 6000 ) ,
2013-03-25 23:12:45 +01:00
go2 ( stream _message , "tutorial" , "By the way, right now, these messages are going to stream `" + my _tutorial _stream + "`.\n\nA stream is like a chatroom or mailing list; anyone on `" + my _tutorial _stream + "` can see and respond to these messages. (In this case, it's just us on this stream right now, so that no one distracts us)" ) ,
2013-03-05 21:04:02 +01:00
go ( sleep , 8000 ) ,
go2 ( stream _message , "tutorial" , "Every stream message has a subject associated with it. (In this case, `tutorial`). "
2013-03-25 23:12:45 +01:00
+ "The subject should be **one or two words** describing the topic of the message.\n\nGood subjects: `lunch`, `website redesign` or `Bug #4567`.\n" ) ,
2013-02-13 22:15:13 +01:00
go ( sleep , 10000 ) ,
2013-03-25 23:12:45 +01:00
go2 ( stream _message , "tutorial" , "Subjects sound like a tiny idea, but they are really powerful. They make it easy to keep track of multiple conversations and to easily skim what you care about and ignore what you don't.\n\n**Send me a reply** when you're ready to continue." ) ,
2013-03-05 21:04:02 +01:00
go2 ( wait _for _message , 300 , any _message _to _me ) ,
2013-02-13 22:15:13 +01:00
go ( sleep , 1000 ) ,
2013-03-25 23:12:45 +01:00
go ( pm , "Psst, Humbug also has private messages, like this one, which you can send to one or more people. No one else can see this message.\n\n**Reply to this private message** to continue." ) ,
2013-03-05 21:04:02 +01:00
go2 ( wait _for _message , 300 , pm _to _me ) ,
2013-02-13 22:15:13 +01:00
go ( sleep , 1000 ) ,
2013-03-05 21:04:02 +01:00
go ( pm , "Nicely done. Alright, back to stream messages we go!" ) ,
go ( sleep , 2000 ) ,
2013-03-25 23:12:45 +01:00
go2 ( stream _message , "tutorial" , "It's easy to make or join streams. If you click the gear on the top right of the page, and then pick 'Streams', you can create your own stream or join streams that other people have made." ) ,
2013-03-05 21:04:02 +01:00
go ( sleep , 4000 ) ,
2013-02-13 22:15:13 +01:00
// Narrowing
2013-03-25 23:12:45 +01:00
go2 ( stream _message , "narrowing" , "Another valuable feature of Humbug is **narrowing**. Click on the word \"narrowing\" directly above this message, and **tell me when you've done so**." ) ,
2013-03-05 21:04:02 +01:00
go2 ( wait _for _message , 300 , any _message _to _me ) ,
2013-02-13 22:15:13 +01:00
go ( sleep , 1000 ) ,
2013-03-25 22:37:55 +01:00
go2 ( stream _message , "narrowing" , "Great! We're now only looking at messages on stream `" + my _tutorial _stream + "`, subject `narrowing`. You can tell because the background is grey, and the search bar at the top has a query in it. You can narrow on:\n"
2013-03-05 21:04:02 +01:00
+ "* A specific stream, by clicking on the stream name\n"
+ "* A specific stream-subject pair, by clicking on the subject name (like we just did)\n"
+ "* Private messages with a specific person\n\n"
2013-03-25 23:12:45 +01:00
+ "Press `Esc` to get out of this narrowed view, scroll down to the bottom, and **tell me when you've done so**.\n" ) ,
2013-03-05 21:04:02 +01:00
go2 ( wait _for _message , 300 , any _message _to _me ) ,
2013-02-13 22:15:13 +01:00
go ( sleep , 1000 ) ,
2013-03-25 23:12:45 +01:00
go2 ( stream _message , "tutorial" , "Great, you've got the hang of the basics. I'll say goodbye for now, but here are some Humbug features you might like to explore:\n"
2013-02-13 22:15:13 +01:00
+ "* Keyboard shortcuts (press `?` to see them)\n"
2013-03-25 22:37:55 +01:00
+ "* Message formatting, including pretty syntax-highlighting. Click on the 'Message formatting' link under the gear icon at the top right to learn more\n"
2013-03-25 23:12:45 +01:00
+ "* Our [API](https://humbughq.com/api) and [integrations](https://humbughq.com/integrations)\n"
+ '* Alpha mobile apps for [Android](https://play.google.com/store/apps/details?id=com.humbughq.mobile) and [iPhone](mailto:support@humbughq.com?subject=Request+for+Humbug+iPhone+app&body=Hi+Humbug,+can+you+send+me+a+link+to+the+iPhone+app+alpha?+I+have+an+iPhone+__.)\n'
2013-03-25 22:37:55 +01:00
+ "* Feedback! Found a bug or have a feature request? We want to hear from you. Click on the feedback tab under the gear icon to get in touch." ) ,
2013-03-05 21:04:02 +01:00
go ( sleep , 4000 ) ,
2013-02-13 22:15:13 +01:00
// Have them go talk to people
2013-03-05 21:04:02 +01:00
go2 ( stream _message , "tutorial" , ":white_check_mark: **Congratulations! The tutorial is now complete** :tada:\n"
2013-03-25 23:12:45 +01:00
+ "Since you're done, we've removed you from the `" + my _tutorial _stream + "` stream.\n\n"
2013-03-05 21:04:02 +01:00
+ "Some things you can do from here:\n"
+ "* Send a private message to someone by clicking their name in the right sidebar\n"
+ "* Send a new stream message, or reply to an existing one\n"
+ "(One suggestion: A message to stream `" + main _stream _name + "` with subject `humbug` to let everyone know you're here!)\n" ) ,
function ( ) { exports . stop ( ) ; }
2013-02-13 22:15:13 +01:00
] ;
}
var tutorial _running = false ;
function run _tutorial ( stepNumber ) {
if ( stepNumber >= script . length ) {
exports . stop ( ) ;
}
if ( ! tutorial _running ) {
return ;
}
var step = script [ stepNumber ] ;
var res = step ( ) ;
if ( res ) {
res . then ( function ( ) { run _tutorial ( ++ stepNumber ) ; } ) ;
} else {
// Our last function wasn't async at all, so just proceed with the next step
run _tutorial ( ++ stepNumber ) ;
}
}
2013-03-05 21:04:02 +01:00
function add _to _tutorial _stream ( ) {
if ( $ . inArray ( my _tutorial _stream , subs . subscribed _streams ( ) ) === - 1 ) {
subs . tutorial _subscribe _or _add _me _to ( my _tutorial _stream ) ;
}
}
2013-02-13 22:15:13 +01:00
exports . start = function ( ) {
if ( tutorial _running ) {
// Not more than one of these at once!
return ;
}
tutorial _running = true ;
2013-03-05 21:04:02 +01:00
add _to _tutorial _stream ( ) ;
2013-02-13 22:15:13 +01:00
run _tutorial ( 0 ) ;
} ;
// This technique is not actually that awesome, because it's pretty
// race-y. Let's say I start() and then stop() and then start() again;
// an async from the first start() might still be returning, and it'll
// come back and say "Oh, we're still going!" and the tutorial #1 will
// continue.
//
// Fortunately, in v1, the only real thing that initiates the tutorial
// is logging in to a fresh system, so this is mostly a non-issue;
// the rationale being that you could just scroll back up and read
// the tutorial if you still need it.
exports . stop = function ( ) {
2013-03-05 21:04:02 +01:00
if ( tutorial _running ) {
subs . tutorial _unsubscribe _me _from ( my _tutorial _stream ) ;
tutorial _running = false ;
}
2013-02-13 22:15:13 +01:00
} ;
exports . is _running = function ( ) {
return tutorial _running ;
} ;
exports . initialize = function ( ) {
make _script ( ) ;
2013-03-12 04:54:17 +01:00
// Global variable populated by the server code
2013-03-25 23:26:14 +01:00
if ( page _params . needs _tutorial ) {
2013-03-07 21:14:44 +01:00
exports . start ( ) ;
2013-02-13 22:15:13 +01:00
}
} ;
2013-02-13 22:04:35 +01:00
return exports ;
} ( ) ) ;