2013-02-13 22:04:35 +01:00
var tutorial = ( function ( ) {
var exports = { } ;
2013-06-20 18:06:31 +02:00
var is _running = false ;
2013-07-02 19:58:44 +02:00
var event _handlers = { } ;
2013-08-09 23:02:43 +02:00
var deferred _work = [ ] ;
2013-02-13 22:15:13 +01:00
2013-06-27 22:18:03 +02:00
// We'll temporarily set stream colors for the streams we use in the demo
// tutorial messages.
var real _stream _info ;
2013-08-19 20:46:27 +02:00
var tutorial _stream _info = Dict . from ( { "design" : { "color" : "#76ce90" } ,
"social" : { "color" : "#fae589" } ,
"devel" : { "color" : "#a6c7e5" } } ) ;
2013-06-27 22:18:03 +02:00
// Each message object contains the minimal information necessary for it to be
// processed by our system for adding messages to your feed.
2013-07-02 21:35:51 +02:00
var today = new Date ( ) . getTime ( ) / 1000 ;
2013-06-27 22:18:03 +02:00
var fake _messages = [
{
id : 1 ,
content : "<p>We're working on some new screenshots for our landing page, and I'll show you what I have shortly.</p>" ,
is _stream : true ,
sender _full _name : "Waseem Daher" ,
avatar _url : "https://secure.gravatar.com/avatar/364a79a57718ede3fadf6dd3623d2e0a" ,
display _recipient : "design" ,
stream : "design" ,
subject : "screenshots" ,
timestr : "12:11" ,
2013-07-02 21:35:51 +02:00
timestamp : today ,
2013-06-27 22:18:03 +02:00
type : "stream"
} ,
{
id : 2 ,
content : "<p>Hey, if I work on Windows, can you two make one for Mac and Linux?</p>" ,
is _stream : false ,
sender _full _name : "Waseem Daher" ,
2013-07-11 16:56:58 +02:00
sender _email : "wdaher@zulip.com" ,
2013-06-27 22:18:03 +02:00
avatar _url : "https://secure.gravatar.com/avatar/364a79a57718ede3fadf6dd3623d2e0a" ,
display _reply _to : "Jeff Arnold, Waseem Daher" ,
reply _to : true ,
timestr : "12:12" ,
2013-07-02 21:35:51 +02:00
timestamp : today ,
2013-06-27 22:18:03 +02:00
type : "private"
} ,
{
id : 3 ,
content : "<p>Sure, no problem <img alt=':+1:' class='emoji' src='static/third/gemoji/images/emoji/+1.png' title=':+1:'></p>" ,
is _stream : false ,
sender _full _name : "Jessica McKellar" ,
2013-07-11 16:56:58 +02:00
sender _email : "jesstess@zulip.com" ,
2013-06-27 22:18:03 +02:00
avatar _url : "https://secure.gravatar.com/avatar/c89814a8ed5814421b617cf2242ff01a" ,
display _reply _to : "Jeff Arnold, Waseem Daher" ,
reply _to : true ,
timestr : "12:12" ,
2013-07-02 21:35:51 +02:00
timestamp : today ,
2013-06-27 22:18:03 +02:00
type : "private"
} ,
{
id : 4 ,
2013-08-07 19:49:18 +02:00
content : "<p>Ok, here's my <a href='https://zulip.com/static/images/app-screenshots/zulip-desktop-windows.png' target='_blank' title='https://zulip.com/static/images/app-screenshots/zulip-desktop-windows.png'>latest version</a><div class='message_inline_image'><a href='https://zulip.com/static/images/app-screenshots/zulip-desktop-windows.png' target='_blank' title='https://zulip.com/static/images/app-screenshots/zulip-desktop-windows.png'><img src='https://zulip.com/static/images/app-screenshots/zulip-desktop-windows.png'></a></div> for the Windows app -- thoughts? I'm particularly wondering whether people think the screenshot should be from Windows 7 or some other version.</p>" ,
2013-06-27 22:18:03 +02:00
is _stream : true ,
sender _full _name : "Waseem Daher" ,
avatar _url : "https://secure.gravatar.com/avatar/364a79a57718ede3fadf6dd3623d2e0a" ,
display _recipient : "design" ,
stream : "design" ,
subject : "screenshots" ,
timestr : "12:15" ,
2013-07-02 21:35:51 +02:00
timestamp : today ,
2013-06-27 22:18:03 +02:00
type : "stream"
} ,
{
id : 5 ,
content : "<p>Looks good to me!</p>" ,
is _stream : true ,
sender _full _name : "Jeff Arnold" ,
avatar _url : "https://secure.gravatar.com/avatar/0e0080b53f71bb975c311a123acd8a48" ,
display _recipient : "design" ,
stream : "design" ,
subject : "screenshots" ,
timestr : "12:15" ,
2013-07-02 21:35:51 +02:00
timestamp : today ,
2013-06-27 22:18:03 +02:00
type : "stream"
} ,
{
id : 6 ,
content : "<p>@<strong>all</strong> Any interest in lunch? I'd go for:</p>" +
"<ul>" +
"<li><img alt=':pizza:' class='emoji' src='static/third/gemoji/images/emoji/pizza.png' title=':pizza:'> (Hi-fi)</li>" +
"<li><img alt=':hamburger:' class='emoji' src='static/third/gemoji/images/emoji/hamburger.png' title=':hamburger:'> (Four Burgers)</li>" +
"<li><img alt=':sushi:' class='emoji' src='static/third/gemoji/images/emoji/sushi.png' title=':sushi:'> (Thelonious)</li>" +
"</ul></p>" ,
is _stream : true ,
sender _full _name : "Tim Abbott" ,
avatar _url : "https://secure.gravatar.com/avatar/364a79a57718ede3fadf6dd3623d2e0a" ,
display _recipient : "social" ,
stream : "social" ,
subject : "lunch" ,
timestr : "12:20" ,
2013-07-02 21:35:51 +02:00
timestamp : today ,
2013-06-27 22:18:03 +02:00
type : "stream"
} ,
{
id : 7 ,
content : "<p>I'd go to Hi-fi</p>" ,
is _stream : true ,
sender _full _name : "Luke Faraone" ,
avatar _url : "https://secure.gravatar.com/avatar/948fcdfa93dd8986106032f1bad7f2c8" ,
display _recipient : "social" ,
stream : "social" ,
subject : "lunch" ,
timestr : "12:20" ,
2013-07-02 21:35:51 +02:00
timestamp : today ,
2013-06-27 22:18:03 +02:00
type : "stream"
} ,
{
id : 8 ,
content : "<p>Reminder: engineering meeting in 1 hour.</p>" ,
is _stream : true ,
sender _full _name : "Jessica McKellar" ,
avatar _url : "https://secure.gravatar.com/avatar/c89814a8ed5814421b617cf2242ff01a" ,
display _recipient : "devel" ,
stream : "devel" ,
subject : "meeting" ,
timestr : "12:34" ,
2013-07-02 21:35:51 +02:00
timestamp : today ,
2013-06-27 22:18:03 +02:00
type : "stream"
}
] ;
2014-01-09 21:00:01 +01:00
function hide _app _alert ( ) {
2013-09-19 16:30:08 +02:00
$ ( '#alert-bar-container' ) . slideUp ( 100 ) ;
}
2014-01-09 21:00:01 +01:00
function show _app _alert ( contents ) {
$ ( '#custom-alert-bar-content' ) . html ( contents ) ;
2013-09-19 16:30:08 +02:00
$ ( '#alert-bar-container' ) . show ( ) ;
2014-01-09 21:00:01 +01:00
$ ( '#alert-bar-container .close-alert-icon' ) . expectOne ( ) . click ( hide _app _alert ) ;
2013-09-19 16:30:08 +02:00
}
2013-07-02 19:58:44 +02:00
function disable _event _handlers ( ) {
$ ( 'body' ) . css ( { 'overflow' : 'hidden' } ) ; // prevents scrolling the feed
2013-07-30 00:35:44 +02:00
_ . each ( [ "keydown" , "keyup" , "keypress" , "scroll" ] , function ( event _name ) {
2013-07-02 19:58:44 +02:00
var existing _events = $ ( document ) . data ( "events" ) [ event _name ] ;
if ( existing _events === undefined ) {
existing _events = [ ] ;
}
event _handlers [ event _name ] = existing _events ;
$ ( document ) . data ( "events" ) [ event _name ] = [ ] ;
} ) ;
}
function enable _event _handlers ( ) {
$ ( 'body' ) . css ( { 'overflow' : 'auto' } ) ; // enables scrolling the feed
2013-07-30 00:35:44 +02:00
_ . each ( [ "keydown" , "keyup" , "keypress" , "scroll" ] , function ( event _name ) {
2013-07-03 22:58:15 +02:00
$ ( document ) . data ( "events" ) [ event _name ] = event _handlers [ event _name ] ;
2013-07-02 19:58:44 +02:00
} ) ;
}
2013-04-12 21:33:00 +02:00
function set _tutorial _status ( status , callback ) {
2013-12-18 19:55:18 +01:00
return channel . post ( {
2013-06-20 18:06:31 +02:00
url : '/json/tutorial_status' ,
data : { status : status } ,
success : callback
2013-04-12 21:33:00 +02:00
} ) ;
}
2013-07-05 17:43:56 +02:00
exports . is _running = function ( ) {
2013-06-20 18:06:31 +02:00
return is _running ;
2013-02-13 22:15:13 +01:00
} ;
2013-06-27 22:18:28 +02:00
function box ( x , y , width , height ) {
// Blanket everything ouside the box defined by the parameters in a
// translucent black screen, and cover the box itself with a clear screen so
// nothing in it is clickable.
//
// x and y are the coordinates for hte upper-left corner of the box.
var doc _width = $ ( document ) . width ( ) ;
var doc _height = $ ( document ) . height ( ) ;
$ ( "#top-screen" ) . css ( { opacity : 0.7 , width : doc _width , height : y } ) ;
$ ( "#bottom-screen" ) . offset ( { top : y + height , left : 0 } ) ;
$ ( "#bottom-screen" ) . css ( { opacity : 0.7 , width : doc _width , height : doc _height } ) ;
$ ( "#left-screen" ) . offset ( { top : y , left : 0 } ) ;
$ ( "#left-screen" ) . css ( { opacity : 0.7 , width : x , height : height } ) ;
$ ( "#right-screen" ) . offset ( { top : y , left : x + width } ) ;
$ ( "#right-screen" ) . css ( { opacity : 0.7 , width : x , height : height } ) ;
$ ( "#clear-screen" ) . css ( { opacity : 0.0 , width : doc _width , height : doc _height } ) ;
}
function messages _in _viewport ( ) {
var vp = viewport . message _viewport _info ( ) ;
var top = vp . visible _top ;
var height = vp . visible _height ;
var last _row = rows . last _visible ( ) ;
return $ . merge ( last _row , last _row . prevAll ( ) ) . filter ( function ( idx , row ) {
var row _offset = $ ( row ) . offset ( ) ;
return ( row _offset . top > top && row _offset . top < top + height ) ;
} ) ;
}
2013-07-10 16:42:54 +02:00
function small _window ( ) {
2013-08-27 20:31:12 +02:00
return ! $ ( "#left-sidebar" ) . is ( ":visible" ) ;
2013-07-10 16:42:54 +02:00
}
function maybe _tweak _placement ( placement ) {
// If viewed on a small screen, move popovers on the left to the center so
// they won't be cut off.
if ( ! small _window ( ) ) {
return placement ;
}
if ( placement === "left" ) {
return "bottom" ;
}
if ( placement === "bottom" ) {
return "right" ;
}
}
2013-06-27 22:18:28 +02:00
function create _and _show _popover ( target _div , placement , title , content _template ) {
target _div . popover ( {
placement : placement ,
2013-08-27 20:31:12 +02:00
title : templates . render ( "tutorial_title" , { title : title ,
placement : placement } ) ,
content : templates . render ( content _template , { placement : placement } ) ,
2013-06-27 22:18:28 +02:00
trigger : "manual"
} ) ;
target _div . popover ( "show" ) ;
$ ( ".popover" ) . css ( "z-index" , 20001 ) ;
2013-08-27 20:31:12 +02:00
$ ( ".popover-title" ) . addClass ( "popover-" + placement ) ;
2013-06-27 22:18:28 +02:00
}
2013-08-09 23:02:43 +02:00
exports . defer = function ( callback ) {
deferred _work . push ( callback ) ;
} ;
2013-06-27 22:18:28 +02:00
function finale ( ) {
var finale _modal = $ ( "#tutorial-finale" ) ;
$ ( ".screen" ) . css ( { opacity : 0.0 } ) ;
finale _modal . css ( "z-index" , 20001 ) ;
finale _modal . modal ( "show" ) ;
$ ( "#tutorial-get-started" ) . click ( function ( ) {
finale _modal . modal ( "hide" ) ;
$ ( ".screen" ) . css ( { opacity : 0.0 , width : 0 , height : 0 } ) ;
2014-01-07 21:07:15 +01:00
} ) . focus ( ) ;
2013-06-27 22:18:28 +02:00
// Restore your actual stream colors and rerender to display any
// messages received during the tutorial.
set _tutorial _status ( "finished" ) ;
is _running = false ;
current _msg _list . clear ( ) ;
// Force a check on new events before we re-render the message list.
force _get _updates ( ) ;
2013-08-15 21:11:07 +02:00
stream _data . set _stream _info ( real _stream _info ) ;
2013-06-27 22:18:28 +02:00
util . show _first _run _message ( ) ;
current _msg _list . rerender ( ) ;
2013-07-02 19:58:44 +02:00
enable _event _handlers ( ) ;
2013-08-09 23:02:43 +02:00
_ . each ( deferred _work , function ( callback ) {
callback ( ) ;
} ) ;
deferred _work = [ ] ;
2014-01-09 22:53:18 +01:00
var alert _contents ;
if ( page _params . prompt _for _invites ) {
alert _contents = "<i class='icon-vector-heart alert-icon'></i>It's lonely in here! <a href='#invite-user' data-toggle='modal'>Invite some coworkers</a>." ;
} else {
alert _contents = "<i class='icon-vector-desktop alert-icon'></i> Have you heard: the <a href='/apps' target='_blank'>Zulip desktop app</a> is awesome and you should get it!" ;
}
2014-01-09 21:00:01 +01:00
show _app _alert ( alert _contents ) ;
2013-06-27 22:18:28 +02:00
}
function reply ( ) {
var spotlight _message = rows . first _visible ( ) . prev ( ".recipient_row" ) ;
2013-07-10 16:42:54 +02:00
create _and _show _popover ( spotlight _message , maybe _tweak _placement ( "left" ) ,
"Replying" , "tutorial_reply" ) ;
2013-06-27 22:18:28 +02:00
var my _popover = $ ( "#tutorial-reply" ) . closest ( ".popover" ) ;
my _popover . offset ( { left : my _popover . offset ( ) . left - 10 } ) ;
$ ( "#tutorial-reply-next" ) . click ( function ( ) {
spotlight _message . popover ( "destroy" ) ;
finale ( ) ;
2014-01-07 21:07:15 +01:00
} ) . focus ( ) ;
2013-06-27 22:18:28 +02:00
}
function home ( ) {
var spotlight _message = rows . first _visible ( ) . prev ( ".recipient_row" ) ;
var x = spotlight _message . offset ( ) . left ;
var y = spotlight _message . offset ( ) . top ;
var height = 0 ;
2013-07-30 00:35:44 +02:00
_ . each ( messages _in _viewport ( ) , function ( row ) {
2013-06-27 22:18:28 +02:00
height += $ ( row ) . height ( ) ;
} ) ;
box ( x , y , spotlight _message . width ( ) , height ) ;
2013-07-10 16:42:54 +02:00
create _and _show _popover ( spotlight _message , maybe _tweak _placement ( "left" ) ,
"Home view" , "tutorial_home" ) ;
2013-06-27 22:18:28 +02:00
var my _popover = $ ( "#tutorial-home" ) . closest ( ".popover" ) ;
my _popover . offset ( { left : my _popover . offset ( ) . left - 10 } ) ;
$ ( "#tutorial-home-next" ) . click ( function ( ) {
spotlight _message . popover ( "destroy" ) ;
reply ( ) ;
2014-01-07 21:07:15 +01:00
} ) . focus ( ) ;
2013-06-27 22:18:28 +02:00
}
function private _message ( ) {
var bar = rows . first _visible ( ) . nextUntil ( ".private_message" ) . first ( ) . next ( ) ;
var spotlight _message = bar . next ( ) ;
var x = bar . offset ( ) . left ;
var y = bar . offset ( ) . top ;
// In the current example we have back-to-back pms.
var message _width = bar . width ( ) ;
var message _height = bar . height ( ) + spotlight _message . height ( ) +
spotlight _message . next ( ) . height ( ) ;
box ( x , y , message _width , message _height ) ;
create _and _show _popover ( bar , "top" , "Private messages" , "tutorial_private" ) ;
var my _popover = $ ( "#tutorial-private" ) . closest ( ".popover" ) ;
2013-07-10 16:42:54 +02:00
var left _offset ;
if ( small _window ( ) ) { // Don't let part of the popover spill out of the feed area.
left _offset = bar . offset ( ) . left + bar . width ( ) / 2 - my _popover . width ( ) / 2 ;
} else {
left _offset = bar . offset ( ) . left + 76 - my _popover . width ( ) / 2 ;
}
my _popover . offset ( { top : my _popover . offset ( ) . top - 10 , left : left _offset } ) ;
2013-06-27 22:18:28 +02:00
$ ( "#tutorial-private-next" ) . click ( function ( ) {
bar . popover ( "destroy" ) ;
home ( ) ;
2014-01-07 21:07:15 +01:00
} ) . focus ( ) ;
2013-06-27 22:18:28 +02:00
}
function subject ( ) {
var spotlight _message = rows . first _visible ( ) ;
var bar = spotlight _message . prev ( ".recipient_row" ) ;
2013-07-10 16:42:54 +02:00
var placement = maybe _tweak _placement ( "bottom" ) ;
2013-07-16 21:00:52 +02:00
create _and _show _popover ( bar , placement , "Topics" , "tutorial_subject" ) ;
2013-06-27 22:18:28 +02:00
var my _popover = $ ( "#tutorial-subject" ) . closest ( ".popover" ) ;
2013-07-10 16:42:54 +02:00
if ( placement === "bottom" ) { // Wider screen, popover is on bottom.
2013-08-27 20:58:02 +02:00
my _popover . offset ( { left : bar . offset ( ) . left + 114 - my _popover . width ( ) / 2 } ) ;
2013-07-10 16:42:54 +02:00
} else {
2013-08-27 20:58:02 +02:00
my _popover . offset ( { left : bar . offset ( ) . left + 166 } ) ;
2013-07-10 16:42:54 +02:00
}
2013-06-27 22:18:28 +02:00
$ ( "#tutorial-subject-next" ) . click ( function ( ) {
bar . popover ( "destroy" ) ;
private _message ( ) ;
2014-01-07 21:07:15 +01:00
} ) . focus ( ) ;
2013-06-27 22:18:28 +02:00
}
function stream ( ) {
var bar = rows . first _visible ( ) . prev ( ".recipient_row" ) ;
2013-07-10 16:42:54 +02:00
var placement = maybe _tweak _placement ( "bottom" ) ;
create _and _show _popover ( bar , placement , "Streams" , "tutorial_stream" ) ;
2013-06-27 22:18:28 +02:00
var my _popover = $ ( "#tutorial-stream" ) . closest ( ".popover" ) ;
2013-07-10 16:42:54 +02:00
if ( placement === "bottom" ) { // Wider screen, popover is on bottom.
2013-08-27 20:58:02 +02:00
my _popover . offset ( { left : bar . offset ( ) . left + 44 - my _popover . width ( ) / 2 } ) ;
2013-07-10 16:42:54 +02:00
} else { // Smaller screen, popover is to the right of the stream label.
2013-08-27 20:58:02 +02:00
my _popover . offset ( { left : bar . offset ( ) . left + 78 } ) ;
2013-07-10 16:42:54 +02:00
}
2013-06-27 22:18:28 +02:00
$ ( "#tutorial-stream-next" ) . click ( function ( ) {
bar . popover ( "destroy" ) ;
subject ( ) ;
2014-01-07 21:07:15 +01:00
} ) . focus ( ) ;
2013-06-27 22:18:28 +02:00
}
function message ( ) {
var spotlight _message = rows . first _visible ( ) ;
var bar = spotlight _message . prev ( ".recipient_row" ) ;
var x = bar . offset ( ) . left ;
var y = bar . offset ( ) . top ;
var message _height = bar . height ( ) + spotlight _message . height ( ) ;
var message _width = bar . width ( ) ;
box ( x , y , message _width , message _height ) ;
2013-07-10 16:42:54 +02:00
create _and _show _popover ( bar , maybe _tweak _placement ( "left" ) , "Messages" ,
"tutorial_message" ) ;
2013-06-27 22:18:28 +02:00
var my _popover = $ ( "#tutorial-message" ) . closest ( ".popover" ) ;
my _popover . offset ( { left : my _popover . offset ( ) . left - 10 } ) ;
$ ( "#tutorial-message-next" ) . click ( function ( ) {
bar . popover ( "destroy" ) ;
stream ( ) ;
2014-01-07 21:07:15 +01:00
} ) . focus ( ) ;
2013-06-27 22:18:28 +02:00
}
function welcome ( ) {
// Grey out everything.
$ ( '#top-screen' ) . css ( { opacity : 0.7 , width : $ ( document ) . width ( ) ,
height : $ ( document ) . height ( ) } ) ;
// Highlight the first recipient row.
var bar = rows . first _visible ( ) . prev ( ".recipient_row" ) ;
2013-07-10 16:42:54 +02:00
create _and _show _popover ( bar , maybe _tweak _placement ( "left" ) ,
"Welcome, " + page _params . fullname + "!" ,
2013-06-27 22:18:28 +02:00
"tutorial_welcome" ) ;
var my _popover = $ ( "#tutorial-welcome" ) . closest ( ".popover" ) ;
my _popover . offset ( { left : my _popover . offset ( ) . left - 10 } ) ;
$ ( "#tutorial-welcome-next" ) . click ( function ( ) {
bar . popover ( "destroy" ) ;
message ( ) ;
2014-01-07 21:07:15 +01:00
} ) . focus ( ) ;
2013-06-27 22:18:28 +02:00
}
2013-06-20 18:06:31 +02:00
exports . start = function ( ) {
2013-07-02 22:13:52 +02:00
if ( ui . home _tab _obscured ( ) ) {
ui . change _tab _to ( '#home' ) ;
}
narrow . deactivate ( ) ;
2013-06-27 22:18:03 +02:00
// Set temporarly colors for the streams used in the tutorial.
2013-08-15 21:11:07 +02:00
real _stream _info = stream _data . get _stream _info ( ) ;
stream _data . set _stream _info ( tutorial _stream _info ) ;
2013-06-27 22:18:03 +02:00
// Add the fake messages to the feed and get started.
current _msg _list . add _and _rerender ( fake _messages ) ;
2013-07-02 19:58:44 +02:00
disable _event _handlers ( ) ;
2013-06-20 18:06:31 +02:00
is _running = true ;
set _tutorial _status ( "started" ) ;
2013-06-27 22:18:28 +02:00
welcome ( ) ;
2013-02-13 22:15:13 +01:00
} ;
exports . initialize = function ( ) {
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 ;
} ( ) ) ;