impress.js 141 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858
  1. /**
  2. * impress.js
  3. *
  4. * impress.js is a presentation tool based on the power of CSS3 transforms and transitions
  5. * in modern browsers and inspired by the idea behind prezi.com.
  6. *
  7. *
  8. * Copyright 2011-2012 Bartek Szopka (@bartaz)
  9. *
  10. * Released under the MIT and GPL Licenses.
  11. *
  12. * ------------------------------------------------
  13. * author: Bartek Szopka
  14. * version: 1.0.0-beta1
  15. * url: http://bartaz.github.com/impress.js/
  16. * source: http://github.com/bartaz/impress.js/
  17. */
  18. // You are one of those who like to know how things work inside?
  19. // Let me show you the cogs that make impress.js run...
  20. ( function( document, window ) {
  21. "use strict";
  22. var lib;
  23. // HELPER FUNCTIONS
  24. // `pfx` is a function that takes a standard CSS property name as a parameter
  25. // and returns it's prefixed version valid for current browser it runs in.
  26. // The code is heavily inspired by Modernizr http://www.modernizr.com/
  27. var pfx = ( function() {
  28. var style = document.createElement( "dummy" ).style,
  29. prefixes = "Webkit Moz O ms Khtml".split( " " ),
  30. memory = {};
  31. return function( prop ) {
  32. if ( typeof memory[ prop ] === "undefined" ) {
  33. var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
  34. props = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
  35. memory[ prop ] = null;
  36. for ( var i in props ) {
  37. if ( style[ props[ i ] ] !== undefined ) {
  38. memory[ prop ] = props[ i ];
  39. break;
  40. }
  41. }
  42. }
  43. return memory[ prop ];
  44. };
  45. } )();
  46. var validateOrder = function( order, fallback ) {
  47. var validChars = "xyz";
  48. var returnStr = "";
  49. if ( typeof order === "string" ) {
  50. for ( var i in order.split( "" ) ) {
  51. if ( validChars.indexOf( order[ i ] >= 0 ) ) {
  52. returnStr += order[ i ];
  53. // Each of x,y,z can be used only once.
  54. validChars = validChars.split( order[ i ] ).join( "" );
  55. }
  56. }
  57. }
  58. if ( returnStr ) {
  59. return returnStr;
  60. } else if ( fallback !== undefined ) {
  61. return fallback;
  62. } else {
  63. return "xyz";
  64. }
  65. };
  66. // `css` function applies the styles given in `props` object to the element
  67. // given as `el`. It runs all property names through `pfx` function to make
  68. // sure proper prefixed version of the property is used.
  69. var css = function( el, props ) {
  70. var key, pkey;
  71. for ( key in props ) {
  72. if ( props.hasOwnProperty( key ) ) {
  73. pkey = pfx( key );
  74. if ( pkey !== null ) {
  75. el.style[ pkey ] = props[ key ];
  76. }
  77. }
  78. }
  79. return el;
  80. };
  81. // `translate` builds a translate transform string for given data.
  82. var translate = function( t ) {
  83. return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) ";
  84. };
  85. // `rotate` builds a rotate transform string for given data.
  86. // By default the rotations are in X Y Z order that can be reverted by passing `true`
  87. // as second parameter.
  88. var rotate = function( r, revert ) {
  89. var order = r.order ? r.order : "xyz";
  90. var css = "";
  91. var axes = order.split( "" );
  92. if ( revert ) {
  93. axes = axes.reverse();
  94. }
  95. for ( var i = 0; i < axes.length; i++ ) {
  96. css += " rotate" + axes[ i ].toUpperCase() + "(" + r[ axes[ i ] ] + "deg)";
  97. }
  98. return css;
  99. };
  100. // `scale` builds a scale transform string for given data.
  101. var scale = function( s ) {
  102. return " scale(" + s + ") ";
  103. };
  104. // `computeWindowScale` counts the scale factor between window size and size
  105. // defined for the presentation in the config.
  106. var computeWindowScale = function( config ) {
  107. var hScale = window.innerHeight / config.height,
  108. wScale = window.innerWidth / config.width,
  109. scale = hScale > wScale ? wScale : hScale;
  110. if ( config.maxScale && scale > config.maxScale ) {
  111. scale = config.maxScale;
  112. }
  113. if ( config.minScale && scale < config.minScale ) {
  114. scale = config.minScale;
  115. }
  116. return scale;
  117. };
  118. // CHECK SUPPORT
  119. var body = document.body;
  120. var impressSupported =
  121. // Browser should support CSS 3D transtorms
  122. ( pfx( "perspective" ) !== null ) &&
  123. // And `classList` and `dataset` APIs
  124. ( body.classList ) &&
  125. ( body.dataset );
  126. if ( !impressSupported ) {
  127. // We can't be sure that `classList` is supported
  128. body.className += " impress-not-supported ";
  129. }
  130. // GLOBALS AND DEFAULTS
  131. // This is where the root elements of all impress.js instances will be kept.
  132. // Yes, this means you can have more than one instance on a page, but I'm not
  133. // sure if it makes any sense in practice ;)
  134. var roots = {};
  135. var preInitPlugins = [];
  136. var preStepLeavePlugins = [];
  137. // Some default config values.
  138. var defaults = {
  139. width: 1024,
  140. height: 768,
  141. maxScale: 1,
  142. minScale: 0,
  143. perspective: 1000,
  144. transitionDuration: 1000
  145. };
  146. // It's just an empty function ... and a useless comment.
  147. var empty = function() { return false; };
  148. // IMPRESS.JS API
  149. // And that's where interesting things will start to happen.
  150. // It's the core `impress` function that returns the impress.js API
  151. // for a presentation based on the element with given id ("impress"
  152. // by default).
  153. var impress = window.impress = function( rootId ) {
  154. // If impress.js is not supported by the browser return a dummy API
  155. // it may not be a perfect solution but we return early and avoid
  156. // running code that may use features not implemented in the browser.
  157. if ( !impressSupported ) {
  158. return {
  159. init: empty,
  160. goto: empty,
  161. prev: empty,
  162. next: empty,
  163. swipe: empty,
  164. tear: empty,
  165. lib: {}
  166. };
  167. }
  168. rootId = rootId || "impress";
  169. // If given root is already initialized just return the API
  170. if ( roots[ "impress-root-" + rootId ] ) {
  171. return roots[ "impress-root-" + rootId ];
  172. }
  173. // The gc library depends on being initialized before we do any changes to DOM.
  174. lib = initLibraries( rootId );
  175. body.classList.remove( "impress-not-supported" );
  176. body.classList.add( "impress-supported" );
  177. // Data of all presentation steps
  178. var stepsData = {};
  179. // Element of currently active step
  180. var activeStep = null;
  181. // Current state (position, rotation and scale) of the presentation
  182. var currentState = null;
  183. // Array of step elements
  184. var steps = null;
  185. // Configuration options
  186. var config = null;
  187. // Scale factor of the browser window
  188. var windowScale = null;
  189. // Root presentation elements
  190. var root = lib.util.byId( rootId );
  191. var canvas = document.createElement( "div" );
  192. var initialized = false;
  193. // STEP EVENTS
  194. //
  195. // There are currently two step events triggered by impress.js
  196. // `impress:stepenter` is triggered when the step is shown on the
  197. // screen (the transition from the previous one is finished) and
  198. // `impress:stepleave` is triggered when the step is left (the
  199. // transition to next step just starts).
  200. // Reference to last entered step
  201. var lastEntered = null;
  202. // `onStepEnter` is called whenever the step element is entered
  203. // but the event is triggered only if the step is different than
  204. // last entered step.
  205. // We sometimes call `goto`, and therefore `onStepEnter`, just to redraw a step, such as
  206. // after screen resize. In this case - more precisely, in any case - we trigger a
  207. // `impress:steprefresh` event.
  208. var onStepEnter = function( step ) {
  209. if ( lastEntered !== step ) {
  210. lib.util.triggerEvent( step, "impress:stepenter" );
  211. lastEntered = step;
  212. }
  213. lib.util.triggerEvent( step, "impress:steprefresh" );
  214. };
  215. // `onStepLeave` is called whenever the currentStep element is left
  216. // but the event is triggered only if the currentStep is the same as
  217. // lastEntered step.
  218. var onStepLeave = function( currentStep, nextStep ) {
  219. if ( lastEntered === currentStep ) {
  220. lib.util.triggerEvent( currentStep, "impress:stepleave", { next: nextStep } );
  221. lastEntered = null;
  222. }
  223. };
  224. // `initStep` initializes given step element by reading data from its
  225. // data attributes and setting correct styles.
  226. var initStep = function( el, idx ) {
  227. var data = el.dataset,
  228. step = {
  229. translate: {
  230. x: lib.util.toNumber( data.x ),
  231. y: lib.util.toNumber( data.y ),
  232. z: lib.util.toNumber( data.z )
  233. },
  234. rotate: {
  235. x: lib.util.toNumber( data.rotateX ),
  236. y: lib.util.toNumber( data.rotateY ),
  237. z: lib.util.toNumber( data.rotateZ || data.rotate ),
  238. order: validateOrder( data.rotateOrder )
  239. },
  240. scale: lib.util.toNumber( data.scale, 1 ),
  241. transitionDuration: lib.util.toNumber(
  242. data.transitionDuration, config.transitionDuration
  243. ),
  244. el: el
  245. };
  246. if ( !el.id ) {
  247. el.id = "step-" + ( idx + 1 );
  248. }
  249. stepsData[ "impress-" + el.id ] = step;
  250. css( el, {
  251. position: "absolute",
  252. transform: "translate(-50%,-50%)" +
  253. translate( step.translate ) +
  254. rotate( step.rotate ) +
  255. scale( step.scale ),
  256. transformStyle: "preserve-3d"
  257. } );
  258. };
  259. // Initialize all steps.
  260. // Read the data-* attributes, store in internal stepsData, and render with CSS.
  261. var initAllSteps = function() {
  262. steps = lib.util.$$( ".step", root );
  263. steps.forEach( initStep );
  264. };
  265. // `init` API function that initializes (and runs) the presentation.
  266. var init = function() {
  267. if ( initialized ) { return; }
  268. execPreInitPlugins( root );
  269. // First we set up the viewport for mobile devices.
  270. // For some reason iPad goes nuts when it is not done properly.
  271. var meta = lib.util.$( "meta[name='viewport']" ) || document.createElement( "meta" );
  272. meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no";
  273. if ( meta.parentNode !== document.head ) {
  274. meta.name = "viewport";
  275. document.head.appendChild( meta );
  276. }
  277. // Initialize configuration object
  278. var rootData = root.dataset;
  279. config = {
  280. width: lib.util.toNumber( rootData.width, defaults.width ),
  281. height: lib.util.toNumber( rootData.height, defaults.height ),
  282. maxScale: lib.util.toNumber( rootData.maxScale, defaults.maxScale ),
  283. minScale: lib.util.toNumber( rootData.minScale, defaults.minScale ),
  284. perspective: lib.util.toNumber( rootData.perspective, defaults.perspective ),
  285. transitionDuration: lib.util.toNumber(
  286. rootData.transitionDuration, defaults.transitionDuration
  287. )
  288. };
  289. windowScale = computeWindowScale( config );
  290. // Wrap steps with "canvas" element
  291. lib.util.arrayify( root.childNodes ).forEach( function( el ) {
  292. canvas.appendChild( el );
  293. } );
  294. root.appendChild( canvas );
  295. // Set initial styles
  296. document.documentElement.style.height = "100%";
  297. css( body, {
  298. height: "100%",
  299. overflow: "hidden"
  300. } );
  301. var rootStyles = {
  302. position: "absolute",
  303. transformOrigin: "top left",
  304. transition: "all 0s ease-in-out",
  305. transformStyle: "preserve-3d"
  306. };
  307. css( root, rootStyles );
  308. css( root, {
  309. top: "50%",
  310. left: "50%",
  311. perspective: ( config.perspective / windowScale ) + "px",
  312. transform: scale( windowScale )
  313. } );
  314. css( canvas, rootStyles );
  315. body.classList.remove( "impress-disabled" );
  316. body.classList.add( "impress-enabled" );
  317. // Get and init steps
  318. initAllSteps();
  319. // Set a default initial state of the canvas
  320. currentState = {
  321. translate: { x: 0, y: 0, z: 0 },
  322. rotate: { x: 0, y: 0, z: 0, order: "xyz" },
  323. scale: 1
  324. };
  325. initialized = true;
  326. lib.util.triggerEvent( root, "impress:init",
  327. { api: roots[ "impress-root-" + rootId ] } );
  328. };
  329. // `getStep` is a helper function that returns a step element defined by parameter.
  330. // If a number is given, step with index given by the number is returned, if a string
  331. // is given step element with such id is returned, if DOM element is given it is returned
  332. // if it is a correct step element.
  333. var getStep = function( step ) {
  334. if ( typeof step === "number" ) {
  335. step = step < 0 ? steps[ steps.length + step ] : steps[ step ];
  336. } else if ( typeof step === "string" ) {
  337. step = lib.util.byId( step );
  338. }
  339. return ( step && step.id && stepsData[ "impress-" + step.id ] ) ? step : null;
  340. };
  341. // Used to reset timeout for `impress:stepenter` event
  342. var stepEnterTimeout = null;
  343. // `goto` API function that moves to step given as `el` parameter (by index, id or element).
  344. // `duration` optionally given as second parameter, is the transition duration in css.
  345. // `reason` is the string "next", "prev" or "goto" (default) and will be made available to
  346. // preStepLeave plugins.
  347. // `origEvent` may contain event that caused the call to goto, such as a key press event
  348. var goto = function( el, duration, reason, origEvent ) {
  349. reason = reason || "goto";
  350. origEvent = origEvent || null;
  351. if ( !initialized ) {
  352. return false;
  353. }
  354. // Re-execute initAllSteps for each transition. This allows to edit step attributes
  355. // dynamically, such as change their coordinates, or even remove or add steps, and have
  356. // that change apply when goto() is called.
  357. initAllSteps();
  358. if ( !( el = getStep( el ) ) ) {
  359. return false;
  360. }
  361. // Sometimes it's possible to trigger focus on first link with some keyboard action.
  362. // Browser in such a case tries to scroll the page to make this element visible
  363. // (even that body overflow is set to hidden) and it breaks our careful positioning.
  364. //
  365. // So, as a lousy (and lazy) workaround we will make the page scroll back to the top
  366. // whenever slide is selected
  367. //
  368. // If you are reading this and know any better way to handle it, I'll be glad to hear
  369. // about it!
  370. window.scrollTo( 0, 0 );
  371. var step = stepsData[ "impress-" + el.id ];
  372. duration = ( duration !== undefined ? duration : step.transitionDuration );
  373. // If we are in fact moving to another step, start with executing the registered
  374. // preStepLeave plugins.
  375. if ( activeStep && activeStep !== el ) {
  376. var event = { target: activeStep, detail: {} };
  377. event.detail.next = el;
  378. event.detail.transitionDuration = duration;
  379. event.detail.reason = reason;
  380. if ( origEvent ) {
  381. event.origEvent = origEvent;
  382. }
  383. if ( execPreStepLeavePlugins( event ) === false ) {
  384. // PreStepLeave plugins are allowed to abort the transition altogether, by
  385. // returning false.
  386. // see stop and substep plugins for an example of doing just that
  387. return false;
  388. }
  389. // Plugins are allowed to change the detail values
  390. el = event.detail.next;
  391. step = stepsData[ "impress-" + el.id ];
  392. duration = event.detail.transitionDuration;
  393. }
  394. if ( activeStep ) {
  395. activeStep.classList.remove( "active" );
  396. body.classList.remove( "impress-on-" + activeStep.id );
  397. }
  398. el.classList.add( "active" );
  399. body.classList.add( "impress-on-" + el.id );
  400. // Compute target state of the canvas based on given step
  401. var target = {
  402. rotate: {
  403. x: -step.rotate.x,
  404. y: -step.rotate.y,
  405. z: -step.rotate.z,
  406. order: step.rotate.order
  407. },
  408. translate: {
  409. x: -step.translate.x,
  410. y: -step.translate.y,
  411. z: -step.translate.z
  412. },
  413. scale: 1 / step.scale
  414. };
  415. // Check if the transition is zooming in or not.
  416. //
  417. // This information is used to alter the transition style:
  418. // when we are zooming in - we start with move and rotate transition
  419. // and the scaling is delayed, but when we are zooming out we start
  420. // with scaling down and move and rotation are delayed.
  421. var zoomin = target.scale >= currentState.scale;
  422. duration = lib.util.toNumber( duration, config.transitionDuration );
  423. var delay = ( duration / 2 );
  424. // If the same step is re-selected, force computing window scaling,
  425. // because it is likely to be caused by window resize
  426. if ( el === activeStep ) {
  427. windowScale = computeWindowScale( config );
  428. }
  429. var targetScale = target.scale * windowScale;
  430. // Trigger leave of currently active element (if it's not the same step again)
  431. if ( activeStep && activeStep !== el ) {
  432. onStepLeave( activeStep, el );
  433. }
  434. // Now we alter transforms of `root` and `canvas` to trigger transitions.
  435. //
  436. // And here is why there are two elements: `root` and `canvas` - they are
  437. // being animated separately:
  438. // `root` is used for scaling and `canvas` for translate and rotations.
  439. // Transitions on them are triggered with different delays (to make
  440. // visually nice and "natural" looking transitions), so we need to know
  441. // that both of them are finished.
  442. css( root, {
  443. // To keep the perspective look similar for different scales
  444. // we need to "scale" the perspective, too
  445. // For IE 11 support we must specify perspective independent
  446. // of transform.
  447. perspective: ( config.perspective / targetScale ) + "px",
  448. transform: scale( targetScale ),
  449. transitionDuration: duration + "ms",
  450. transitionDelay: ( zoomin ? delay : 0 ) + "ms"
  451. } );
  452. css( canvas, {
  453. transform: rotate( target.rotate, true ) + translate( target.translate ),
  454. transitionDuration: duration + "ms",
  455. transitionDelay: ( zoomin ? 0 : delay ) + "ms"
  456. } );
  457. // Here is a tricky part...
  458. //
  459. // If there is no change in scale or no change in rotation and translation, it means
  460. // there was actually no delay - because there was no transition on `root` or `canvas`
  461. // elements. We want to trigger `impress:stepenter` event in the correct moment, so
  462. // here we compare the current and target values to check if delay should be taken into
  463. // account.
  464. //
  465. // I know that this `if` statement looks scary, but it's pretty simple when you know
  466. // what is going on - it's simply comparing all the values.
  467. if ( currentState.scale === target.scale ||
  468. ( currentState.rotate.x === target.rotate.x &&
  469. currentState.rotate.y === target.rotate.y &&
  470. currentState.rotate.z === target.rotate.z &&
  471. currentState.translate.x === target.translate.x &&
  472. currentState.translate.y === target.translate.y &&
  473. currentState.translate.z === target.translate.z ) ) {
  474. delay = 0;
  475. }
  476. // Store current state
  477. currentState = target;
  478. activeStep = el;
  479. // And here is where we trigger `impress:stepenter` event.
  480. // We simply set up a timeout to fire it taking transition duration (and possible delay)
  481. // into account.
  482. //
  483. // I really wanted to make it in more elegant way. The `transitionend` event seemed to
  484. // be the best way to do it, but the fact that I'm using transitions on two separate
  485. // elements and that the `transitionend` event is only triggered when there was a
  486. // transition (change in the values) caused some bugs and made the code really
  487. // complicated, cause I had to handle all the conditions separately. And it still
  488. // needed a `setTimeout` fallback for the situations when there is no transition at all.
  489. // So I decided that I'd rather make the code simpler than use shiny new
  490. // `transitionend`.
  491. //
  492. // If you want learn something interesting and see how it was done with `transitionend`
  493. // go back to version 0.5.2 of impress.js:
  494. // http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js
  495. window.clearTimeout( stepEnterTimeout );
  496. stepEnterTimeout = window.setTimeout( function() {
  497. onStepEnter( activeStep );
  498. }, duration + delay );
  499. return el;
  500. };
  501. // `prev` API function goes to previous step (in document order)
  502. // `event` is optional, may contain the event that caused the need to call prev()
  503. var prev = function( origEvent ) {
  504. var prev = steps.indexOf( activeStep ) - 1;
  505. prev = prev >= 0 ? steps[ prev ] : steps[ steps.length - 1 ];
  506. return goto( prev, undefined, "prev", origEvent );
  507. };
  508. // `next` API function goes to next step (in document order)
  509. // `event` is optional, may contain the event that caused the need to call next()
  510. var next = function( origEvent ) {
  511. var next = steps.indexOf( activeStep ) + 1;
  512. next = next < steps.length ? steps[ next ] : steps[ 0 ];
  513. return goto( next, undefined, "next", origEvent );
  514. };
  515. // Swipe for touch devices by @and3rson.
  516. // Below we extend the api to control the animation between the currently
  517. // active step and a presumed next/prev step. See touch plugin for
  518. // an example of using this api.
  519. // Helper function
  520. var interpolate = function( a, b, k ) {
  521. return a + ( b - a ) * k;
  522. };
  523. // Animate a swipe.
  524. //
  525. // Pct is a value between -1.0 and +1.0, designating the current length
  526. // of the swipe.
  527. //
  528. // If pct is negative, swipe towards the next() step, if positive,
  529. // towards the prev() step.
  530. //
  531. // Note that pre-stepleave plugins such as goto can mess with what is a
  532. // next() and prev() step, so we need to trigger the pre-stepleave event
  533. // here, even if a swipe doesn't guarantee that the transition will
  534. // actually happen.
  535. //
  536. // Calling swipe(), with any value of pct, won't in itself cause a
  537. // transition to happen, this is just to animate the swipe. Once the
  538. // transition is committed - such as at a touchend event - caller is
  539. // responsible for also calling prev()/next() as appropriate.
  540. //
  541. // Note: For now, this function is made available to be used by the swipe plugin (which
  542. // is the UI counterpart to this). It is a semi-internal API and intentionally not
  543. // documented in DOCUMENTATION.md.
  544. var swipe = function( pct ) {
  545. if ( Math.abs( pct ) > 1 ) {
  546. return;
  547. }
  548. // Prepare & execute the preStepLeave event
  549. var event = { target: activeStep, detail: {} };
  550. event.detail.swipe = pct;
  551. // Will be ignored within swipe animation, but just in case a plugin wants to read this,
  552. // humor them
  553. event.detail.transitionDuration = config.transitionDuration;
  554. var idx; // Needed by jshint
  555. if ( pct < 0 ) {
  556. idx = steps.indexOf( activeStep ) + 1;
  557. event.detail.next = idx < steps.length ? steps[ idx ] : steps[ 0 ];
  558. event.detail.reason = "next";
  559. } else if ( pct > 0 ) {
  560. idx = steps.indexOf( activeStep ) - 1;
  561. event.detail.next = idx >= 0 ? steps[ idx ] : steps[ steps.length - 1 ];
  562. event.detail.reason = "prev";
  563. } else {
  564. // No move
  565. return;
  566. }
  567. if ( execPreStepLeavePlugins( event ) === false ) {
  568. // If a preStepLeave plugin wants to abort the transition, don't animate a swipe
  569. // For stop, this is probably ok. For substep, the plugin it self might want to do
  570. // some animation, but that's not the current implementation.
  571. return false;
  572. }
  573. var nextElement = event.detail.next;
  574. var nextStep = stepsData[ "impress-" + nextElement.id ];
  575. // If the same step is re-selected, force computing window scaling,
  576. var nextScale = nextStep.scale * windowScale;
  577. var k = Math.abs( pct );
  578. var interpolatedStep = {
  579. translate: {
  580. x: interpolate( currentState.translate.x, -nextStep.translate.x, k ),
  581. y: interpolate( currentState.translate.y, -nextStep.translate.y, k ),
  582. z: interpolate( currentState.translate.z, -nextStep.translate.z, k )
  583. },
  584. rotate: {
  585. x: interpolate( currentState.rotate.x, -nextStep.rotate.x, k ),
  586. y: interpolate( currentState.rotate.y, -nextStep.rotate.y, k ),
  587. z: interpolate( currentState.rotate.z, -nextStep.rotate.z, k ),
  588. // Unfortunately there's a discontinuity if rotation order changes. Nothing I
  589. // can do about it?
  590. order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order
  591. },
  592. scale: interpolate( currentState.scale, nextScale, k )
  593. };
  594. css( root, {
  595. // To keep the perspective look similar for different scales
  596. // we need to 'scale' the perspective, too
  597. perspective: config.perspective / interpolatedStep.scale + "px",
  598. transform: scale( interpolatedStep.scale ),
  599. transitionDuration: "0ms",
  600. transitionDelay: "0ms"
  601. } );
  602. css( canvas, {
  603. transform: rotate( interpolatedStep.rotate, true ) +
  604. translate( interpolatedStep.translate ),
  605. transitionDuration: "0ms",
  606. transitionDelay: "0ms"
  607. } );
  608. };
  609. // Teardown impress
  610. // Resets the DOM to the state it was before impress().init() was called.
  611. // (If you called impress(rootId).init() for multiple different rootId's, then you must
  612. // also call tear() once for each of them.)
  613. var tear = function() {
  614. lib.gc.teardown();
  615. delete roots[ "impress-root-" + rootId ];
  616. };
  617. // Adding some useful classes to step elements.
  618. //
  619. // All the steps that have not been shown yet are given `future` class.
  620. // When the step is entered the `future` class is removed and the `present`
  621. // class is given. When the step is left `present` class is replaced with
  622. // `past` class.
  623. //
  624. // So every step element is always in one of three possible states:
  625. // `future`, `present` and `past`.
  626. //
  627. // There classes can be used in CSS to style different types of steps.
  628. // For example the `present` class can be used to trigger some custom
  629. // animations when step is shown.
  630. lib.gc.addEventListener( root, "impress:init", function() {
  631. // STEP CLASSES
  632. steps.forEach( function( step ) {
  633. step.classList.add( "future" );
  634. } );
  635. lib.gc.addEventListener( root, "impress:stepenter", function( event ) {
  636. event.target.classList.remove( "past" );
  637. event.target.classList.remove( "future" );
  638. event.target.classList.add( "present" );
  639. }, false );
  640. lib.gc.addEventListener( root, "impress:stepleave", function( event ) {
  641. event.target.classList.remove( "present" );
  642. event.target.classList.add( "past" );
  643. }, false );
  644. }, false );
  645. // Adding hash change support.
  646. lib.gc.addEventListener( root, "impress:init", function() {
  647. // Last hash detected
  648. var lastHash = "";
  649. // `#/step-id` is used instead of `#step-id` to prevent default browser
  650. // scrolling to element in hash.
  651. //
  652. // And it has to be set after animation finishes, because in Chrome it
  653. // makes transtion laggy.
  654. // BUG: http://code.google.com/p/chromium/issues/detail?id=62820
  655. lib.gc.addEventListener( root, "impress:stepenter", function( event ) {
  656. window.location.hash = lastHash = "#/" + event.target.id;
  657. }, false );
  658. lib.gc.addEventListener( window, "hashchange", function() {
  659. // When the step is entered hash in the location is updated
  660. // (just few lines above from here), so the hash change is
  661. // triggered and we would call `goto` again on the same element.
  662. //
  663. // To avoid this we store last entered hash and compare.
  664. if ( window.location.hash !== lastHash ) {
  665. goto( lib.util.getElementFromHash() );
  666. }
  667. }, false );
  668. // START
  669. // by selecting step defined in url or first step of the presentation
  670. goto( lib.util.getElementFromHash() || steps[ 0 ], 0 );
  671. }, false );
  672. body.classList.add( "impress-disabled" );
  673. // Store and return API for given impress.js root element
  674. return ( roots[ "impress-root-" + rootId ] = {
  675. init: init,
  676. goto: goto,
  677. next: next,
  678. prev: prev,
  679. swipe: swipe,
  680. tear: tear,
  681. lib: lib
  682. } );
  683. };
  684. // Flag that can be used in JS to check if browser have passed the support test
  685. impress.supported = impressSupported;
  686. // ADD and INIT LIBRARIES
  687. // Library factories are defined in src/lib/*.js, and register themselves by calling
  688. // impress.addLibraryFactory(libraryFactoryObject). They're stored here, and used to augment
  689. // the API with library functions when client calls impress(rootId).
  690. // See src/lib/README.md for clearer example.
  691. // (Advanced usage: For different values of rootId, a different instance of the libaries are
  692. // generated, in case they need to hold different state for different root elements.)
  693. var libraryFactories = {};
  694. impress.addLibraryFactory = function( obj ) {
  695. for ( var libname in obj ) {
  696. if ( obj.hasOwnProperty( libname ) ) {
  697. libraryFactories[ libname ] = obj[ libname ];
  698. }
  699. }
  700. };
  701. // Call each library factory, and return the lib object that is added to the api.
  702. var initLibraries = function( rootId ) { //jshint ignore:line
  703. var lib = {};
  704. for ( var libname in libraryFactories ) {
  705. if ( libraryFactories.hasOwnProperty( libname ) ) {
  706. if ( lib[ libname ] !== undefined ) {
  707. throw "impress.js ERROR: Two libraries both tried to use libname: " + libname;
  708. }
  709. lib[ libname ] = libraryFactories[ libname ]( rootId );
  710. }
  711. }
  712. return lib;
  713. };
  714. // `addPreInitPlugin` allows plugins to register a function that should
  715. // be run (synchronously) at the beginning of init, before
  716. // impress().init() itself executes.
  717. impress.addPreInitPlugin = function( plugin, weight ) {
  718. weight = parseInt( weight ) || 10;
  719. if ( weight <= 0 ) {
  720. throw "addPreInitPlugin: weight must be a positive integer";
  721. }
  722. if ( preInitPlugins[ weight ] === undefined ) {
  723. preInitPlugins[ weight ] = [];
  724. }
  725. preInitPlugins[ weight ].push( plugin );
  726. };
  727. // Called at beginning of init, to execute all pre-init plugins.
  728. var execPreInitPlugins = function( root ) { //jshint ignore:line
  729. for ( var i = 0; i < preInitPlugins.length; i++ ) {
  730. var thisLevel = preInitPlugins[ i ];
  731. if ( thisLevel !== undefined ) {
  732. for ( var j = 0; j < thisLevel.length; j++ ) {
  733. thisLevel[ j ]( root );
  734. }
  735. }
  736. }
  737. };
  738. // `addPreStepLeavePlugin` allows plugins to register a function that should
  739. // be run (synchronously) at the beginning of goto()
  740. impress.addPreStepLeavePlugin = function( plugin, weight ) { //jshint ignore:line
  741. weight = parseInt( weight ) || 10;
  742. if ( weight <= 0 ) {
  743. throw "addPreStepLeavePlugin: weight must be a positive integer";
  744. }
  745. if ( preStepLeavePlugins[ weight ] === undefined ) {
  746. preStepLeavePlugins[ weight ] = [];
  747. }
  748. preStepLeavePlugins[ weight ].push( plugin );
  749. };
  750. // Called at beginning of goto(), to execute all preStepLeave plugins.
  751. var execPreStepLeavePlugins = function( event ) { //jshint ignore:line
  752. for ( var i = 0; i < preStepLeavePlugins.length; i++ ) {
  753. var thisLevel = preStepLeavePlugins[ i ];
  754. if ( thisLevel !== undefined ) {
  755. for ( var j = 0; j < thisLevel.length; j++ ) {
  756. if ( thisLevel[ j ]( event ) === false ) {
  757. // If a plugin returns false, the stepleave event (and related transition)
  758. // is aborted
  759. return false;
  760. }
  761. }
  762. }
  763. }
  764. };
  765. } )( document, window );
  766. // THAT'S ALL FOLKS!
  767. //
  768. // Thanks for reading it all.
  769. // Or thanks for scrolling down and reading the last part.
  770. //
  771. // I've learnt a lot when building impress.js and I hope this code and comments
  772. // will help somebody learn at least some part of it.
  773. /**
  774. * Garbage collection utility
  775. *
  776. * This library allows plugins to add elements and event listeners they add to the DOM. The user
  777. * can call `impress().lib.gc.teardown()` to cause all of them to be removed from DOM, so that
  778. * the document is in the state it was before calling `impress().init()`.
  779. *
  780. * In addition to just adding elements and event listeners to the garbage collector, plugins
  781. * can also register callback functions to do arbitrary cleanup upon teardown.
  782. *
  783. * Henrik Ingo (c) 2016
  784. * MIT License
  785. */
  786. ( function( document, window ) {
  787. "use strict";
  788. var roots = [];
  789. var rootsCount = 0;
  790. var startingState = { roots: [] };
  791. var libraryFactory = function( rootId ) {
  792. if ( roots[ rootId ] ) {
  793. return roots[ rootId ];
  794. }
  795. // Per root global variables (instance variables?)
  796. var elementList = [];
  797. var eventListenerList = [];
  798. var callbackList = [];
  799. recordStartingState( rootId );
  800. // LIBRARY FUNCTIONS
  801. // Definitions of the library functions we return as an object at the end
  802. // `pushElement` adds a DOM element to the gc stack
  803. var pushElement = function( element ) {
  804. elementList.push( element );
  805. };
  806. // `appendChild` is a convenience wrapper that combines DOM appendChild with gc.pushElement
  807. var appendChild = function( parent, element ) {
  808. parent.appendChild( element );
  809. pushElement( element );
  810. };
  811. // `pushEventListener` adds an event listener to the gc stack
  812. var pushEventListener = function( target, type, listenerFunction ) {
  813. eventListenerList.push( { target:target, type:type, listener:listenerFunction } );
  814. };
  815. // `addEventListener` combines DOM addEventListener with gc.pushEventListener
  816. var addEventListener = function( target, type, listenerFunction ) {
  817. target.addEventListener( type, listenerFunction );
  818. pushEventListener( target, type, listenerFunction );
  819. };
  820. // `pushCallback` If the above utilities are not enough, plugins can add their own callback
  821. // function to do arbitrary things.
  822. var pushCallback = function( callback ) {
  823. callbackList.push( callback );
  824. };
  825. pushCallback( function( rootId ) { resetStartingState( rootId ); } );
  826. // `teardown` will
  827. // - execute all callbacks in LIFO order
  828. // - call `removeChild` on all DOM elements in LIFO order
  829. // - call `removeEventListener` on all event listeners in LIFO order
  830. // The goal of a teardown is to return to the same state that the DOM was before
  831. // `impress().init()` was called.
  832. var teardown = function() {
  833. // Execute the callbacks in LIFO order
  834. var i; // Needed by jshint
  835. for ( i = callbackList.length - 1; i >= 0; i-- ) {
  836. callbackList[ i ]( rootId );
  837. }
  838. callbackList = [];
  839. for ( i = 0; i < elementList.length; i++ ) {
  840. elementList[ i ].parentElement.removeChild( elementList[ i ] );
  841. }
  842. elementList = [];
  843. for ( i = 0; i < eventListenerList.length; i++ ) {
  844. var target = eventListenerList[ i ].target;
  845. var type = eventListenerList[ i ].type;
  846. var listener = eventListenerList[ i ].listener;
  847. target.removeEventListener( type, listener );
  848. }
  849. };
  850. var lib = {
  851. pushElement: pushElement,
  852. appendChild: appendChild,
  853. pushEventListener: pushEventListener,
  854. addEventListener: addEventListener,
  855. pushCallback: pushCallback,
  856. teardown: teardown
  857. };
  858. roots[ rootId ] = lib;
  859. rootsCount++;
  860. return lib;
  861. };
  862. // Let impress core know about the existence of this library
  863. window.impress.addLibraryFactory( { gc: libraryFactory } );
  864. // CORE INIT
  865. // The library factory (gc(rootId)) is called at the beginning of impress(rootId).init()
  866. // For the purposes of teardown(), we can use this as an opportunity to save the state
  867. // of a few things in the DOM in their virgin state, before impress().init() did anything.
  868. // Note: These could also be recorded by the code in impress.js core as these values
  869. // are changed, but in an effort to not deviate too much from upstream, I'm adding
  870. // them here rather than the core itself.
  871. var recordStartingState = function( rootId ) {
  872. startingState.roots[ rootId ] = {};
  873. startingState.roots[ rootId ].steps = [];
  874. // Record whether the steps have an id or not
  875. var steps = document.getElementById( rootId ).querySelectorAll( ".step" );
  876. for ( var i = 0; i < steps.length; i++ ) {
  877. var el = steps[ i ];
  878. startingState.roots[ rootId ].steps.push( {
  879. el: el,
  880. id: el.getAttribute( "id" )
  881. } );
  882. }
  883. // In the rare case of multiple roots, the following is changed on first init() and
  884. // reset at last tear().
  885. if ( rootsCount === 0 ) {
  886. startingState.body = {};
  887. // It is customary for authors to set body.class="impress-not-supported" as a starting
  888. // value, which can then be removed by impress().init(). But it is not required.
  889. // Remember whether it was there or not.
  890. if ( document.body.classList.contains( "impress-not-supported" ) ) {
  891. startingState.body.impressNotSupported = true;
  892. } else {
  893. startingState.body.impressNotSupported = false;
  894. }
  895. // If there's a <meta name="viewport"> element, its contents will be overwritten by init
  896. var metas = document.head.querySelectorAll( "meta" );
  897. for ( i = 0; i < metas.length; i++ ) {
  898. var m = metas[ i ];
  899. if ( m.name === "viewport" ) {
  900. startingState.meta = m.content;
  901. }
  902. }
  903. }
  904. };
  905. // CORE TEARDOWN
  906. var resetStartingState = function( rootId ) {
  907. // Reset body element
  908. document.body.classList.remove( "impress-enabled" );
  909. document.body.classList.remove( "impress-disabled" );
  910. var root = document.getElementById( rootId );
  911. var activeId = root.querySelector( ".active" ).id;
  912. document.body.classList.remove( "impress-on-" + activeId );
  913. document.documentElement.style.height = "";
  914. document.body.style.height = "";
  915. document.body.style.overflow = "";
  916. // Remove style values from the root and step elements
  917. // Note: We remove the ones set by impress.js core. Otoh, we didn't preserve any original
  918. // values. A more sophisticated implementation could keep track of original values and then
  919. // reset those.
  920. var steps = root.querySelectorAll( ".step" );
  921. for ( var i = 0; i < steps.length; i++ ) {
  922. steps[ i ].classList.remove( "future" );
  923. steps[ i ].classList.remove( "past" );
  924. steps[ i ].classList.remove( "present" );
  925. steps[ i ].classList.remove( "active" );
  926. steps[ i ].style.position = "";
  927. steps[ i ].style.transform = "";
  928. steps[ i ].style[ "transform-style" ] = "";
  929. }
  930. root.style.position = "";
  931. root.style[ "transform-origin" ] = "";
  932. root.style.transition = "";
  933. root.style[ "transform-style" ] = "";
  934. root.style.top = "";
  935. root.style.left = "";
  936. root.style.transform = "";
  937. // Reset id of steps ("step-1" id's are auto generated)
  938. steps = startingState.roots[ rootId ].steps;
  939. var step;
  940. while ( step = steps.pop() ) {
  941. if ( step.id === null ) {
  942. step.el.removeAttribute( "id" );
  943. } else {
  944. step.el.setAttribute( "id", step.id );
  945. }
  946. }
  947. delete startingState.roots[ rootId ];
  948. // Move step div elements away from canvas, then delete canvas
  949. // Note: There's an implicit assumption here that the canvas div is the only child element
  950. // of the root div. If there would be something else, it's gonna be lost.
  951. var canvas = root.firstChild;
  952. var canvasHTML = canvas.innerHTML;
  953. root.innerHTML = canvasHTML;
  954. if ( roots[ rootId ] !== undefined ) {
  955. delete roots[ rootId ];
  956. rootsCount--;
  957. }
  958. if ( rootsCount === 0 ) {
  959. // In the rare case that more than one impress root elements were initialized, these
  960. // are only reset when all are uninitialized.
  961. document.body.classList.remove( "impress-supported" );
  962. if ( startingState.body.impressNotSupported ) {
  963. document.body.classList.add( "impress-not-supported" );
  964. }
  965. // We need to remove or reset the meta element inserted by impress.js
  966. var metas = document.head.querySelectorAll( "meta" );
  967. for ( i = 0; i < metas.length; i++ ) {
  968. var m = metas[ i ];
  969. if ( m.name === "viewport" ) {
  970. if ( startingState.meta !== undefined ) {
  971. m.content = startingState.meta;
  972. } else {
  973. m.parentElement.removeChild( m );
  974. }
  975. }
  976. }
  977. }
  978. };
  979. } )( document, window );
  980. /**
  981. * Common utility functions
  982. *
  983. * Copyright 2011-2012 Bartek Szopka (@bartaz)
  984. * Henrik Ingo (c) 2016
  985. * MIT License
  986. */
  987. ( function( document, window ) {
  988. "use strict";
  989. var roots = [];
  990. var libraryFactory = function( rootId ) {
  991. if ( roots[ rootId ] ) {
  992. return roots[ rootId ];
  993. }
  994. // `$` returns first element for given CSS `selector` in the `context` of
  995. // the given element or whole document.
  996. var $ = function( selector, context ) {
  997. context = context || document;
  998. return context.querySelector( selector );
  999. };
  1000. // `$$` return an array of elements for given CSS `selector` in the `context` of
  1001. // the given element or whole document.
  1002. var $$ = function( selector, context ) {
  1003. context = context || document;
  1004. return arrayify( context.querySelectorAll( selector ) );
  1005. };
  1006. // `arrayify` takes an array-like object and turns it into real Array
  1007. // to make all the Array.prototype goodness available.
  1008. var arrayify = function( a ) {
  1009. return [].slice.call( a );
  1010. };
  1011. // `byId` returns element with given `id` - you probably have guessed that ;)
  1012. var byId = function( id ) {
  1013. return document.getElementById( id );
  1014. };
  1015. // `getElementFromHash` returns an element located by id from hash part of
  1016. // window location.
  1017. var getElementFromHash = function() {
  1018. // Get id from url # by removing `#` or `#/` from the beginning,
  1019. // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work
  1020. return byId( window.location.hash.replace( /^#\/?/, "" ) );
  1021. };
  1022. // Throttling function calls, by Remy Sharp
  1023. // http://remysharp.com/2010/07/21/throttling-function-calls/
  1024. var throttle = function( fn, delay ) {
  1025. var timer = null;
  1026. return function() {
  1027. var context = this, args = arguments;
  1028. window.clearTimeout( timer );
  1029. timer = window.setTimeout( function() {
  1030. fn.apply( context, args );
  1031. }, delay );
  1032. };
  1033. };
  1034. // `toNumber` takes a value given as `numeric` parameter and tries to turn
  1035. // it into a number. If it is not possible it returns 0 (or other value
  1036. // given as `fallback`).
  1037. var toNumber = function( numeric, fallback ) {
  1038. return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );
  1039. };
  1040. // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data
  1041. // and triggers it on element given as `el`.
  1042. var triggerEvent = function( el, eventName, detail ) {
  1043. var event = document.createEvent( "CustomEvent" );
  1044. event.initCustomEvent( eventName, true, true, detail );
  1045. el.dispatchEvent( event );
  1046. };
  1047. var lib = {
  1048. $: $,
  1049. $$: $$,
  1050. arrayify: arrayify,
  1051. byId: byId,
  1052. getElementFromHash: getElementFromHash,
  1053. throttle: throttle,
  1054. toNumber: toNumber,
  1055. triggerEvent: triggerEvent
  1056. };
  1057. roots[ rootId ] = lib;
  1058. return lib;
  1059. };
  1060. // Let impress core know about the existence of this library
  1061. window.impress.addLibraryFactory( { util: libraryFactory } );
  1062. } )( document, window );
  1063. /**
  1064. * Autoplay plugin - Automatically advance slideshow after N seconds
  1065. *
  1066. * Copyright 2016 Henrik Ingo, henrik.ingo@avoinelama.fi
  1067. * Released under the MIT license.
  1068. */
  1069. /* global clearTimeout, setTimeout, document */
  1070. ( function( document ) {
  1071. "use strict";
  1072. var autoplayDefault = 0;
  1073. var currentStepTimeout = 0;
  1074. var api = null;
  1075. var timeoutHandle = null;
  1076. var root = null;
  1077. var util;
  1078. // On impress:init, check whether there is a default setting, as well as
  1079. // handle step-1.
  1080. document.addEventListener( "impress:init", function( event ) {
  1081. util = event.detail.api.lib.util;
  1082. // Getting API from event data instead of global impress().init().
  1083. // You don't even need to know what is the id of the root element
  1084. // or anything. `impress:init` event data gives you everything you
  1085. // need to control the presentation that was just initialized.
  1086. api = event.detail.api;
  1087. root = event.target;
  1088. // Element attributes starting with "data-", become available under
  1089. // element.dataset. In addition hyphenized words become camelCased.
  1090. var data = root.dataset;
  1091. if ( data.autoplay ) {
  1092. autoplayDefault = util.toNumber( data.autoplay, 0 );
  1093. }
  1094. var toolbar = document.querySelector( "#impress-toolbar" );
  1095. if ( toolbar ) {
  1096. addToolbarButton( toolbar );
  1097. }
  1098. api.lib.gc.pushCallback( function() {
  1099. clearTimeout( timeoutHandle );
  1100. } );
  1101. // Note that right after impress:init event, also impress:stepenter is
  1102. // triggered for the first slide, so that's where code flow continues.
  1103. }, false );
  1104. // If default autoplay time was defined in the presentation root, or
  1105. // in this step, set timeout.
  1106. var reloadTimeout = function( event ) {
  1107. var step = event.target;
  1108. currentStepTimeout = util.toNumber( step.dataset.autoplay, autoplayDefault );
  1109. if ( status === "paused" ) {
  1110. setAutoplayTimeout( 0 );
  1111. } else {
  1112. setAutoplayTimeout( currentStepTimeout );
  1113. }
  1114. };
  1115. document.addEventListener( "impress:stepenter", function( event ) {
  1116. reloadTimeout( event );
  1117. }, false );
  1118. document.addEventListener( "impress:substep:stepleaveaborted", function( event ) {
  1119. reloadTimeout( event );
  1120. }, false );
  1121. /**
  1122. * Set timeout after which we move to next() step.
  1123. */
  1124. var setAutoplayTimeout = function( timeout ) {
  1125. if ( timeoutHandle ) {
  1126. clearTimeout( timeoutHandle );
  1127. }
  1128. if ( timeout > 0 ) {
  1129. timeoutHandle = setTimeout( function() { api.next(); }, timeout * 1000 );
  1130. }
  1131. setButtonText();
  1132. };
  1133. /*** Toolbar plugin integration *******************************************/
  1134. var status = "not clicked";
  1135. var toolbarButton = null;
  1136. // Copied from core impress.js. Good candidate for moving to a utilities collection.
  1137. var triggerEvent = function( el, eventName, detail ) {
  1138. var event = document.createEvent( "CustomEvent" );
  1139. event.initCustomEvent( eventName, true, true, detail );
  1140. el.dispatchEvent( event );
  1141. };
  1142. var makeDomElement = function( html ) {
  1143. var tempDiv = document.createElement( "div" );
  1144. tempDiv.innerHTML = html;
  1145. return tempDiv.firstChild;
  1146. };
  1147. var toggleStatus = function() {
  1148. if ( currentStepTimeout > 0 && status !== "paused" ) {
  1149. status = "paused";
  1150. } else {
  1151. status = "playing";
  1152. }
  1153. };
  1154. var getButtonText = function() {
  1155. if ( currentStepTimeout > 0 && status !== "paused" ) {
  1156. return "||"; // Pause
  1157. } else {
  1158. return "&#9654;"; // Play
  1159. }
  1160. };
  1161. var setButtonText = function() {
  1162. if ( toolbarButton ) {
  1163. // Keep button size the same even if label content is changing
  1164. var buttonWidth = toolbarButton.offsetWidth;
  1165. var buttonHeight = toolbarButton.offsetHeight;
  1166. toolbarButton.innerHTML = getButtonText();
  1167. if ( !toolbarButton.style.width ) {
  1168. toolbarButton.style.width = buttonWidth + "px";
  1169. }
  1170. if ( !toolbarButton.style.height ) {
  1171. toolbarButton.style.height = buttonHeight + "px";
  1172. }
  1173. }
  1174. };
  1175. var addToolbarButton = function( toolbar ) {
  1176. var html = '<button id="impress-autoplay-playpause" ' + // jshint ignore:line
  1177. 'title="Autoplay" class="impress-autoplay">' + // jshint ignore:line
  1178. getButtonText() + "</button>"; // jshint ignore:line
  1179. toolbarButton = makeDomElement( html );
  1180. toolbarButton.addEventListener( "click", function() {
  1181. toggleStatus();
  1182. if ( status === "playing" ) {
  1183. if ( autoplayDefault === 0 ) {
  1184. autoplayDefault = 7;
  1185. }
  1186. if ( currentStepTimeout === 0 ) {
  1187. currentStepTimeout = autoplayDefault;
  1188. }
  1189. setAutoplayTimeout( currentStepTimeout );
  1190. } else if ( status === "paused" ) {
  1191. setAutoplayTimeout( 0 );
  1192. }
  1193. } );
  1194. triggerEvent( toolbar, "impress:toolbar:appendChild",
  1195. { group: 10, element: toolbarButton } );
  1196. };
  1197. } )( document );
  1198. /**
  1199. * Blackout plugin
  1200. *
  1201. * Press Ctrl+b to hide all slides, and Ctrl+b again to show them.
  1202. * Also navigating to a different slide will show them again (impress:stepleave).
  1203. *
  1204. * Copyright 2014 @Strikeskids
  1205. * Released under the MIT license.
  1206. */
  1207. /* global document */
  1208. ( function( document ) {
  1209. "use strict";
  1210. var canvas = null;
  1211. var blackedOut = false;
  1212. // While waiting for a shared library of utilities, copying these 2 from main impress.js
  1213. var css = function( el, props ) {
  1214. var key, pkey;
  1215. for ( key in props ) {
  1216. if ( props.hasOwnProperty( key ) ) {
  1217. pkey = pfx( key );
  1218. if ( pkey !== null ) {
  1219. el.style[ pkey ] = props[ key ];
  1220. }
  1221. }
  1222. }
  1223. return el;
  1224. };
  1225. var pfx = ( function() {
  1226. var style = document.createElement( "dummy" ).style,
  1227. prefixes = "Webkit Moz O ms Khtml".split( " " ),
  1228. memory = {};
  1229. return function( prop ) {
  1230. if ( typeof memory[ prop ] === "undefined" ) {
  1231. var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
  1232. props = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
  1233. memory[ prop ] = null;
  1234. for ( var i in props ) {
  1235. if ( style[ props[ i ] ] !== undefined ) {
  1236. memory[ prop ] = props[ i ];
  1237. break;
  1238. }
  1239. }
  1240. }
  1241. return memory[ prop ];
  1242. };
  1243. } )();
  1244. var removeBlackout = function() {
  1245. if ( blackedOut ) {
  1246. css( canvas, {
  1247. display: "block"
  1248. } );
  1249. blackedOut = false;
  1250. }
  1251. };
  1252. var blackout = function() {
  1253. if ( blackedOut ) {
  1254. removeBlackout();
  1255. } else {
  1256. css( canvas, {
  1257. display: ( blackedOut = !blackedOut ) ? "none" : "block"
  1258. } );
  1259. blackedOut = true;
  1260. }
  1261. };
  1262. // Wait for impress.js to be initialized
  1263. document.addEventListener( "impress:init", function( event ) {
  1264. var api = event.detail.api;
  1265. var root = event.target;
  1266. canvas = root.firstElementChild;
  1267. var gc = api.lib.gc;
  1268. gc.addEventListener( document, "keydown", function( event ) {
  1269. if ( event.ctrlKey && event.keyCode === 66 ) {
  1270. event.preventDefault();
  1271. if ( !blackedOut ) {
  1272. blackout();
  1273. } else {
  1274. // Note: This doesn't work on Firefox. It will set display:block,
  1275. // but slides only become visible again upon next transition, which
  1276. // forces some kind of redraw. Works as intended on Chrome.
  1277. removeBlackout();
  1278. }
  1279. }
  1280. }, false );
  1281. gc.addEventListener( document, "keyup", function( event ) {
  1282. if ( event.ctrlKey && event.keyCode === 66 ) {
  1283. event.preventDefault();
  1284. }
  1285. }, false );
  1286. }, false );
  1287. document.addEventListener( "impress:stepleave", function() {
  1288. removeBlackout();
  1289. }, false );
  1290. } )( document );
  1291. /**
  1292. * Extras Plugin
  1293. *
  1294. * This plugin performs initialization (like calling mermaid.initialize())
  1295. * for the extras/ plugins if they are loaded into a presentation.
  1296. *
  1297. * See README.md for details.
  1298. *
  1299. * Copyright 2016 Henrik Ingo (@henrikingo)
  1300. * Released under the MIT license.
  1301. */
  1302. /* global markdown, hljs, mermaid, impress, document, window */
  1303. ( function( document, window ) {
  1304. "use strict";
  1305. var preInit = function() {
  1306. if ( window.markdown ) {
  1307. // Unlike the other extras, Markdown.js doesn't by default do anything in
  1308. // particular. We do it ourselves here.
  1309. // In addition, we use "-----" as a delimiter for new slide.
  1310. // Query all .markdown elements and translate to HTML
  1311. var markdownDivs = document.querySelectorAll( ".markdown" );
  1312. for ( var idx = 0; idx < markdownDivs.length; idx++ ) {
  1313. var element = markdownDivs[ idx ];
  1314. var slides = element.textContent.split( /^-----$/m );
  1315. var i = slides.length - 1;
  1316. element.innerHTML = markdown.toHTML( slides[ i ] );
  1317. // If there's an id, unset it for last, and all other, elements,
  1318. // and then set it for the first.
  1319. var id = null;
  1320. if ( element.id ) {
  1321. id = element.id;
  1322. element.id = "";
  1323. }
  1324. i--;
  1325. while ( i >= 0 ) {
  1326. var newElement = element.cloneNode( false );
  1327. newElement.innerHTML = markdown.toHTML( slides[ i ] );
  1328. element.parentNode.insertBefore( newElement, element );
  1329. element = newElement;
  1330. i--;
  1331. }
  1332. if ( id !== null ) {
  1333. element.id = id;
  1334. }
  1335. }
  1336. } // Markdown
  1337. if ( window.hljs ) {
  1338. hljs.initHighlightingOnLoad();
  1339. }
  1340. if ( window.mermaid ) {
  1341. mermaid.initialize( { startOnLoad:true } );
  1342. }
  1343. };
  1344. // Register the plugin to be called in pre-init phase
  1345. // Note: Markdown.js should run early/first, because it creates new div elements.
  1346. // So add this with a lower-than-default weight.
  1347. impress.addPreInitPlugin( preInit, 1 );
  1348. } )( document, window );
  1349. /**
  1350. * Form support
  1351. *
  1352. * Functionality to better support use of input, textarea, button... elements in a presentation.
  1353. *
  1354. * Currently this does only one single thing: On impress:stepleave, de-focus any potentially active
  1355. * element. This is to prevent the focus from being left in a form element that is no longer visible
  1356. * in the window, and user therefore typing garbage into the form.
  1357. *
  1358. * TODO: Currently it is not possible to use TAB to navigate between form elements. Impress.js, and
  1359. * in particular the navigation plugin, unfortunately must fully take control of the tab key,
  1360. * otherwise a user could cause the browser to scroll to a link or button that's not on the current
  1361. * step. However, it could be possible to allow tab navigation between form elements, as long as
  1362. * they are on the active step. This is a topic for further study.
  1363. *
  1364. * Copyright 2016 Henrik Ingo
  1365. * MIT License
  1366. */
  1367. /* global document */
  1368. ( function( document ) {
  1369. "use strict";
  1370. document.addEventListener( "impress:stepleave", function() {
  1371. document.activeElement.blur();
  1372. }, false );
  1373. } )( document );
  1374. /**
  1375. * Goto Plugin
  1376. *
  1377. * The goto plugin is a pre-stepleave plugin. It is executed before impress:stepleave,
  1378. * and will alter the destination where to transition next.
  1379. *
  1380. * Example:
  1381. *
  1382. * <!-- When leaving this step, go directly to "step-5" -->
  1383. * <div class="step" data-goto="step-5">
  1384. *
  1385. * <!-- When leaving this step with next(), go directly to "step-5", instead of next step.
  1386. * If moving backwards to previous step - e.g. prev() instead of next() -
  1387. * then go to "step-1". -->
  1388. * <div class="step" data-goto-next="step-5" data-goto-prev="step-1">
  1389. *
  1390. * <!-- data-goto-key-list and data-goto-next-list allow you to build advanced non-linear
  1391. * navigation. -->
  1392. * <div class="step"
  1393. * data-goto-key-list="ArrowUp ArrowDown ArrowRight ArrowLeft"
  1394. * data-goto-next-list="step-4 step-3 step-2 step-5">
  1395. *
  1396. * See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values for a table
  1397. * of what strings to use for each key.
  1398. *
  1399. * Copyright 2016-2017 Henrik Ingo (@henrikingo)
  1400. * Released under the MIT license.
  1401. */
  1402. /* global window, document, impress */
  1403. ( function( document, window ) {
  1404. "use strict";
  1405. var lib;
  1406. document.addEventListener( "impress:init", function( event ) {
  1407. lib = event.detail.api.lib;
  1408. }, false );
  1409. var isNumber = function( numeric ) {
  1410. return !isNaN( numeric );
  1411. };
  1412. var goto = function( event ) {
  1413. if ( ( !event ) || ( !event.target ) ) {
  1414. return;
  1415. }
  1416. var data = event.target.dataset;
  1417. var steps = document.querySelectorAll( ".step" );
  1418. // Data-goto-key-list="" & data-goto-next-list="" //////////////////////////////////////////
  1419. if ( data.gotoKeyList !== undefined &&
  1420. data.gotoNextList !== undefined &&
  1421. event.origEvent !== undefined &&
  1422. event.origEvent.key !== undefined ) {
  1423. var keylist = data.gotoKeyList.split( " " );
  1424. var nextlist = data.gotoNextList.split( " " );
  1425. if ( keylist.length !== nextlist.length ) {
  1426. window.console.log(
  1427. "impress goto plugin: data-goto-key-list and data-goto-next-list don't match:"
  1428. );
  1429. window.console.log( keylist );
  1430. window.console.log( nextlist );
  1431. // Don't return, allow the other categories to work despite this error
  1432. } else {
  1433. var index = keylist.indexOf( event.origEvent.key );
  1434. if ( index >= 0 ) {
  1435. var next = nextlist[ index ];
  1436. if ( isNumber( next ) ) {
  1437. event.detail.next = steps[ next ];
  1438. // If the new next element has its own transitionDuration, we're responsible
  1439. // for setting that on the event as well
  1440. event.detail.transitionDuration = lib.util.toNumber(
  1441. event.detail.next.dataset.transitionDuration,
  1442. event.detail.transitionDuration
  1443. );
  1444. return;
  1445. } else {
  1446. var newTarget = document.getElementById( next );
  1447. if ( newTarget && newTarget.classList.contains( "step" ) ) {
  1448. event.detail.next = newTarget;
  1449. event.detail.transitionDuration = lib.util.toNumber(
  1450. event.detail.next.dataset.transitionDuration,
  1451. event.detail.transitionDuration
  1452. );
  1453. return;
  1454. } else {
  1455. window.console.log( "impress goto plugin: " + next +
  1456. " is not a step in this impress presentation." );
  1457. }
  1458. }
  1459. }
  1460. }
  1461. }
  1462. // Data-goto-next="" & data-goto-prev="" ///////////////////////////////////////////////////
  1463. // Handle event.target data-goto-next attribute
  1464. if ( isNumber( data.gotoNext ) && event.detail.reason === "next" ) {
  1465. event.detail.next = steps[ data.gotoNext ];
  1466. // If the new next element has its own transitionDuration, we're responsible for setting
  1467. // that on the event as well
  1468. event.detail.transitionDuration = lib.util.toNumber(
  1469. event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
  1470. );
  1471. return;
  1472. }
  1473. if ( data.gotoNext && event.detail.reason === "next" ) {
  1474. var newTarget = document.getElementById( data.gotoNext ); // jshint ignore:line
  1475. if ( newTarget && newTarget.classList.contains( "step" ) ) {
  1476. event.detail.next = newTarget;
  1477. event.detail.transitionDuration = lib.util.toNumber(
  1478. event.detail.next.dataset.transitionDuration,
  1479. event.detail.transitionDuration
  1480. );
  1481. return;
  1482. } else {
  1483. window.console.log( "impress goto plugin: " + data.gotoNext +
  1484. " is not a step in this impress presentation." );
  1485. }
  1486. }
  1487. // Handle event.target data-goto-prev attribute
  1488. if ( isNumber( data.gotoPrev ) && event.detail.reason === "prev" ) {
  1489. event.detail.next = steps[ data.gotoPrev ];
  1490. event.detail.transitionDuration = lib.util.toNumber(
  1491. event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
  1492. );
  1493. return;
  1494. }
  1495. if ( data.gotoPrev && event.detail.reason === "prev" ) {
  1496. var newTarget = document.getElementById( data.gotoPrev ); // jshint ignore:line
  1497. if ( newTarget && newTarget.classList.contains( "step" ) ) {
  1498. event.detail.next = newTarget;
  1499. event.detail.transitionDuration = lib.util.toNumber(
  1500. event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
  1501. );
  1502. return;
  1503. } else {
  1504. window.console.log( "impress goto plugin: " + data.gotoPrev +
  1505. " is not a step in this impress presentation." );
  1506. }
  1507. }
  1508. // Data-goto="" ///////////////////////////////////////////////////////////////////////////
  1509. // Handle event.target data-goto attribute
  1510. if ( isNumber( data.goto ) ) {
  1511. event.detail.next = steps[ data.goto ];
  1512. event.detail.transitionDuration = lib.util.toNumber(
  1513. event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
  1514. );
  1515. return;
  1516. }
  1517. if ( data.goto ) {
  1518. var newTarget = document.getElementById( data.goto ); // jshint ignore:line
  1519. if ( newTarget && newTarget.classList.contains( "step" ) ) {
  1520. event.detail.next = newTarget;
  1521. event.detail.transitionDuration = lib.util.toNumber(
  1522. event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
  1523. );
  1524. return;
  1525. } else {
  1526. window.console.log( "impress goto plugin: " + data.goto +
  1527. " is not a step in this impress presentation." );
  1528. }
  1529. }
  1530. };
  1531. // Register the plugin to be called in pre-stepleave phase
  1532. impress.addPreStepLeavePlugin( goto );
  1533. } )( document, window );
  1534. /**
  1535. * Help popup plugin
  1536. *
  1537. * Example:
  1538. *
  1539. * <!-- Show a help popup at start, or if user presses "H" -->
  1540. * <div id="impress-help"></div>
  1541. *
  1542. * For developers:
  1543. *
  1544. * Typical use for this plugin, is for plugins that support some keypress, to add a line
  1545. * to the help popup produced by this plugin. For example "P: Presenter console".
  1546. *
  1547. * Copyright 2016 Henrik Ingo (@henrikingo)
  1548. * Released under the MIT license.
  1549. */
  1550. /* global window, document */
  1551. ( function( document, window ) {
  1552. "use strict";
  1553. var rows = [];
  1554. var timeoutHandle;
  1555. var triggerEvent = function( el, eventName, detail ) {
  1556. var event = document.createEvent( "CustomEvent" );
  1557. event.initCustomEvent( eventName, true, true, detail );
  1558. el.dispatchEvent( event );
  1559. };
  1560. var renderHelpDiv = function() {
  1561. var helpDiv = document.getElementById( "impress-help" );
  1562. if ( helpDiv ) {
  1563. var html = [];
  1564. for ( var row in rows ) {
  1565. for ( var arrayItem in row ) {
  1566. html.push( rows[ row ][ arrayItem ] );
  1567. }
  1568. }
  1569. if ( html ) {
  1570. helpDiv.innerHTML = "<table>\n" + html.join( "\n" ) + "</table>\n";
  1571. }
  1572. }
  1573. };
  1574. var toggleHelp = function() {
  1575. var helpDiv = document.getElementById( "impress-help" );
  1576. if ( !helpDiv ) {
  1577. return;
  1578. }
  1579. if ( helpDiv.style.display === "block" ) {
  1580. helpDiv.style.display = "none";
  1581. } else {
  1582. helpDiv.style.display = "block";
  1583. window.clearTimeout( timeoutHandle );
  1584. }
  1585. };
  1586. document.addEventListener( "keyup", function( event ) {
  1587. // Check that event target is html or body element.
  1588. if ( event.target.nodeName === "BODY" || event.target.nodeName === "HTML" ) {
  1589. if ( event.keyCode === 72 ) { // "h"
  1590. event.preventDefault();
  1591. toggleHelp();
  1592. }
  1593. }
  1594. }, false );
  1595. // API
  1596. // Other plugins can add help texts, typically if they support an action on a keypress.
  1597. /**
  1598. * Add a help text to the help popup.
  1599. *
  1600. * :param: e.detail.command Example: "H"
  1601. * :param: e.detail.text Example: "Show this help."
  1602. * :param: e.detail.row Row index from 0 to 9 where to place this help text. Example: 0
  1603. */
  1604. document.addEventListener( "impress:help:add", function( e ) {
  1605. // The idea is for the sender of the event to supply a unique row index, used for sorting.
  1606. // But just in case two plugins would ever use the same row index, we wrap each row into
  1607. // its own array. If there are more than one entry for the same index, they are shown in
  1608. // first come, first serve ordering.
  1609. var rowIndex = e.detail.row;
  1610. if ( typeof rows[ rowIndex ] !== "object" || !rows[ rowIndex ].isArray ) {
  1611. rows[ rowIndex ] = [];
  1612. }
  1613. rows[ e.detail.row ].push( "<tr><td><strong>" + e.detail.command + "</strong></td><td>" +
  1614. e.detail.text + "</td></tr>" );
  1615. renderHelpDiv();
  1616. } );
  1617. document.addEventListener( "impress:init", function( e ) {
  1618. renderHelpDiv();
  1619. // At start, show the help for 7 seconds.
  1620. var helpDiv = document.getElementById( "impress-help" );
  1621. if ( helpDiv ) {
  1622. helpDiv.style.display = "block";
  1623. timeoutHandle = window.setTimeout( function() {
  1624. var helpDiv = document.getElementById( "impress-help" );
  1625. helpDiv.style.display = "none";
  1626. }, 7000 );
  1627. // Regster callback to empty the help div on teardown
  1628. var api = e.detail.api;
  1629. api.lib.gc.pushCallback( function() {
  1630. window.clearTimeout( timeoutHandle );
  1631. helpDiv.style.display = "";
  1632. helpDiv.innerHTML = "";
  1633. rows = [];
  1634. } );
  1635. }
  1636. // Use our own API to register the help text for "h"
  1637. triggerEvent( document, "impress:help:add",
  1638. { command: "H", text: "Show this help", row: 0 } );
  1639. } );
  1640. } )( document, window );
  1641. /**
  1642. * Adds a presenter console to impress.js
  1643. *
  1644. * MIT Licensed, see license.txt.
  1645. *
  1646. * Copyright 2012, 2013, 2015 impress-console contributors (see README.txt)
  1647. *
  1648. * version: 1.3-dev
  1649. *
  1650. */
  1651. // This file contains so much HTML, that we will just respectfully disagree about js
  1652. /* jshint quotmark:single */
  1653. /* global navigator, top, setInterval, clearInterval, document, window */
  1654. ( function( document, window ) {
  1655. 'use strict';
  1656. // TODO: Move this to src/lib/util.js
  1657. var triggerEvent = function( el, eventName, detail ) {
  1658. var event = document.createEvent( 'CustomEvent' );
  1659. event.initCustomEvent( eventName, true, true, detail );
  1660. el.dispatchEvent( event );
  1661. };
  1662. // Create Language object depending on browsers language setting
  1663. var lang;
  1664. switch ( navigator.language ) {
  1665. case 'de':
  1666. lang = {
  1667. 'noNotes': '<div class="noNotes">Keine Notizen hierzu</div>',
  1668. 'restart': 'Neustart',
  1669. 'clickToOpen': 'Klicken um Sprecherkonsole zu öffnen',
  1670. 'prev': 'zurück',
  1671. 'next': 'weiter',
  1672. 'loading': 'initalisiere',
  1673. 'ready': 'Bereit',
  1674. 'moving': 'in Bewegung',
  1675. 'useAMPM': false
  1676. };
  1677. break;
  1678. case 'en': // jshint ignore:line
  1679. default : // jshint ignore:line
  1680. lang = {
  1681. 'noNotes': '<div class="noNotes">No notes for this step</div>',
  1682. 'restart': 'Restart',
  1683. 'clickToOpen': 'Click to open speaker console',
  1684. 'prev': 'Prev',
  1685. 'next': 'Next',
  1686. 'loading': 'Loading',
  1687. 'ready': 'Ready',
  1688. 'moving': 'Moving',
  1689. 'useAMPM': false
  1690. };
  1691. break;
  1692. }
  1693. // Settings to set iframe in speaker console
  1694. const preViewDefaultFactor = 0.7;
  1695. const preViewMinimumFactor = 0.5;
  1696. const preViewGap = 4;
  1697. // This is the default template for the speaker console window
  1698. const consoleTemplate = '<!DOCTYPE html>' +
  1699. '<html id="impressconsole"><head>' +
  1700. // Order is important: If user provides a cssFile, those will win, because they're later
  1701. '{{cssStyle}}' +
  1702. '{{cssLink}}' +
  1703. '</head><body>' +
  1704. '<div id="console">' +
  1705. '<div id="views">' +
  1706. '<iframe id="slideView" scrolling="no"></iframe>' +
  1707. '<iframe id="preView" scrolling="no"></iframe>' +
  1708. '<div id="blocker"></div>' +
  1709. '</div>' +
  1710. '<div id="notes"></div>' +
  1711. '</div>' +
  1712. '<div id="controls"> ' +
  1713. '<div id="prev"><a href="#" onclick="impress().prev(); return false;" />' +
  1714. '{{prev}}</a></div>' +
  1715. '<div id="next"><a href="#" onclick="impress().next(); return false;" />' +
  1716. '{{next}}</a></div>' +
  1717. '<div id="clock">--:--</div>' +
  1718. '<div id="timer" onclick="timerReset()">00m 00s</div>' +
  1719. '<div id="status">{{loading}}</div>' +
  1720. '</div>' +
  1721. '</body></html>';
  1722. // Default css location
  1723. var cssFileOldDefault = 'css/impressConsole.css';
  1724. var cssFile = undefined; // jshint ignore:line
  1725. // Css for styling iframs on the console
  1726. var cssFileIframeOldDefault = 'css/iframe.css';
  1727. var cssFileIframe = undefined; // jshint ignore:line
  1728. // All console windows, so that you can call impressConsole() repeatedly.
  1729. var allConsoles = {};
  1730. // Zero padding helper function:
  1731. var zeroPad = function( i ) {
  1732. return ( i < 10 ? '0' : '' ) + i;
  1733. };
  1734. // The console object
  1735. var impressConsole = window.impressConsole = function( rootId ) {
  1736. rootId = rootId || 'impress';
  1737. if ( allConsoles[ rootId ] ) {
  1738. return allConsoles[ rootId ];
  1739. }
  1740. // Root presentation elements
  1741. var root = document.getElementById( rootId );
  1742. var consoleWindow = null;
  1743. var nextStep = function() {
  1744. var classes = '';
  1745. var nextElement = document.querySelector( '.active' );
  1746. // Return to parents as long as there is no next sibling
  1747. while ( !nextElement.nextElementSibling && nextElement.parentNode ) {
  1748. nextElement = nextElement.parentNode;
  1749. }
  1750. nextElement = nextElement.nextElementSibling;
  1751. while ( nextElement ) {
  1752. classes = nextElement.attributes[ 'class' ];
  1753. if ( classes && classes.value.indexOf( 'step' ) !== -1 ) {
  1754. consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.next;
  1755. return nextElement;
  1756. }
  1757. if ( nextElement.firstElementChild ) { // First go into deep
  1758. nextElement = nextElement.firstElementChild;
  1759. } else {
  1760. // Go to next sibling or through parents until there is a next sibling
  1761. while ( !nextElement.nextElementSibling && nextElement.parentNode ) {
  1762. nextElement = nextElement.parentNode;
  1763. }
  1764. nextElement = nextElement.nextElementSibling;
  1765. }
  1766. }
  1767. // No next element. Pick the first
  1768. consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.restart;
  1769. return document.querySelector( '.step' );
  1770. };
  1771. // Sync the notes to the step
  1772. var onStepLeave = function() {
  1773. if ( consoleWindow ) {
  1774. // Set notes to next steps notes.
  1775. var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );
  1776. if ( newNotes ) {
  1777. newNotes = newNotes.innerHTML;
  1778. } else {
  1779. newNotes = lang.noNotes;
  1780. }
  1781. consoleWindow.document.getElementById( 'notes' ).innerHTML = newNotes;
  1782. // Set the views
  1783. var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );
  1784. var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;
  1785. var preSrc = baseURL + '#' + nextStep().id;
  1786. var slideView = consoleWindow.document.getElementById( 'slideView' );
  1787. // Setting them when they are already set causes glithes in Firefox, so check first:
  1788. if ( slideView.src !== slideSrc ) {
  1789. slideView.src = slideSrc;
  1790. }
  1791. var preView = consoleWindow.document.getElementById( 'preView' );
  1792. if ( preView.src !== preSrc ) {
  1793. preView.src = preSrc;
  1794. }
  1795. consoleWindow.document.getElementById( 'status' ).innerHTML =
  1796. '<span class="moving">' + lang.moving + '</span>';
  1797. }
  1798. };
  1799. // Sync the previews to the step
  1800. var onStepEnter = function() {
  1801. if ( consoleWindow ) {
  1802. // We do everything here again, because if you stopped the previos step to
  1803. // early, the onstepleave trigger is not called for that step, so
  1804. // we need this to sync things.
  1805. var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );
  1806. if ( newNotes ) {
  1807. newNotes = newNotes.innerHTML;
  1808. } else {
  1809. newNotes = lang.noNotes;
  1810. }
  1811. var notes = consoleWindow.document.getElementById( 'notes' );
  1812. notes.innerHTML = newNotes;
  1813. notes.scrollTop = 0;
  1814. // Set the views
  1815. var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );
  1816. var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;
  1817. var preSrc = baseURL + '#' + nextStep().id;
  1818. var slideView = consoleWindow.document.getElementById( 'slideView' );
  1819. // Setting them when they are already set causes glithes in Firefox, so check first:
  1820. if ( slideView.src !== slideSrc ) {
  1821. slideView.src = slideSrc;
  1822. }
  1823. var preView = consoleWindow.document.getElementById( 'preView' );
  1824. if ( preView.src !== preSrc ) {
  1825. preView.src = preSrc;
  1826. }
  1827. consoleWindow.document.getElementById( 'status' ).innerHTML =
  1828. '<span class="ready">' + lang.ready + '</span>';
  1829. }
  1830. };
  1831. // Sync substeps
  1832. var onSubstep = function( event ) {
  1833. if ( consoleWindow ) {
  1834. if ( event.detail.reason === 'next' ) {
  1835. onSubstepShow();
  1836. }
  1837. if ( event.detail.reason === 'prev' ) {
  1838. onSubstepHide();
  1839. }
  1840. }
  1841. };
  1842. var onSubstepShow = function() {
  1843. var slideView = consoleWindow.document.getElementById( 'slideView' );
  1844. triggerEventInView( slideView, 'impress:substep:show' );
  1845. };
  1846. var onSubstepHide = function() {
  1847. var slideView = consoleWindow.document.getElementById( 'slideView' );
  1848. triggerEventInView( slideView, 'impress:substep:hide' );
  1849. };
  1850. var triggerEventInView = function( frame, eventName, detail ) {
  1851. // Note: Unfortunately Chrome does not allow createEvent on file:// URLs, so this won't
  1852. // work. This does work on Firefox, and should work if viewing the presentation on a
  1853. // http:// URL on Chrome.
  1854. var event = frame.contentDocument.createEvent( 'CustomEvent' );
  1855. event.initCustomEvent( eventName, true, true, detail );
  1856. frame.contentDocument.dispatchEvent( event );
  1857. };
  1858. var spaceHandler = function() {
  1859. var notes = consoleWindow.document.getElementById( 'notes' );
  1860. if ( notes.scrollTopMax - notes.scrollTop > 20 ) {
  1861. notes.scrollTop = notes.scrollTop + notes.clientHeight * 0.8;
  1862. } else {
  1863. window.impress().next();
  1864. }
  1865. };
  1866. var timerReset = function() {
  1867. consoleWindow.timerStart = new Date();
  1868. };
  1869. // Show a clock
  1870. var clockTick = function() {
  1871. var now = new Date();
  1872. var hours = now.getHours();
  1873. var minutes = now.getMinutes();
  1874. var seconds = now.getSeconds();
  1875. var ampm = '';
  1876. if ( lang.useAMPM ) {
  1877. ampm = ( hours < 12 ) ? 'AM' : 'PM';
  1878. hours = ( hours > 12 ) ? hours - 12 : hours;
  1879. hours = ( hours === 0 ) ? 12 : hours;
  1880. }
  1881. // Clock
  1882. var clockStr = zeroPad( hours ) + ':' + zeroPad( minutes ) + ':' + zeroPad( seconds ) +
  1883. ' ' + ampm;
  1884. consoleWindow.document.getElementById( 'clock' ).firstChild.nodeValue = clockStr;
  1885. // Timer
  1886. seconds = Math.floor( ( now - consoleWindow.timerStart ) / 1000 );
  1887. minutes = Math.floor( seconds / 60 );
  1888. seconds = Math.floor( seconds % 60 );
  1889. consoleWindow.document.getElementById( 'timer' ).firstChild.nodeValue =
  1890. zeroPad( minutes ) + 'm ' + zeroPad( seconds ) + 's';
  1891. if ( !consoleWindow.initialized ) {
  1892. // Nudge the slide windows after load, or they will scrolled wrong on Firefox.
  1893. consoleWindow.document.getElementById( 'slideView' ).contentWindow.scrollTo( 0, 0 );
  1894. consoleWindow.document.getElementById( 'preView' ).contentWindow.scrollTo( 0, 0 );
  1895. consoleWindow.initialized = true;
  1896. }
  1897. };
  1898. var registerKeyEvent = function( keyCodes, handler, window ) {
  1899. if ( window === undefined ) {
  1900. window = consoleWindow;
  1901. }
  1902. // Prevent default keydown action when one of supported key is pressed
  1903. window.document.addEventListener( 'keydown', function( event ) {
  1904. if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&
  1905. keyCodes.indexOf( event.keyCode ) !== -1 ) {
  1906. event.preventDefault();
  1907. }
  1908. }, false );
  1909. // Trigger impress action on keyup
  1910. window.document.addEventListener( 'keyup', function( event ) {
  1911. if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&
  1912. keyCodes.indexOf( event.keyCode ) !== -1 ) {
  1913. handler();
  1914. event.preventDefault();
  1915. }
  1916. }, false );
  1917. };
  1918. var consoleOnLoad = function() {
  1919. var slideView = consoleWindow.document.getElementById( 'slideView' );
  1920. var preView = consoleWindow.document.getElementById( 'preView' );
  1921. // Firefox:
  1922. slideView.contentDocument.body.classList.add( 'impress-console' );
  1923. preView.contentDocument.body.classList.add( 'impress-console' );
  1924. if ( cssFileIframe !== undefined ) {
  1925. slideView.contentDocument.head.insertAdjacentHTML(
  1926. 'beforeend',
  1927. '<link rel="stylesheet" type="text/css" href="' + cssFileIframe + '">'
  1928. );
  1929. preView.contentDocument.head.insertAdjacentHTML(
  1930. 'beforeend',
  1931. '<link rel="stylesheet" type="text/css" href="' + cssFileIframe + '">'
  1932. );
  1933. }
  1934. // Chrome:
  1935. slideView.addEventListener( 'load', function() {
  1936. slideView.contentDocument.body.classList.add( 'impress-console' );
  1937. if ( cssFileIframe !== undefined ) {
  1938. slideView.contentDocument.head.insertAdjacentHTML(
  1939. 'beforeend',
  1940. '<link rel="stylesheet" type="text/css" href="' +
  1941. cssFileIframe + '">'
  1942. );
  1943. }
  1944. } );
  1945. preView.addEventListener( 'load', function() {
  1946. preView.contentDocument.body.classList.add( 'impress-console' );
  1947. if ( cssFileIframe !== undefined ) {
  1948. preView.contentDocument.head.insertAdjacentHTML(
  1949. 'beforeend',
  1950. '<link rel="stylesheet" type="text/css" href="' +
  1951. cssFileIframe + '">' );
  1952. }
  1953. } );
  1954. };
  1955. var open = function() {
  1956. if ( top.isconsoleWindow ) {
  1957. return;
  1958. }
  1959. if ( consoleWindow && !consoleWindow.closed ) {
  1960. consoleWindow.focus();
  1961. } else {
  1962. consoleWindow = window.open( '', 'impressConsole' );
  1963. // If opening failes this may be because the browser prevents this from
  1964. // not (or less) interactive JavaScript...
  1965. if ( consoleWindow == null ) {
  1966. // ... so I add a button to klick.
  1967. // workaround on firefox
  1968. var message = document.createElement( 'div' );
  1969. message.id = 'consoleWindowError';
  1970. message.style.position = 'fixed';
  1971. message.style.left = 0;
  1972. message.style.top = 0;
  1973. message.style.right = 0;
  1974. message.style.bottom = 0;
  1975. message.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
  1976. var onClickStr = 'var x = document.getElementById(\'consoleWindowError\');' +
  1977. 'x.parentNode.removeChild(x);impressConsole().open();';
  1978. message.innerHTML = '<button style="margin: 25vh 25vw;width:50vw;height:50vh;' +
  1979. 'onclick="' + onClickStr + '">' +
  1980. lang.clickToOpen +
  1981. '</button>';
  1982. document.body.appendChild( message );
  1983. return;
  1984. }
  1985. var cssLink = '';
  1986. if ( cssFile !== undefined ) {
  1987. cssLink = '<link rel="stylesheet" type="text/css" media="screen" href="' +
  1988. cssFile + '">';
  1989. }
  1990. // This sets the window location to the main window location, so css can be loaded:
  1991. consoleWindow.document.open();
  1992. // Write the template:
  1993. consoleWindow.document.write(
  1994. // CssStyleStr is lots of inline <style></style> defined at the end of this file
  1995. consoleTemplate.replace( '{{cssStyle}}', cssStyleStr() )
  1996. .replace( '{{cssLink}}', cssLink )
  1997. .replace( /{{.*?}}/gi, function( x ) {
  1998. return lang[ x.substring( 2, x.length - 2 ) ]; }
  1999. )
  2000. );
  2001. consoleWindow.document.title = 'Speaker Console (' + document.title + ')';
  2002. consoleWindow.impress = window.impress;
  2003. // We set this flag so we can detect it later, to prevent infinite popups.
  2004. consoleWindow.isconsoleWindow = true;
  2005. // Set the onload function:
  2006. consoleWindow.onload = consoleOnLoad;
  2007. // Add clock tick
  2008. consoleWindow.timerStart = new Date();
  2009. consoleWindow.timerReset = timerReset;
  2010. consoleWindow.clockInterval = setInterval( allConsoles[ rootId ].clockTick, 1000 );
  2011. // Keyboard navigation handlers
  2012. // 33: pg up, 37: left, 38: up
  2013. registerKeyEvent( [ 33, 37, 38 ], window.impress().prev );
  2014. // 34: pg down, 39: right, 40: down
  2015. registerKeyEvent( [ 34, 39, 40 ], window.impress().next );
  2016. // 32: space
  2017. registerKeyEvent( [ 32 ], spaceHandler );
  2018. // 82: R
  2019. registerKeyEvent( [ 82 ], timerReset );
  2020. // Cleanup
  2021. consoleWindow.onbeforeunload = function() {
  2022. // I don't know why onunload doesn't work here.
  2023. clearInterval( consoleWindow.clockInterval );
  2024. };
  2025. // It will need a little nudge on Firefox, but only after loading:
  2026. onStepEnter();
  2027. consoleWindow.initialized = false;
  2028. consoleWindow.document.close();
  2029. //Catch any window resize to pass size on
  2030. window.onresize = resize;
  2031. consoleWindow.onresize = resize;
  2032. return consoleWindow;
  2033. }
  2034. };
  2035. var resize = function() {
  2036. var slideView = consoleWindow.document.getElementById( 'slideView' );
  2037. var preView = consoleWindow.document.getElementById( 'preView' );
  2038. // Get ratio of presentation
  2039. var ratio = window.innerHeight / window.innerWidth;
  2040. // Get size available for views
  2041. var views = consoleWindow.document.getElementById( 'views' );
  2042. // SlideView may have a border or some padding:
  2043. // asuming same border width on both direktions
  2044. var delta = slideView.offsetWidth - slideView.clientWidth;
  2045. // Set views
  2046. var slideViewWidth = ( views.clientWidth - delta );
  2047. var slideViewHeight = Math.floor( slideViewWidth * ratio );
  2048. var preViewTop = slideViewHeight + preViewGap;
  2049. var preViewWidth = Math.floor( slideViewWidth * preViewDefaultFactor );
  2050. var preViewHeight = Math.floor( slideViewHeight * preViewDefaultFactor );
  2051. // Shrink preview to fit into space available
  2052. if ( views.clientHeight - delta < preViewTop + preViewHeight ) {
  2053. preViewHeight = views.clientHeight - delta - preViewTop;
  2054. preViewWidth = Math.floor( preViewHeight / ratio );
  2055. }
  2056. // If preview is not high enough forget ratios!
  2057. if ( preViewWidth <= Math.floor( slideViewWidth * preViewMinimumFactor ) ) {
  2058. slideViewWidth = ( views.clientWidth - delta );
  2059. slideViewHeight = Math.floor( ( views.clientHeight - delta - preViewGap ) /
  2060. ( 1 + preViewMinimumFactor ) );
  2061. preViewTop = slideViewHeight + preViewGap;
  2062. preViewWidth = Math.floor( slideViewWidth * preViewMinimumFactor );
  2063. preViewHeight = views.clientHeight - delta - preViewTop;
  2064. }
  2065. // Set the calculated into styles
  2066. slideView.style.width = slideViewWidth + 'px';
  2067. slideView.style.height = slideViewHeight + 'px';
  2068. preView.style.top = preViewTop + 'px';
  2069. preView.style.width = preViewWidth + 'px';
  2070. preView.style.height = preViewHeight + 'px';
  2071. };
  2072. var _init = function( cssConsole, cssIframe ) {
  2073. if ( cssConsole !== undefined ) {
  2074. cssFile = cssConsole;
  2075. }
  2076. // You can also specify the css in the presentation root div:
  2077. // <div id="impress" data-console-css=..." data-console-css-iframe="...">
  2078. else if ( root.dataset.consoleCss !== undefined ) {
  2079. cssFile = root.dataset.consoleCss;
  2080. }
  2081. if ( cssIframe !== undefined ) {
  2082. cssFileIframe = cssIframe;
  2083. } else if ( root.dataset.consoleCssIframe !== undefined ) {
  2084. cssFileIframe = root.dataset.consoleCssIframe;
  2085. }
  2086. // Register the event
  2087. root.addEventListener( 'impress:stepleave', onStepLeave );
  2088. root.addEventListener( 'impress:stepenter', onStepEnter );
  2089. root.addEventListener( 'impress:substep:stepleaveaborted', onSubstep );
  2090. root.addEventListener( 'impress:substep:show', onSubstepShow );
  2091. root.addEventListener( 'impress:substep:hide', onSubstepHide );
  2092. //When the window closes, clean up after ourselves.
  2093. window.onunload = function() {
  2094. if ( consoleWindow && !consoleWindow.closed ) {
  2095. consoleWindow.close();
  2096. }
  2097. };
  2098. //Open speaker console when they press 'p'
  2099. registerKeyEvent( [ 80 ], open, window );
  2100. //Btw, you can also launch console automatically:
  2101. //<div id="impress" data-console-autolaunch="true">
  2102. if ( root.dataset.consoleAutolaunch === 'true' ) {
  2103. window.open();
  2104. }
  2105. };
  2106. var init = function( cssConsole, cssIframe ) {
  2107. if ( ( cssConsole === undefined || cssConsole === cssFileOldDefault ) &&
  2108. ( cssIframe === undefined || cssIframe === cssFileIframeOldDefault ) ) {
  2109. window.console.log( 'impressConsole.init() is deprecated. ' +
  2110. 'impressConsole is now initialized automatically when you ' +
  2111. 'call impress().init().' );
  2112. }
  2113. _init( cssConsole, cssIframe );
  2114. };
  2115. document.addEventListener( 'impress:init', function() {
  2116. _init();
  2117. // Add 'P' to the help popup
  2118. triggerEvent( document, 'impress:help:add',
  2119. { command: 'P', text: 'Presenter console', row: 10 } );
  2120. } );
  2121. // New API for impress.js plugins is based on using events
  2122. root.addEventListener( 'impress:console:open', function() {
  2123. window.open();
  2124. } );
  2125. /**
  2126. * Register a key code to an event handler
  2127. *
  2128. * :param: event.detail.keyCodes List of key codes
  2129. * :param: event.detail.handler A function registered as the event handler
  2130. * :param: event.detail.window The console window to register the keycode in
  2131. */
  2132. root.addEventListener( 'impress:console:registerKeyEvent', function( event ) {
  2133. registerKeyEvent( event.detail.keyCodes, event.detail.handler, event.detail.window );
  2134. } );
  2135. // Return the object
  2136. allConsoles[ rootId ] = { init: init, open: open, clockTick: clockTick,
  2137. registerKeyEvent: registerKeyEvent };
  2138. return allConsoles[ rootId ];
  2139. };
  2140. // Returns a string to be used inline as a css <style> element in the console window.
  2141. // Apologies for length, but hiding it here at the end to keep it away from rest of the code.
  2142. var cssStyleStr = function() {
  2143. return `<style>
  2144. #impressconsole body {
  2145. background-color: rgb(255, 255, 255);
  2146. padding: 0;
  2147. margin: 0;
  2148. font-family: verdana, arial, sans-serif;
  2149. font-size: 2vw;
  2150. }
  2151. #impressconsole div#console {
  2152. position: absolute;
  2153. top: 0.5vw;
  2154. left: 0.5vw;
  2155. right: 0.5vw;
  2156. bottom: 3vw;
  2157. margin: 0;
  2158. }
  2159. #impressconsole div#views, #impressconsole div#notes {
  2160. position: absolute;
  2161. top: 0;
  2162. bottom: 0;
  2163. }
  2164. #impressconsole div#views {
  2165. left: 0;
  2166. right: 50vw;
  2167. overflow: hidden;
  2168. }
  2169. #impressconsole div#blocker {
  2170. position: absolute;
  2171. right: 0;
  2172. bottom: 0;
  2173. }
  2174. #impressconsole div#notes {
  2175. left: 50vw;
  2176. right: 0;
  2177. overflow-x: hidden;
  2178. overflow-y: auto;
  2179. padding: 0.3ex;
  2180. background-color: rgb(255, 255, 255);
  2181. border: solid 1px rgb(120, 120, 120);
  2182. }
  2183. #impressconsole div#notes .noNotes {
  2184. color: rgb(200, 200, 200);
  2185. }
  2186. #impressconsole div#notes p {
  2187. margin-top: 0;
  2188. }
  2189. #impressconsole iframe {
  2190. position: absolute;
  2191. margin: 0;
  2192. padding: 0;
  2193. left: 0;
  2194. border: solid 1px rgb(120, 120, 120);
  2195. }
  2196. #impressconsole iframe#slideView {
  2197. top: 0;
  2198. width: 49vw;
  2199. height: 49vh;
  2200. }
  2201. #impressconsole iframe#preView {
  2202. opacity: 0.7;
  2203. top: 50vh;
  2204. width: 30vw;
  2205. height: 30vh;
  2206. }
  2207. #impressconsole div#controls {
  2208. margin: 0;
  2209. position: absolute;
  2210. bottom: 0.25vw;
  2211. left: 0.5vw;
  2212. right: 0.5vw;
  2213. height: 2.5vw;
  2214. background-color: rgb(255, 255, 255);
  2215. background-color: rgba(255, 255, 255, 0.6);
  2216. }
  2217. #impressconsole div#prev, div#next {
  2218. }
  2219. #impressconsole div#prev a, #impressconsole div#next a {
  2220. display: block;
  2221. border: solid 1px rgb(70, 70, 70);
  2222. border-radius: 0.5vw;
  2223. font-size: 1.5vw;
  2224. padding: 0.25vw;
  2225. text-decoration: none;
  2226. background-color: rgb(220, 220, 220);
  2227. color: rgb(0, 0, 0);
  2228. }
  2229. #impressconsole div#prev a:hover, #impressconsole div#next a:hover {
  2230. background-color: rgb(245, 245, 245);
  2231. }
  2232. #impressconsole div#prev {
  2233. float: left;
  2234. }
  2235. #impressconsole div#next {
  2236. float: right;
  2237. }
  2238. #impressconsole div#status {
  2239. margin-left: 2em;
  2240. margin-right: 2em;
  2241. text-align: center;
  2242. float: right;
  2243. }
  2244. #impressconsole div#clock {
  2245. margin-left: 2em;
  2246. margin-right: 2em;
  2247. text-align: center;
  2248. float: left;
  2249. }
  2250. #impressconsole div#timer {
  2251. margin-left: 2em;
  2252. margin-right: 2em;
  2253. text-align: center;
  2254. float: left;
  2255. }
  2256. #impressconsole span.moving {
  2257. color: rgb(255, 0, 0);
  2258. }
  2259. #impressconsole span.ready {
  2260. color: rgb(0, 128, 0);
  2261. }
  2262. </style>`;
  2263. };
  2264. impressConsole();
  2265. } )( document, window );
  2266. /**
  2267. * Mobile devices support
  2268. *
  2269. * Allow presentation creators to hide all but 3 slides, to save resources, particularly on mobile
  2270. * devices, using classes body.impress-mobile, .step.prev, .step.active and .step.next.
  2271. *
  2272. * Note: This plugin does not take into account possible redirections done with skip, goto etc
  2273. * plugins. Basically it wouldn't work as intended in such cases, but the active step will at least
  2274. * be correct.
  2275. *
  2276. * Adapted to a plugin from a submission by @Kzeni:
  2277. * https://github.com/impress/impress.js/issues/333
  2278. */
  2279. /* global document, navigator */
  2280. ( function( document ) {
  2281. "use strict";
  2282. var getNextStep = function( el ) {
  2283. var steps = document.querySelectorAll( ".step" );
  2284. for ( var i = 0; i < steps.length; i++ ) {
  2285. if ( steps[ i ] === el ) {
  2286. if ( i + 1 < steps.length ) {
  2287. return steps[ i + 1 ];
  2288. } else {
  2289. return steps[ 0 ];
  2290. }
  2291. }
  2292. }
  2293. };
  2294. var getPrevStep = function( el ) {
  2295. var steps = document.querySelectorAll( ".step" );
  2296. for ( var i = steps.length - 1; i >= 0; i-- ) {
  2297. if ( steps[ i ] === el ) {
  2298. if ( i - 1 >= 0 ) {
  2299. return steps[ i - 1 ];
  2300. } else {
  2301. return steps[ steps.length - 1 ];
  2302. }
  2303. }
  2304. }
  2305. };
  2306. // Detect mobile browsers & add CSS class as appropriate.
  2307. document.addEventListener( "impress:init", function( event ) {
  2308. var body = document.body;
  2309. if ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
  2310. navigator.userAgent
  2311. ) ) {
  2312. body.classList.add( "impress-mobile" );
  2313. }
  2314. // Unset all this on teardown
  2315. var api = event.detail.api;
  2316. api.lib.gc.pushCallback( function() {
  2317. document.body.classList.remove( "impress-mobile" );
  2318. var prev = document.getElementsByClassName( "prev" )[ 0 ];
  2319. var next = document.getElementsByClassName( "next" )[ 0 ];
  2320. if ( typeof prev !== "undefined" ) {
  2321. prev.classList.remove( "prev" );
  2322. }
  2323. if ( typeof next !== "undefined" ) {
  2324. next.classList.remove( "next" );
  2325. }
  2326. } );
  2327. } );
  2328. // Add prev and next classes to the siblings of the newly entered active step element
  2329. // Remove prev and next classes from their current step elements
  2330. // Note: As an exception we break namespacing rules, as these are useful general purpose
  2331. // classes. (Naming rules would require us to use css classes mobile-next and mobile-prev,
  2332. // based on plugin name.)
  2333. document.addEventListener( "impress:stepenter", function( event ) {
  2334. var oldprev = document.getElementsByClassName( "prev" )[ 0 ];
  2335. var oldnext = document.getElementsByClassName( "next" )[ 0 ];
  2336. var prev = getPrevStep( event.target );
  2337. prev.classList.add( "prev" );
  2338. var next = getNextStep( event.target );
  2339. next.classList.add( "next" );
  2340. if ( typeof oldprev !== "undefined" ) {
  2341. oldprev.classList.remove( "prev" );
  2342. }
  2343. if ( typeof oldnext !== "undefined" ) {
  2344. oldnext.classList.remove( "next" );
  2345. }
  2346. } );
  2347. } )( document );
  2348. /**
  2349. * Mouse timeout plugin
  2350. *
  2351. * After 3 seconds of mouse inactivity, add the css class
  2352. * `body.impress-mouse-timeout`. On `mousemove`, `click` or `touch`, remove the
  2353. * class.
  2354. *
  2355. * The use case for this plugin is to use CSS to hide elements from the screen
  2356. * and only make them visible when the mouse is moved. Examples where this
  2357. * might be used are: the toolbar from the toolbar plugin, and the mouse cursor
  2358. * itself.
  2359. *
  2360. * Example CSS:
  2361. *
  2362. * body.impress-mouse-timeout {
  2363. * cursor: none;
  2364. * }
  2365. * body.impress-mouse-timeout div#impress-toolbar {
  2366. * display: none;
  2367. * }
  2368. *
  2369. *
  2370. * Copyright 2016 Henrik Ingo (@henrikingo)
  2371. * Released under the MIT license.
  2372. */
  2373. /* global window, document */
  2374. ( function( document, window ) {
  2375. "use strict";
  2376. var timeout = 3;
  2377. var timeoutHandle;
  2378. var hide = function() {
  2379. // Mouse is now inactive
  2380. document.body.classList.add( "impress-mouse-timeout" );
  2381. };
  2382. var show = function() {
  2383. if ( timeoutHandle ) {
  2384. window.clearTimeout( timeoutHandle );
  2385. }
  2386. // Mouse is now active
  2387. document.body.classList.remove( "impress-mouse-timeout" );
  2388. // Then set new timeout after which it is considered inactive again
  2389. timeoutHandle = window.setTimeout( hide, timeout * 1000 );
  2390. };
  2391. document.addEventListener( "impress:init", function( event ) {
  2392. var api = event.detail.api;
  2393. var gc = api.lib.gc;
  2394. gc.addEventListener( document, "mousemove", show );
  2395. gc.addEventListener( document, "click", show );
  2396. gc.addEventListener( document, "touch", show );
  2397. // Set first timeout
  2398. show();
  2399. // Unset all this on teardown
  2400. gc.pushCallback( function() {
  2401. window.clearTimeout( timeoutHandle );
  2402. document.body.classList.remove( "impress-mouse-timeout" );
  2403. } );
  2404. }, false );
  2405. } )( document, window );
  2406. /**
  2407. * Navigation events plugin
  2408. *
  2409. * As you can see this part is separate from the impress.js core code.
  2410. * It's because these navigation actions only need what impress.js provides with
  2411. * its simple API.
  2412. *
  2413. * This plugin is what we call an _init plugin_. It's a simple kind of
  2414. * impress.js plugin. When loaded, it starts listening to the `impress:init`
  2415. * event. That event listener initializes the plugin functionality - in this
  2416. * case we listen to some keypress and mouse events. The only dependencies on
  2417. * core impress.js functionality is the `impress:init` method, as well as using
  2418. * the public api `next(), prev(),` etc when keys are pressed.
  2419. *
  2420. * Copyright 2011-2012 Bartek Szopka (@bartaz)
  2421. * Released under the MIT license.
  2422. * ------------------------------------------------
  2423. * author: Bartek Szopka
  2424. * version: 0.5.3
  2425. * url: http://bartaz.github.com/impress.js/
  2426. * source: http://github.com/bartaz/impress.js/
  2427. *
  2428. */
  2429. /* global document */
  2430. ( function( document ) {
  2431. "use strict";
  2432. // Wait for impress.js to be initialized
  2433. document.addEventListener( "impress:init", function( event ) {
  2434. // Getting API from event data.
  2435. // So you don't event need to know what is the id of the root element
  2436. // or anything. `impress:init` event data gives you everything you
  2437. // need to control the presentation that was just initialized.
  2438. var api = event.detail.api;
  2439. var gc = api.lib.gc;
  2440. var util = api.lib.util;
  2441. // Supported keys are:
  2442. // [space] - quite common in presentation software to move forward
  2443. // [up] [right] / [down] [left] - again common and natural addition,
  2444. // [pgdown] / [pgup] - often triggered by remote controllers,
  2445. // [tab] - this one is quite controversial, but the reason it ended up on
  2446. // this list is quite an interesting story... Remember that strange part
  2447. // in the impress.js code where window is scrolled to 0,0 on every presentation
  2448. // step, because sometimes browser scrolls viewport because of the focused element?
  2449. // Well, the [tab] key by default navigates around focusable elements, so clicking
  2450. // it very often caused scrolling to focused element and breaking impress.js
  2451. // positioning. I didn't want to just prevent this default action, so I used [tab]
  2452. // as another way to moving to next step... And yes, I know that for the sake of
  2453. // consistency I should add [shift+tab] as opposite action...
  2454. var isNavigationEvent = function( event ) {
  2455. // Don't trigger navigation for example when user returns to browser window with ALT+TAB
  2456. if ( event.altKey || event.ctrlKey || event.metaKey ) {
  2457. return false;
  2458. }
  2459. // In the case of TAB, we force step navigation always, overriding the browser
  2460. // navigation between input elements, buttons and links.
  2461. if ( event.keyCode === 9 ) {
  2462. return true;
  2463. }
  2464. // With the sole exception of TAB, we also ignore keys pressed if shift is down.
  2465. if ( event.shiftKey ) {
  2466. return false;
  2467. }
  2468. // For arrows, etc, check that event target is html or body element. This is to allow
  2469. // presentations to have, for example, forms with input elements where user can type
  2470. // text, including space, and not move to next step.
  2471. if ( event.target.nodeName !== "BODY" && event.target.nodeName !== "HTML" ) {
  2472. return false;
  2473. }
  2474. if ( ( event.keyCode >= 32 && event.keyCode <= 34 ) ||
  2475. ( event.keyCode >= 37 && event.keyCode <= 40 ) ) {
  2476. return true;
  2477. }
  2478. };
  2479. // KEYBOARD NAVIGATION HANDLERS
  2480. // Prevent default keydown action when one of supported key is pressed.
  2481. gc.addEventListener( document, "keydown", function( event ) {
  2482. if ( isNavigationEvent( event ) ) {
  2483. event.preventDefault();
  2484. }
  2485. }, false );
  2486. // Trigger impress action (next or prev) on keyup.
  2487. gc.addEventListener( document, "keyup", function( event ) {
  2488. if ( isNavigationEvent( event ) ) {
  2489. if ( event.shiftKey ) {
  2490. switch ( event.keyCode ) {
  2491. case 9: // Shift+tab
  2492. api.prev();
  2493. break;
  2494. }
  2495. } else {
  2496. switch ( event.keyCode ) {
  2497. case 33: // Pg up
  2498. case 37: // Left
  2499. case 38: // Up
  2500. api.prev( event );
  2501. break;
  2502. case 9: // Tab
  2503. case 32: // Space
  2504. case 34: // Pg down
  2505. case 39: // Right
  2506. case 40: // Down
  2507. api.next( event );
  2508. break;
  2509. }
  2510. }
  2511. event.preventDefault();
  2512. }
  2513. }, false );
  2514. // Delegated handler for clicking on the links to presentation steps
  2515. gc.addEventListener( document, "click", function( event ) {
  2516. // Event delegation with "bubbling"
  2517. // check if event target (or any of its parents is a link)
  2518. var target = event.target;
  2519. while ( ( target.tagName !== "A" ) &&
  2520. ( target !== document.documentElement ) ) {
  2521. target = target.parentNode;
  2522. }
  2523. if ( target.tagName === "A" ) {
  2524. var href = target.getAttribute( "href" );
  2525. // If it's a link to presentation step, target this step
  2526. if ( href && href[ 0 ] === "#" ) {
  2527. target = document.getElementById( href.slice( 1 ) );
  2528. }
  2529. }
  2530. if ( api.goto( target ) ) {
  2531. event.stopImmediatePropagation();
  2532. event.preventDefault();
  2533. }
  2534. }, false );
  2535. // Delegated handler for clicking on step elements
  2536. gc.addEventListener( document, "click", function( event ) {
  2537. var target = event.target;
  2538. // Find closest step element that is not active
  2539. while ( !( target.classList.contains( "step" ) &&
  2540. !target.classList.contains( "active" ) ) &&
  2541. ( target !== document.documentElement ) ) {
  2542. target = target.parentNode;
  2543. }
  2544. if ( api.goto( target ) ) {
  2545. event.preventDefault();
  2546. }
  2547. }, false );
  2548. // Add a line to the help popup
  2549. util.triggerEvent( document, "impress:help:add", { command: "Left &amp; Right",
  2550. text: "Previous &amp; Next step",
  2551. row: 1 } );
  2552. }, false );
  2553. } )( document );
  2554. /**
  2555. * Navigation UI plugin
  2556. *
  2557. * This plugin provides UI elements "back", "forward" and a list to select
  2558. * a specific slide number.
  2559. *
  2560. * The navigation controls are added to the toolbar plugin via DOM events. User must enable the
  2561. * toolbar in a presentation to have them visible.
  2562. *
  2563. * Copyright 2016 Henrik Ingo (@henrikingo)
  2564. * Released under the MIT license.
  2565. */
  2566. // This file contains so much HTML, that we will just respectfully disagree about js
  2567. /* jshint quotmark:single */
  2568. /* global document */
  2569. ( function( document ) {
  2570. 'use strict';
  2571. var toolbar;
  2572. var api;
  2573. var root;
  2574. var steps;
  2575. var hideSteps = [];
  2576. var prev;
  2577. var select;
  2578. var next;
  2579. var triggerEvent = function( el, eventName, detail ) {
  2580. var event = document.createEvent( 'CustomEvent' );
  2581. event.initCustomEvent( eventName, true, true, detail );
  2582. el.dispatchEvent( event );
  2583. };
  2584. var makeDomElement = function( html ) {
  2585. var tempDiv = document.createElement( 'div' );
  2586. tempDiv.innerHTML = html;
  2587. return tempDiv.firstChild;
  2588. };
  2589. var selectOptionsHtml = function() {
  2590. var options = '';
  2591. for ( var i = 0; i < steps.length; i++ ) {
  2592. // Omit steps that are listed as hidden from select widget
  2593. if ( hideSteps.indexOf( steps[ i ] ) < 0 ) {
  2594. options = options + '<option value="' + steps[ i ].id + '">' + // jshint ignore:line
  2595. steps[ i ].id + '</option>' + '\n'; // jshint ignore:line
  2596. }
  2597. }
  2598. return options;
  2599. };
  2600. var addNavigationControls = function( event ) {
  2601. api = event.detail.api;
  2602. var gc = api.lib.gc;
  2603. root = event.target;
  2604. steps = root.querySelectorAll( '.step' );
  2605. var prevHtml = '<button id="impress-navigation-ui-prev" title="Previous" ' +
  2606. 'class="impress-navigation-ui">&lt;</button>';
  2607. var selectHtml = '<select id="impress-navigation-ui-select" title="Go to" ' +
  2608. 'class="impress-navigation-ui">' + '\n' +
  2609. selectOptionsHtml() +
  2610. '</select>';
  2611. var nextHtml = '<button id="impress-navigation-ui-next" title="Next" ' +
  2612. 'class="impress-navigation-ui">&gt;</button>';
  2613. prev = makeDomElement( prevHtml );
  2614. prev.addEventListener( 'click',
  2615. function() {
  2616. api.prev();
  2617. } );
  2618. select = makeDomElement( selectHtml );
  2619. select.addEventListener( 'change',
  2620. function( event ) {
  2621. api.goto( event.target.value );
  2622. } );
  2623. gc.addEventListener( root, 'impress:steprefresh', function( event ) {
  2624. // As impress.js core now allows to dynamically edit the steps, including adding,
  2625. // removing, and reordering steps, we need to requery and redraw the select list on
  2626. // every stepenter event.
  2627. steps = root.querySelectorAll( '.step' );
  2628. select.innerHTML = '\n' + selectOptionsHtml();
  2629. // Make sure the list always shows the step we're actually on, even if it wasn't
  2630. // selected from the list
  2631. select.value = event.target.id;
  2632. } );
  2633. next = makeDomElement( nextHtml );
  2634. next.addEventListener( 'click',
  2635. function() {
  2636. api.next();
  2637. } );
  2638. triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: prev } );
  2639. triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: select } );
  2640. triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: next } );
  2641. };
  2642. // API for not listing given step in the select widget.
  2643. // For example, if you set class="skip" on some element, you may not want it to show up in the
  2644. // list either. Otoh we cannot assume that, or anything else, so steps that user wants omitted
  2645. // must be specifically added with this API call.
  2646. document.addEventListener( 'impress:navigation-ui:hideStep', function( event ) {
  2647. hideSteps.push( event.target );
  2648. if ( select ) {
  2649. select.innerHTML = selectOptionsHtml();
  2650. }
  2651. }, false );
  2652. // Wait for impress.js to be initialized
  2653. document.addEventListener( 'impress:init', function( event ) {
  2654. toolbar = document.querySelector( '#impress-toolbar' );
  2655. if ( toolbar ) {
  2656. addNavigationControls( event );
  2657. }
  2658. }, false );
  2659. } )( document );
  2660. /* global document */
  2661. ( function( document ) {
  2662. "use strict";
  2663. var root;
  2664. var stepids = [];
  2665. // Get stepids from the steps under impress root
  2666. var getSteps = function() {
  2667. stepids = [];
  2668. var steps = root.querySelectorAll( ".step" );
  2669. for ( var i = 0; i < steps.length; i++ )
  2670. {
  2671. stepids[ i + 1 ] = steps[ i ].id;
  2672. }
  2673. };
  2674. // Wait for impress.js to be initialized
  2675. document.addEventListener( "impress:init", function( event ) {
  2676. root = event.target;
  2677. getSteps();
  2678. var gc = event.detail.api.lib.gc;
  2679. gc.pushCallback( function() {
  2680. stepids = [];
  2681. if ( progressbar ) {
  2682. progressbar.style.width = "";
  2683. }
  2684. if ( progress ) {
  2685. progress.innerHTML = "";
  2686. }
  2687. } );
  2688. } );
  2689. var progressbar = document.querySelector( "div.impress-progressbar div" );
  2690. var progress = document.querySelector( "div.impress-progress" );
  2691. if ( null !== progressbar || null !== progress ) {
  2692. document.addEventListener( "impress:stepleave", function( event ) {
  2693. updateProgressbar( event.detail.next.id );
  2694. } );
  2695. document.addEventListener( "impress:steprefresh", function( event ) {
  2696. getSteps();
  2697. updateProgressbar( event.target.id );
  2698. } );
  2699. }
  2700. function updateProgressbar( slideId ) {
  2701. var slideNumber = stepids.indexOf( slideId );
  2702. if ( null !== progressbar ) {
  2703. var width = 100 / ( stepids.length - 1 ) * ( slideNumber );
  2704. progressbar.style.width = width.toFixed( 2 ) + "%";
  2705. }
  2706. if ( null !== progress ) {
  2707. progress.innerHTML = slideNumber + "/" + ( stepids.length - 1 );
  2708. }
  2709. }
  2710. } )( document );
  2711. /**
  2712. * Relative Positioning Plugin
  2713. *
  2714. * This plugin provides support for defining the coordinates of a step relative
  2715. * to the previous step. This is often more convenient when creating presentations,
  2716. * since as you add, remove or move steps, you may not need to edit the positions
  2717. * as much as is the case with the absolute coordinates supported by impress.js
  2718. * core.
  2719. *
  2720. * Example:
  2721. *
  2722. * <!-- Position step 1000 px to the right and 500 px up from the previous step. -->
  2723. * <div class="step" data-rel-x="1000" data-rel-y="500">
  2724. *
  2725. * Following html attributes are supported for step elements:
  2726. *
  2727. * data-rel-x
  2728. * data-rel-y
  2729. * data-rel-z
  2730. *
  2731. * These values are also inherited from the previous step. This makes it easy to
  2732. * create a boring presentation where each slide shifts for example 1000px down
  2733. * from the previous.
  2734. *
  2735. * In addition to plain numbers, which are pixel values, it is also possible to
  2736. * define relative positions as a multiple of screen height and width, using
  2737. * a unit of "h" and "w", respectively, appended to the number.
  2738. *
  2739. * Example:
  2740. *
  2741. * <div class="step" data-rel-x="1.5w" data-rel-y="1.5h">
  2742. *
  2743. * This plugin is a *pre-init plugin*. It is called synchronously from impress.js
  2744. * core at the beginning of `impress().init()`. This allows it to process its own
  2745. * data attributes first, and possibly alter the data-x, data-y and data-z attributes
  2746. * that will then be processed by `impress().init()`.
  2747. *
  2748. * (Another name for this kind of plugin might be called a *filter plugin*, but
  2749. * *pre-init plugin* is more generic, as a plugin might do whatever it wants in
  2750. * the pre-init stage.)
  2751. *
  2752. * Copyright 2016 Henrik Ingo (@henrikingo)
  2753. * Released under the MIT license.
  2754. */
  2755. /* global document, window */
  2756. ( function( document, window ) {
  2757. "use strict";
  2758. var startingState = {};
  2759. /**
  2760. * Copied from core impress.js. We currently lack a library mechanism to
  2761. * to share utility functions like this.
  2762. */
  2763. var toNumber = function( numeric, fallback ) {
  2764. return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );
  2765. };
  2766. /**
  2767. * Extends toNumber() to correctly compute also relative-to-screen-size values 5w and 5h.
  2768. *
  2769. * Returns the computed value in pixels with w/h postfix removed.
  2770. */
  2771. var toNumberAdvanced = function( numeric, fallback ) {
  2772. if ( typeof numeric !== "string" ) {
  2773. return toNumber( numeric, fallback );
  2774. }
  2775. var ratio = numeric.match( /^([+-]*[\d\.]+)([wh])$/ );
  2776. if ( ratio == null ) {
  2777. return toNumber( numeric, fallback );
  2778. } else {
  2779. var value = parseFloat( ratio[ 1 ] );
  2780. var multiplier = ratio[ 2 ] === "w" ? window.innerWidth : window.innerHeight;
  2781. return value * multiplier;
  2782. }
  2783. };
  2784. var computeRelativePositions = function( el, prev ) {
  2785. var data = el.dataset;
  2786. if ( !prev ) {
  2787. // For the first step, inherit these defaults
  2788. prev = { x:0, y:0, z:0, relative: { x:0, y:0, z:0 } };
  2789. }
  2790. var step = {
  2791. x: toNumber( data.x, prev.x ),
  2792. y: toNumber( data.y, prev.y ),
  2793. z: toNumber( data.z, prev.z ),
  2794. relative: {
  2795. x: toNumberAdvanced( data.relX, prev.relative.x ),
  2796. y: toNumberAdvanced( data.relY, prev.relative.y ),
  2797. z: toNumberAdvanced( data.relZ, prev.relative.z )
  2798. }
  2799. };
  2800. // Relative position is ignored/zero if absolute is given.
  2801. // Note that this also has the effect of resetting any inherited relative values.
  2802. if ( data.x !== undefined ) {
  2803. step.relative.x = 0;
  2804. }
  2805. if ( data.y !== undefined ) {
  2806. step.relative.y = 0;
  2807. }
  2808. if ( data.z !== undefined ) {
  2809. step.relative.z = 0;
  2810. }
  2811. // Apply relative position to absolute position, if non-zero
  2812. // Note that at this point, the relative values contain a number value of pixels.
  2813. step.x = step.x + step.relative.x;
  2814. step.y = step.y + step.relative.y;
  2815. step.z = step.z + step.relative.z;
  2816. return step;
  2817. };
  2818. var rel = function( root ) {
  2819. var steps = root.querySelectorAll( ".step" );
  2820. var prev;
  2821. startingState[ root.id ] = [];
  2822. for ( var i = 0; i < steps.length; i++ ) {
  2823. var el = steps[ i ];
  2824. startingState[ root.id ].push( {
  2825. el: el,
  2826. x: el.getAttribute( "data-x" ),
  2827. y: el.getAttribute( "data-y" ),
  2828. z: el.getAttribute( "data-z" )
  2829. } );
  2830. var step = computeRelativePositions( el, prev );
  2831. // Apply relative position (if non-zero)
  2832. el.setAttribute( "data-x", step.x );
  2833. el.setAttribute( "data-y", step.y );
  2834. el.setAttribute( "data-z", step.z );
  2835. prev = step;
  2836. }
  2837. };
  2838. // Register the plugin to be called in pre-init phase
  2839. window.impress.addPreInitPlugin( rel );
  2840. // Register teardown callback to reset the data.x, .y, .z values.
  2841. document.addEventListener( "impress:init", function( event ) {
  2842. var root = event.target;
  2843. event.detail.api.lib.gc.pushCallback( function() {
  2844. var steps = startingState[ root.id ];
  2845. var step;
  2846. while ( step = steps.pop() ) {
  2847. if ( step.x === null ) {
  2848. step.el.removeAttribute( "data-x" );
  2849. } else {
  2850. step.el.setAttribute( "data-x", step.x );
  2851. }
  2852. if ( step.y === null ) {
  2853. step.el.removeAttribute( "data-y" );
  2854. } else {
  2855. step.el.setAttribute( "data-y", step.y );
  2856. }
  2857. if ( step.z === null ) {
  2858. step.el.removeAttribute( "data-z" );
  2859. } else {
  2860. step.el.setAttribute( "data-z", step.z );
  2861. }
  2862. }
  2863. delete startingState[ root.id ];
  2864. } );
  2865. }, false );
  2866. } )( document, window );
  2867. /**
  2868. * Resize plugin
  2869. *
  2870. * Rescale the presentation after a window resize.
  2871. *
  2872. * Copyright 2011-2012 Bartek Szopka (@bartaz)
  2873. * Released under the MIT license.
  2874. * ------------------------------------------------
  2875. * author: Bartek Szopka
  2876. * version: 0.5.3
  2877. * url: http://bartaz.github.com/impress.js/
  2878. * source: http://github.com/bartaz/impress.js/
  2879. *
  2880. */
  2881. /* global document, window */
  2882. ( function( document, window ) {
  2883. "use strict";
  2884. // Wait for impress.js to be initialized
  2885. document.addEventListener( "impress:init", function( event ) {
  2886. var api = event.detail.api;
  2887. // Rescale presentation when window is resized
  2888. api.lib.gc.addEventListener( window, "resize", api.lib.util.throttle( function() {
  2889. // Force going to active step again, to trigger rescaling
  2890. api.goto( document.querySelector( ".step.active" ), 500 );
  2891. }, 250 ), false );
  2892. }, false );
  2893. } )( document, window );
  2894. /**
  2895. * Skip Plugin
  2896. *
  2897. * Example:
  2898. *
  2899. * <!-- This slide is disabled in presentations, when moving with next()
  2900. * and prev() commands, but you can still move directly to it, for
  2901. * example with a url (anything using goto()). -->
  2902. * <div class="step skip">
  2903. *
  2904. * Copyright 2016 Henrik Ingo (@henrikingo)
  2905. * Released under the MIT license.
  2906. */
  2907. /* global document, window */
  2908. ( function( document, window ) {
  2909. "use strict";
  2910. var util;
  2911. document.addEventListener( "impress:init", function( event ) {
  2912. util = event.detail.api.lib.util;
  2913. }, false );
  2914. var getNextStep = function( el ) {
  2915. var steps = document.querySelectorAll( ".step" );
  2916. for ( var i = 0; i < steps.length; i++ ) {
  2917. if ( steps[ i ] === el ) {
  2918. if ( i + 1 < steps.length ) {
  2919. return steps[ i + 1 ];
  2920. } else {
  2921. return steps[ 0 ];
  2922. }
  2923. }
  2924. }
  2925. };
  2926. var getPrevStep = function( el ) {
  2927. var steps = document.querySelectorAll( ".step" );
  2928. for ( var i = steps.length - 1; i >= 0; i-- ) {
  2929. if ( steps[ i ] === el ) {
  2930. if ( i - 1 >= 0 ) {
  2931. return steps[ i - 1 ];
  2932. } else {
  2933. return steps[ steps.length - 1 ];
  2934. }
  2935. }
  2936. }
  2937. };
  2938. var skip = function( event ) {
  2939. if ( ( !event ) || ( !event.target ) ) {
  2940. return;
  2941. }
  2942. if ( event.detail.next.classList.contains( "skip" ) ) {
  2943. if ( event.detail.reason === "next" ) {
  2944. // Go to the next next step instead
  2945. event.detail.next = getNextStep( event.detail.next );
  2946. // Recursively call this plugin again, until there's a step not to skip
  2947. skip( event );
  2948. } else if ( event.detail.reason === "prev" ) {
  2949. // Go to the previous previous step instead
  2950. event.detail.next = getPrevStep( event.detail.next );
  2951. skip( event );
  2952. }
  2953. // If the new next element has its own transitionDuration, we're responsible for setting
  2954. // that on the event as well
  2955. event.detail.transitionDuration = util.toNumber(
  2956. event.detail.next.dataset.transitionDuration, event.detail.transitionDuration
  2957. );
  2958. }
  2959. };
  2960. // Register the plugin to be called in pre-stepleave phase
  2961. // The weight makes this plugin run early. This is a good thing, because this plugin calls
  2962. // itself recursively.
  2963. window.impress.addPreStepLeavePlugin( skip, 1 );
  2964. } )( document, window );
  2965. /**
  2966. * Stop Plugin
  2967. *
  2968. * Example:
  2969. *
  2970. * <!-- Stop at this slide.
  2971. * (For example, when used on the last slide, this prevents the
  2972. * presentation from wrapping back to the beginning.) -->
  2973. * <div class="step stop">
  2974. *
  2975. * Copyright 2016 Henrik Ingo (@henrikingo)
  2976. * Released under the MIT license.
  2977. */
  2978. /* global document, window */
  2979. ( function( document, window ) {
  2980. "use strict";
  2981. var stop = function( event ) {
  2982. if ( ( !event ) || ( !event.target ) ) {
  2983. return;
  2984. }
  2985. if ( event.target.classList.contains( "stop" ) ) {
  2986. if ( event.detail.reason === "next" ) {
  2987. return false;
  2988. }
  2989. }
  2990. };
  2991. // Register the plugin to be called in pre-stepleave phase
  2992. // The weight makes this plugin run fairly early.
  2993. window.impress.addPreStepLeavePlugin( stop, 2 );
  2994. } )( document, window );
  2995. /**
  2996. * Substep Plugin
  2997. *
  2998. * Copyright 2017 Henrik Ingo (@henrikingo)
  2999. * Released under the MIT license.
  3000. */
  3001. /* global document, window */
  3002. ( function( document, window ) {
  3003. "use strict";
  3004. // Copied from core impress.js. Good candidate for moving to src/lib/util.js.
  3005. var triggerEvent = function( el, eventName, detail ) {
  3006. var event = document.createEvent( "CustomEvent" );
  3007. event.initCustomEvent( eventName, true, true, detail );
  3008. el.dispatchEvent( event );
  3009. };
  3010. var activeStep = null;
  3011. document.addEventListener( "impress:stepenter", function( event ) {
  3012. activeStep = event.target;
  3013. }, false );
  3014. var substep = function( event ) {
  3015. if ( ( !event ) || ( !event.target ) ) {
  3016. return;
  3017. }
  3018. var step = event.target;
  3019. var el; // Needed by jshint
  3020. if ( event.detail.reason === "next" ) {
  3021. el = showSubstepIfAny( step );
  3022. if ( el ) {
  3023. // Send a message to others, that we aborted a stepleave event.
  3024. // Autoplay will reload itself from this, as there won't be a stepenter event now.
  3025. triggerEvent( step, "impress:substep:stepleaveaborted",
  3026. { reason: "next", substep: el } );
  3027. // Returning false aborts the stepleave event
  3028. return false;
  3029. }
  3030. }
  3031. if ( event.detail.reason === "prev" ) {
  3032. el = hideSubstepIfAny( step );
  3033. if ( el ) {
  3034. triggerEvent( step, "impress:substep:stepleaveaborted",
  3035. { reason: "prev", substep: el } );
  3036. return false;
  3037. }
  3038. }
  3039. };
  3040. var showSubstepIfAny = function( step ) {
  3041. var substeps = step.querySelectorAll( ".substep" );
  3042. var visible = step.querySelectorAll( ".substep-visible" );
  3043. if ( substeps.length > 0 ) {
  3044. return showSubstep( substeps, visible );
  3045. }
  3046. };
  3047. var showSubstep = function( substeps, visible ) {
  3048. if ( visible.length < substeps.length ) {
  3049. var el = substeps[ visible.length ];
  3050. el.classList.add( "substep-visible" );
  3051. return el;
  3052. }
  3053. };
  3054. var hideSubstepIfAny = function( step ) {
  3055. var substeps = step.querySelectorAll( ".substep" );
  3056. var visible = step.querySelectorAll( ".substep-visible" );
  3057. if ( substeps.length > 0 ) {
  3058. return hideSubstep( visible );
  3059. }
  3060. };
  3061. var hideSubstep = function( visible ) {
  3062. if ( visible.length > 0 ) {
  3063. var el = visible[ visible.length - 1 ];
  3064. el.classList.remove( "substep-visible" );
  3065. return el;
  3066. }
  3067. };
  3068. // Register the plugin to be called in pre-stepleave phase.
  3069. // The weight makes this plugin run before other preStepLeave plugins.
  3070. window.impress.addPreStepLeavePlugin( substep, 1 );
  3071. // When entering a step, in particular when re-entering, make sure that all substeps are hidden
  3072. // at first
  3073. document.addEventListener( "impress:stepenter", function( event ) {
  3074. var step = event.target;
  3075. var visible = step.querySelectorAll( ".substep-visible" );
  3076. for ( var i = 0; i < visible.length; i++ ) {
  3077. visible[ i ].classList.remove( "substep-visible" );
  3078. }
  3079. }, false );
  3080. // API for others to reveal/hide next substep ////////////////////////////////////////////////
  3081. document.addEventListener( "impress:substep:show", function() {
  3082. showSubstepIfAny( activeStep );
  3083. }, false );
  3084. document.addEventListener( "impress:substep:hide", function() {
  3085. hideSubstepIfAny( activeStep );
  3086. }, false );
  3087. } )( document, window );
  3088. /**
  3089. * Support for swipe and tap on touch devices
  3090. *
  3091. * This plugin implements navigation for plugin devices, via swiping left/right,
  3092. * or tapping on the left/right edges of the screen.
  3093. *
  3094. *
  3095. *
  3096. * Copyright 2015: Andrew Dunai (@and3rson)
  3097. * Modified to a plugin, 2016: Henrik Ingo (@henrikingo)
  3098. *
  3099. * MIT License
  3100. */
  3101. /* global document, window */
  3102. ( function( document, window ) {
  3103. "use strict";
  3104. // Touch handler to detect swiping left and right based on window size.
  3105. // If the difference in X change is bigger than 1/20 of the screen width,
  3106. // we simply call an appropriate API function to complete the transition.
  3107. var startX = 0;
  3108. var lastX = 0;
  3109. var lastDX = 0;
  3110. var threshold = window.innerWidth / 20;
  3111. document.addEventListener( "touchstart", function( event ) {
  3112. lastX = startX = event.touches[ 0 ].clientX;
  3113. } );
  3114. document.addEventListener( "touchmove", function( event ) {
  3115. var x = event.touches[ 0 ].clientX;
  3116. var diff = x - startX;
  3117. // To be used in touchend
  3118. lastDX = lastX - x;
  3119. lastX = x;
  3120. window.impress().swipe( diff / window.innerWidth );
  3121. } );
  3122. document.addEventListener( "touchend", function() {
  3123. var totalDiff = lastX - startX;
  3124. if ( Math.abs( totalDiff ) > window.innerWidth / 5 && ( totalDiff * lastDX ) <= 0 ) {
  3125. if ( totalDiff > window.innerWidth / 5 && lastDX <= 0 ) {
  3126. window.impress().prev();
  3127. } else if ( totalDiff < -window.innerWidth / 5 && lastDX >= 0 ) {
  3128. window.impress().next();
  3129. }
  3130. } else if ( Math.abs( lastDX ) > threshold ) {
  3131. if ( lastDX < -threshold ) {
  3132. window.impress().prev();
  3133. } else if ( lastDX > threshold ) {
  3134. window.impress().next();
  3135. }
  3136. } else {
  3137. // No movement - move (back) to the current slide
  3138. window.impress().goto( document.querySelector( "#impress .step.active" ) );
  3139. }
  3140. } );
  3141. document.addEventListener( "touchcancel", function() {
  3142. // Move (back) to the current slide
  3143. window.impress().goto( document.querySelector( "#impress .step.active" ) );
  3144. } );
  3145. } )( document, window );
  3146. /**
  3147. * Toolbar plugin
  3148. *
  3149. * This plugin provides a generic graphical toolbar. Other plugins that
  3150. * want to expose a button or other widget, can add those to this toolbar.
  3151. *
  3152. * Using a single consolidated toolbar for all GUI widgets makes it easier
  3153. * to position and style the toolbar rather than having to do that for lots
  3154. * of different divs.
  3155. *
  3156. *
  3157. * *** For presentation authors: *****************************************
  3158. *
  3159. * To add/activate the toolbar in your presentation, add this div:
  3160. *
  3161. * <div id="impress-toolbar"></div>
  3162. *
  3163. * Styling the toolbar is left to presentation author. Here's an example CSS:
  3164. *
  3165. * .impress-enabled div#impress-toolbar {
  3166. * position: fixed;
  3167. * right: 1px;
  3168. * bottom: 1px;
  3169. * opacity: 0.6;
  3170. * }
  3171. * .impress-enabled div#impress-toolbar > span {
  3172. * margin-right: 10px;
  3173. * }
  3174. *
  3175. * The [mouse-timeout](../mouse-timeout/README.md) plugin can be leveraged to hide
  3176. * the toolbar from sight, and only make it visible when mouse is moved.
  3177. *
  3178. * body.impress-mouse-timeout div#impress-toolbar {
  3179. * display: none;
  3180. * }
  3181. *
  3182. *
  3183. * *** For plugin authors **********************************************
  3184. *
  3185. * To add a button to the toolbar, trigger the `impress:toolbar:appendChild`
  3186. * or `impress:toolbar:insertBefore` events as appropriate. The detail object
  3187. * should contain following parameters:
  3188. *
  3189. * { group : 1, // integer. Widgets with the same group are grouped inside
  3190. * // the same <span> element.
  3191. * html : "<button>Click</button>", // The html to add.
  3192. * callback : "mycallback", // Toolbar plugin will trigger event
  3193. * // `impress:toolbar:added:mycallback` when done.
  3194. * before: element } // The reference element for an insertBefore() call.
  3195. *
  3196. * You should also listen to the `impress:toolbar:added:mycallback` event. At
  3197. * this point you can find the new widget in the DOM, and for example add an
  3198. * event listener to it.
  3199. *
  3200. * You are free to use any integer for the group. It's ok to leave gaps. It's
  3201. * ok to co-locate with widgets for another plugin, if you think they belong
  3202. * together.
  3203. *
  3204. * See navigation-ui for an example.
  3205. *
  3206. * Copyright 2016 Henrik Ingo (@henrikingo)
  3207. * Released under the MIT license.
  3208. */
  3209. /* global document */
  3210. ( function( document ) {
  3211. "use strict";
  3212. var toolbar = document.getElementById( "impress-toolbar" );
  3213. var groups = [];
  3214. /**
  3215. * Get the span element that is a child of toolbar, identified by index.
  3216. *
  3217. * If span element doesn't exist yet, it is created.
  3218. *
  3219. * Note: Because of Run-to-completion, this is not a race condition.
  3220. * https://developer.mozilla.org/en/docs/Web/JavaScript/EventLoop#Run-to-completion
  3221. *
  3222. * :param: index Method will return the element <span id="impress-toolbar-group-{index}">
  3223. */
  3224. var getGroupElement = function( index ) {
  3225. var id = "impress-toolbar-group-" + index;
  3226. if ( !groups[ index ] ) {
  3227. groups[ index ] = document.createElement( "span" );
  3228. groups[ index ].id = id;
  3229. var nextIndex = getNextGroupIndex( index );
  3230. if ( nextIndex === undefined ) {
  3231. toolbar.appendChild( groups[ index ] );
  3232. } else {
  3233. toolbar.insertBefore( groups[ index ], groups[ nextIndex ] );
  3234. }
  3235. }
  3236. return groups[ index ];
  3237. };
  3238. /**
  3239. * Get the span element from groups[] that is immediately after given index.
  3240. *
  3241. * This can be used to find the reference node for an insertBefore() call.
  3242. * If no element exists at a larger index, returns undefined. (In this case,
  3243. * you'd use appendChild() instead.)
  3244. *
  3245. * Note that index needn't itself exist in groups[].
  3246. */
  3247. var getNextGroupIndex = function( index ) {
  3248. var i = index + 1;
  3249. while ( !groups[ i ] && i < groups.length ) {
  3250. i++;
  3251. }
  3252. if ( i < groups.length ) {
  3253. return i;
  3254. }
  3255. };
  3256. // API
  3257. // Other plugins can add and remove buttons by sending them as events.
  3258. // In return, toolbar plugin will trigger events when button was added.
  3259. if ( toolbar ) {
  3260. /**
  3261. * Append a widget inside toolbar span element identified by given group index.
  3262. *
  3263. * :param: e.detail.group integer specifying the span element where widget will be placed
  3264. * :param: e.detail.element a dom element to add to the toolbar
  3265. */
  3266. toolbar.addEventListener( "impress:toolbar:appendChild", function( e ) {
  3267. var group = getGroupElement( e.detail.group );
  3268. group.appendChild( e.detail.element );
  3269. } );
  3270. /**
  3271. * Add a widget to toolbar using insertBefore() DOM method.
  3272. *
  3273. * :param: e.detail.before the reference dom element, before which new element is added
  3274. * :param: e.detail.element a dom element to add to the toolbar
  3275. */
  3276. toolbar.addEventListener( "impress:toolbar:insertBefore", function( e ) {
  3277. toolbar.insertBefore( e.detail.element, e.detail.before );
  3278. } );
  3279. /**
  3280. * Remove the widget in e.detail.remove.
  3281. */
  3282. toolbar.addEventListener( "impress:toolbar:removeWidget", function( e ) {
  3283. toolbar.removeChild( e.detail.remove );
  3284. } );
  3285. document.addEventListener( "impress:init", function( event ) {
  3286. var api = event.detail.api;
  3287. api.lib.gc.pushCallback( function() {
  3288. toolbar.innerHTML = "";
  3289. groups = [];
  3290. } );
  3291. } );
  3292. } // If toolbar
  3293. } )( document );