From 8c31e245872fdf5aef59503a042836ae247b0ef8 Mon Sep 17 00:00:00 2001 From: Gene Wu Date: Tue, 8 Oct 2013 16:41:29 +0800 Subject: [PATCH 001/100] reset to previous working version --- .travis.yml | 2 -- ...-10-07-call-2013-oct-ruby-tuesday.markdown | 31 ------------------- shrubyslides | 1 - 3 files changed, 34 deletions(-) delete mode 100644 .travis.yml delete mode 100644 _drafts/2013-10-07-call-2013-oct-ruby-tuesday.markdown delete mode 160000 shrubyslides diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9249e85..0000000 --- a/.travis.yml +++ /dev/null @@ -1,2 +0,0 @@ -language: ruby -script: "bundle exec jekyll build" diff --git a/_drafts/2013-10-07-call-2013-oct-ruby-tuesday.markdown b/_drafts/2013-10-07-call-2013-oct-ruby-tuesday.markdown deleted file mode 100644 index 895d64f..0000000 --- a/_drafts/2013-10-07-call-2013-oct-ruby-tuesday.markdown +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: post -title: "2013年10月15日 Ruby Tuesday 活动召集 (周二)" -date: 2013-10-07 11:07:00 -categories: monthly ---- - -Hi, 大家好, -本月 Ruby Tuesday 将预备安排在十月第三个周二,也就是10月15日。到那天想必大家都回来了吧。 -这次活动计划在浦西举办,地点是大家熟悉的静安寺。 - -####时间:2013年10月15日 (周二) 晚上7点至9点 - -####地点:Donut King (静安公园店) - -####地址:静安区南京西路1649号静安公园内 (久光百货对面) - -####交通:地铁二号线或者七号线 静安寺站 - -####网址: - -[大众点评](http://www.dianping.com/shop/2787973) -[Google Maps](http://goo.gl/maps/EO3bG) - -####主题: - -未定,尚在征集中,有意向的欢迎跟帖。不过我们也可以自由讨论下列 Topic: - -* 关于组队前往 RubyConf 2013 -* 关于发起一个 Jekyll Generator Service 项目的讨论 (hosted by [Gene Wu](http://twitter.com/gene_wu)) - diff --git a/shrubyslides b/shrubyslides deleted file mode 160000 index e49bf36..0000000 --- a/shrubyslides +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e49bf368ef90c0b0f1866667e76bb71d1b0fe041 From beeccfbbc7387069b939240886b363b6ae36a05d Mon Sep 17 00:00:00 2001 From: Gene Wu Date: Tue, 8 Oct 2013 17:15:47 +0800 Subject: [PATCH 002/100] restore post of oct call up --- ...-10-07-call-2013-oct-ruby-tuesday.markdown | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 _posts/2013-10-07-call-2013-oct-ruby-tuesday.markdown diff --git a/_posts/2013-10-07-call-2013-oct-ruby-tuesday.markdown b/_posts/2013-10-07-call-2013-oct-ruby-tuesday.markdown new file mode 100644 index 0000000..895d64f --- /dev/null +++ b/_posts/2013-10-07-call-2013-oct-ruby-tuesday.markdown @@ -0,0 +1,31 @@ +--- +layout: post +title: "2013年10月15日 Ruby Tuesday 活动召集 (周二)" +date: 2013-10-07 11:07:00 +categories: monthly +--- + +Hi, 大家好, +本月 Ruby Tuesday 将预备安排在十月第三个周二,也就是10月15日。到那天想必大家都回来了吧。 +这次活动计划在浦西举办,地点是大家熟悉的静安寺。 + +####时间:2013年10月15日 (周二) 晚上7点至9点 + +####地点:Donut King (静安公园店) + +####地址:静安区南京西路1649号静安公园内 (久光百货对面) + +####交通:地铁二号线或者七号线 静安寺站 + +####网址: + +[大众点评](http://www.dianping.com/shop/2787973) +[Google Maps](http://goo.gl/maps/EO3bG) + +####主题: + +未定,尚在征集中,有意向的欢迎跟帖。不过我们也可以自由讨论下列 Topic: + +* 关于组队前往 RubyConf 2013 +* 关于发起一个 Jekyll Generator Service 项目的讨论 (hosted by [Gene Wu](http://twitter.com/gene_wu)) + From 47c09614b24049bd47a7b3ad513a3879927e338b Mon Sep 17 00:00:00 2001 From: Gene Wu Date: Tue, 8 Oct 2013 17:17:08 +0800 Subject: [PATCH 003/100] RSS update at 10/08/2013 by jian --- rss.xml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/rss.xml b/rss.xml index ac79129..a84e006 100644 --- a/rss.xml +++ b/rss.xml @@ -8,7 +8,18 @@ Shanghai Ruby User Group http://shruby.github.io Web site of ShRuby.github.io - Mon, 30 Sep 2013 07:51:20 +0800 + Mon, 07 Oct 2013 19:07:00 +0800 + + 2013年10月15日 Ruby Tuesday 活动召集 (周二) + http://shruby.github.io/monthly/2013/10/07/call-2013-oct-ruby-tuesday.html + Hi, 大家好, +本月 Ruby Tuesday 将预备安排在十月第三个周二,也就是10月15日。到那天想必大家都回来了吧。 +这次活动计划在浦西举办,地点是大家熟悉的静安寺。 + + + Mon, 07 Oct 2013 19:07:00 +0800 + 2013-10-07T19:07:00+08:00 + 为 Shanghai Ruby User Group 创建了一个 Twitter 同步账号 http://shruby.github.io/twitter/accounts/2013/09/30/new-twitter-account-for-shruby.html @@ -39,6 +50,6 @@ Thu, 12 Sep 2013 21:14:07 +0800 2013-09-12T21:14:07+08:00 - 2013-09-30T07:51:20+08:00 + 2013-10-07T19:07:00+08:00 \ No newline at end of file From 62ba1d2d37399c9a672dee1d6b6ed23d04c7ba17 Mon Sep 17 00:00:00 2001 From: zhouguangming Date: Tue, 8 Oct 2013 13:28:22 +0800 Subject: [PATCH 004/100] add .travis.yml --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9249e85 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,2 @@ +language: ruby +script: "bundle exec jekyll build" From 6f5190d10ddbb525cb05c2180e962b482e6618d6 Mon Sep 17 00:00:00 2001 From: Bachue Zhou Date: Tue, 8 Oct 2013 17:54:11 +0800 Subject: [PATCH 005/100] Fix the failure of Travis building --- _config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_config.yml b/_config.yml index 26c7720..dde5505 100644 --- a/_config.yml +++ b/_config.yml @@ -6,7 +6,8 @@ keep_files: ['.git'] site: http://shruby.github.io url: http://shruby.github.io description: 'Web site of ShRuby.github.io' +exclude: ["vendor"] save: true lsi: false -pygments: true \ No newline at end of file +pygments: true From 54fa712c511426578eba226779b19e574780818b Mon Sep 17 00:00:00 2001 From: Bachue Zhou Date: Tue, 8 Oct 2013 18:08:07 +0800 Subject: [PATCH 006/100] add --safe in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9249e85..184167d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,2 +1,2 @@ language: ruby -script: "bundle exec jekyll build" +script: "bundle exec jekyll build --safe" From adaf32cf786c913f8273d2fb4034e99a7d24f31a Mon Sep 17 00:00:00 2001 From: Gene Wu Date: Sat, 12 Oct 2013 22:54:11 +0800 Subject: [PATCH 007/100] Add a sample sitemap.xml to support jekyll --- sitemap.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 sitemap.xml diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..94ec792 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,18 @@ + + + {% for post in site.posts %} + + http://shruby.github.io{{ post.url | remove: 'index.html' }} + + {% endfor %} + + {% for page in site.pages %} + {% if page.layout != nil %} + {% if page.layout != 'feed' %} + + http://shruby.github.io{{ page.url | remove: 'index.html' }} + + {% endif %} + {% endif %} + {% endfor %} + From 1ce6b53a3a787c538709ede07a3f71ed6749abad Mon Sep 17 00:00:00 2001 From: Gene Wu Date: Sat, 12 Oct 2013 23:28:23 +0800 Subject: [PATCH 008/100] add layout to indicate nil as layout --- sitemap.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sitemap.xml b/sitemap.xml index 94ec792..f4424b5 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -1,3 +1,6 @@ +--- +layout: nil +--- {% for post in site.posts %} From 3995568e93a835b13c5d35ab90460dd99eadcb4e Mon Sep 17 00:00:00 2001 From: Gene Wu Date: Sun, 13 Oct 2013 00:07:33 +0800 Subject: [PATCH 009/100] Get an idea how to generate rss without plugin. Need to be verified - jian --- rss_noplugin.xml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 rss_noplugin.xml diff --git a/rss_noplugin.xml b/rss_noplugin.xml new file mode 100644 index 0000000..1e10967 --- /dev/null +++ b/rss_noplugin.xml @@ -0,0 +1,27 @@ +--- +layout: nil +title : RSS Feed +--- + + + + {{ site.title }} + {{ site.title }} - {{ site.author.name }} + {{ site.production_url }}{{ site.rss_path }} + {{ site.production_url }} + {{ site.time | date_to_xmlschema }} + {{ site.time | date_to_xmlschema }} + 1800 + +{% for post in site.posts %} + + {{ post.title }} + {{ post.content | xml_escape }} + {{ site.production_url }}{{ post.url }} + {{ site.production_url }}{{ post.id }} + {{ post.date | date_to_xmlschema }} + +{% endfor %} + + + From 40f76b69c5d267dec247fd6a26d7a42efc7a15ca Mon Sep 17 00:00:00 2001 From: Bachue Zhou Date: Sun, 13 Oct 2013 15:58:43 +0800 Subject: [PATCH 010/100] upgrade github-pages from 6 to 8 --- Gemfile.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index db43e6a..5d074c7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,27 +9,27 @@ GEM highline (~> 1.6.11) directory_watcher (1.4.1) fast-stemmer (1.0.2) - github-pages (6) + github-pages (8) RedCloth (= 4.2.9) - jekyll (= 1.2.1) + jekyll (= 1.2.0) kramdown (= 1.0.2) - liquid (= 2.5.2) + liquid (= 2.5.1) maruku (= 0.6.1) rdiscount (= 1.6.8) - redcarpet (= 2.3.0) + redcarpet (= 2.2.2) highline (1.6.19) - jekyll (1.2.1) + jekyll (1.2.0) classifier (~> 1.3) colorator (~> 0.1) commander (~> 4.1.3) directory_watcher (~> 1.4.1) - liquid (~> 2.5.2) + liquid (~> 2.5.1) maruku (~> 0.5) pygments.rb (~> 0.5.0) - redcarpet (~> 2.3.0) + redcarpet (~> 2.2.2) safe_yaml (~> 0.7.0) kramdown (1.0.2) - liquid (2.5.2) + liquid (2.5.1) maruku (0.6.1) syntax (>= 1.0.0) posix-spawn (0.3.6) @@ -37,7 +37,7 @@ GEM posix-spawn (~> 0.3.6) yajl-ruby (~> 1.1.0) rdiscount (1.6.8) - redcarpet (2.3.0) + redcarpet (2.2.2) safe_yaml (0.7.1) syntax (1.0.0) yajl-ruby (1.1.0) From 480bb4e24547ec5082d92a43e0fd3e3b4e5e8709 Mon Sep 17 00:00:00 2001 From: Gene Wu Date: Mon, 14 Oct 2013 01:17:22 +0800 Subject: [PATCH 011/100] add reference of github-cards --- .gitmodules | 3 +++ _includes/footer.html | 6 +++++- github-cards | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) create mode 160000 github-cards diff --git a/.gitmodules b/.gitmodules index e69de29..8e0ea64 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "github-cards"] + path = github-cards + url = https://github.com/lepture/github-cards.git diff --git a/_includes/footer.html b/_includes/footer.html index 4093397..f2bd69f 100644 --- a/_includes/footer.html +++ b/_includes/footer.html @@ -8,8 +8,12 @@

+ +

+

- \ No newline at end of file + diff --git a/github-cards b/github-cards new file mode 160000 index 0000000..b6e0933 --- /dev/null +++ b/github-cards @@ -0,0 +1 @@ +Subproject commit b6e0933c1919e1a50ab0aa7b46d8673019897974 From 2977346da5e8fbc49a1d6d5cdc72345d9b9eb659 Mon Sep 17 00:00:00 2001 From: Gene Wu Date: Mon, 14 Oct 2013 01:28:09 +0800 Subject: [PATCH 012/100] keep a-z order of member --- members.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/members.md b/members.md index f9441f4..073bd04 100644 --- a/members.md +++ b/members.md @@ -32,13 +32,6 @@ title: Group Members 非典型 Ruby 程序员,喜好 Unix 系统以及 CLI,在上海工作生活 10 多年。LifeHacker,从小学起开始拆装各类电子器件为乐。 现在在某企业中开发移动应用为生。 -[Vincent178](https://github.com/vincent178) -[![RubyChina](/assets/images/sprites/ruby_china.png)](http://ruby-china.org/vincent178) -[![Twitter](/assets/images/sprites/twitter.png)](https://twitter.com/Vincent_178) - - 半路出家的程序员, unix 粉, 喜欢 Ruby 和 Objective-C . 希望能够做一个有用的应用. - 热爱音乐,动漫,篮球,骑行. - [JiaYang Song](https://github.com/songjiayang) 宋佳洋,阳光 90 后, Ruby 程序员,喜欢创业,技术分享, 四川眉山人,2013/04/28 一个人开始闯荡上海滩,目前工作于在 www.gigabase.org. @@ -47,6 +40,13 @@ title: Group Members 包敏华,80 后的上海人,Ruby 爱好者,目前在普华永道工作。喜欢看漫画,喜欢看球,喜欢研究前沿 IT 技术。 +[Vincent178](https://github.com/vincent178) +[![RubyChina](/assets/images/sprites/ruby_china.png)](http://ruby-china.org/vincent178) +[![Twitter](/assets/images/sprites/twitter.png)](https://twitter.com/Vincent_178) + + 半路出家的程序员, unix 粉, 喜欢 Ruby 和 Objective-C . 希望能够做一个有用的应用. + 热爱音乐,动漫,篮球,骑行. + ---- # 荣誉成员 From 58d11d1300caa63cd747ea4f588fe6a50001eb62 Mon Sep 17 00:00:00 2001 From: Bachue Zhou Date: Wed, 16 Oct 2013 00:35:28 +0800 Subject: [PATCH 013/100] update 10-16 rubytuesday info --- .../2013-10-07-call-2013-oct-ruby-tuesday.markdown | 13 +++++++++++++ css/main.css | 2 ++ 2 files changed, 15 insertions(+) diff --git a/_posts/2013-10-07-call-2013-oct-ruby-tuesday.markdown b/_posts/2013-10-07-call-2013-oct-ruby-tuesday.markdown index 895d64f..b1fb2ee 100644 --- a/_posts/2013-10-07-call-2013-oct-ruby-tuesday.markdown +++ b/_posts/2013-10-07-call-2013-oct-ruby-tuesday.markdown @@ -29,3 +29,16 @@ Hi, 大家好, * 关于组队前往 RubyConf 2013 * 关于发起一个 Jekyll Generator Service 项目的讨论 (hosted by [Gene Wu](http://twitter.com/gene_wu)) +####会后总结 + +活动照片: + +![照片](http://d.pr/i/LJyN+) + +本次活动没有 Topic,主要由 [Gene Wu](http://twitter.com/gene_wu) 宣传他关于在线 Jekyll 编辑 和 Build 服务的构想。 + +在这次会议中,来自杭州的 RubyConfChina 2012 讲师,Gitlab 之神 [@Saito](https://twitter.com/SaitoWu) 向我们做出了宝贵的批评,我们将在以后的 RubyTuesday 活动中努力改进,争取每次都做到有一定实质性的主题分享。 + +这里还有一篇由 [@small\_fish\_\_](http://ruby-china.org/small_fish__) 做出的[总结](http://dianrui.weidaxue.me/blogs/3),非常详细和深入的提出了自己对于 Jekyll 服务的想法,欢迎阅读。 + +另,本次活动是今年十月唯一一次活动,因为月末将在北京召开 [RubyConfChina](http://rubyconfchina.org) 大会,故一切上海活动取消。下次活动将安排在十一月。先提前向大家征集主题分享,欢迎跟帖回复,谢谢。 \ No newline at end of file diff --git a/css/main.css b/css/main.css index 4204646..611868c 100755 --- a/css/main.css +++ b/css/main.css @@ -29,6 +29,8 @@ a { color: #00a; } a:hover { color: #000; } a:visited { color: #a0a; } +img { max-width: 42em; } + /*****************************************************************************/ /* /* Home From 923370a1f2c40d9332e05649ac82569403aed880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ryan=20L=C3=BC?= Date: Fri, 18 Oct 2013 15:53:28 +0800 Subject: [PATCH 014/100] Update members.md --- members.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/members.md b/members.md index 073bd04..5341308 100644 --- a/members.md +++ b/members.md @@ -40,6 +40,12 @@ title: Group Members 包敏华,80 后的上海人,Ruby 爱好者,目前在普华永道工作。喜欢看漫画,喜欢看球,喜欢研究前沿 IT 技术。 + +[Ryan Lv](https://github.com/xiaoronglv) + + 吕小荣,前医生,做了2年产品经理,目前是个 Ruby 程序员 + + [Vincent178](https://github.com/vincent178) [![RubyChina](/assets/images/sprites/ruby_china.png)](http://ruby-china.org/vincent178) [![Twitter](/assets/images/sprites/twitter.png)](https://twitter.com/Vincent_178) @@ -51,7 +57,7 @@ title: Group Members # 荣誉成员 -[Danile Lv](https://github.com/lgn21st) +[Daniel Lv](https://github.com/lgn21st) Daniel Lv (吕国宁) Ruby 程序员,专职 Ruby on Rails 以及 Web 应用开发,生于新疆乌鲁木齐,在上海工作 10 多年后移居青岛。 [Shanghaionrails] 在线社区的发起人和组织者, RubyChina 社区的联合创始人,管理员。 From cf1e662ab4cc67f39271867b38deba0fc53475d5 Mon Sep 17 00:00:00 2001 From: Gene Wu Date: Fri, 25 Oct 2013 13:37:30 +0000 Subject: [PATCH 015/100] remove slides, since we have sub site --- slides/opensourcedcommunity.html | 247 -- slides/reveal/.gitignore | 6 - slides/reveal/.travis.yml | 5 - slides/reveal/Gruntfile.js | 136 - slides/reveal/LICENSE | 19 - slides/reveal/README.md | 813 ----- slides/reveal/css/print/paper.css | 176 -- slides/reveal/css/print/pdf.css | 190 -- slides/reveal/css/reveal.css | 1653 ---------- slides/reveal/css/reveal.min.css | 7 - slides/reveal/css/theme/README.md | 23 - slides/reveal/css/theme/beige.css | 142 - slides/reveal/css/theme/default.css | 142 - slides/reveal/css/theme/moon.css | 142 - slides/reveal/css/theme/night.css | 130 - slides/reveal/css/theme/serif.css | 132 - slides/reveal/css/theme/simple.css | 132 - slides/reveal/css/theme/sky.css | 139 - slides/reveal/css/theme/solarized.css | 142 - slides/reveal/css/theme/source/beige.scss | 50 - slides/reveal/css/theme/source/default.scss | 42 - slides/reveal/css/theme/source/moon.scss | 68 - slides/reveal/css/theme/source/night.scss | 35 - slides/reveal/css/theme/source/serif.scss | 35 - slides/reveal/css/theme/source/simple.scss | 38 - slides/reveal/css/theme/source/sky.scss | 46 - slides/reveal/css/theme/source/solarized.scss | 74 - slides/reveal/css/theme/template/mixins.scss | 29 - .../reveal/css/theme/template/settings.scss | 34 - slides/reveal/css/theme/template/theme.scss | 163 - slides/reveal/index.html | 383 --- slides/reveal/js/reveal.js | 2777 ----------------- slides/reveal/js/reveal.min.js | 8 - slides/reveal/lib/css/zenburn.css | 114 - .../reveal/lib/font/league_gothic-webfont.eot | Bin 18485 -> 0 bytes .../reveal/lib/font/league_gothic-webfont.svg | 230 -- .../reveal/lib/font/league_gothic-webfont.ttf | Bin 42324 -> 0 bytes .../lib/font/league_gothic-webfont.woff | Bin 21288 -> 0 bytes slides/reveal/lib/font/league_gothic_license | 2 - slides/reveal/lib/js/classList.js | 2 - slides/reveal/lib/js/head.min.js | 8 - slides/reveal/lib/js/html5shiv.js | 7 - slides/reveal/package.json | 46 - slides/reveal/plugin/highlight/highlight.js | 31 - slides/reveal/plugin/leap/leap.js | 157 - slides/reveal/plugin/markdown/example.html | 98 - slides/reveal/plugin/markdown/example.md | 31 - slides/reveal/plugin/markdown/markdown.js | 320 -- slides/reveal/plugin/markdown/marked.js | 37 - slides/reveal/plugin/math/math.js | 64 - slides/reveal/plugin/multiplex/client.js | 13 - slides/reveal/plugin/multiplex/index.js | 56 - slides/reveal/plugin/multiplex/master.js | 51 - slides/reveal/plugin/notes-server/client.js | 57 - slides/reveal/plugin/notes-server/index.js | 59 - slides/reveal/plugin/notes-server/notes.html | 142 - slides/reveal/plugin/notes/notes.html | 263 -- slides/reveal/plugin/notes/notes.js | 78 - slides/reveal/plugin/postmessage/example.html | 39 - .../reveal/plugin/postmessage/postmessage.js | 42 - slides/reveal/plugin/print-pdf/print-pdf.js | 44 - slides/reveal/plugin/remotes/remotes.js | 39 - slides/reveal/plugin/search/search.js | 196 -- slides/reveal/plugin/zoom-js/zoom.js | 256 -- slides/reveal/test/examples/assets/image1.png | Bin 21991 -> 0 bytes slides/reveal/test/examples/assets/image2.png | Bin 10237 -> 0 bytes slides/reveal/test/examples/barebones.html | 42 - .../reveal/test/examples/embedded-media.html | 49 - slides/reveal/test/examples/math.html | 185 -- .../test/examples/slide-backgrounds.html | 101 - slides/reveal/test/qunit-1.12.0.css | 244 -- slides/reveal/test/qunit-1.12.0.js | 2212 ------------- slides/reveal/test/test-markdown.html | 52 - slides/reveal/test/test-markdown.js | 15 - slides/reveal/test/test.html | 62 - slides/reveal/test/test.js | 353 --- 76 files changed, 13955 deletions(-) delete mode 100644 slides/opensourcedcommunity.html delete mode 100644 slides/reveal/.gitignore delete mode 100644 slides/reveal/.travis.yml delete mode 100644 slides/reveal/Gruntfile.js delete mode 100644 slides/reveal/LICENSE delete mode 100644 slides/reveal/README.md delete mode 100644 slides/reveal/css/print/paper.css delete mode 100644 slides/reveal/css/print/pdf.css delete mode 100644 slides/reveal/css/reveal.css delete mode 100644 slides/reveal/css/reveal.min.css delete mode 100644 slides/reveal/css/theme/README.md delete mode 100644 slides/reveal/css/theme/beige.css delete mode 100644 slides/reveal/css/theme/default.css delete mode 100644 slides/reveal/css/theme/moon.css delete mode 100644 slides/reveal/css/theme/night.css delete mode 100644 slides/reveal/css/theme/serif.css delete mode 100644 slides/reveal/css/theme/simple.css delete mode 100644 slides/reveal/css/theme/sky.css delete mode 100644 slides/reveal/css/theme/solarized.css delete mode 100644 slides/reveal/css/theme/source/beige.scss delete mode 100644 slides/reveal/css/theme/source/default.scss delete mode 100644 slides/reveal/css/theme/source/moon.scss delete mode 100644 slides/reveal/css/theme/source/night.scss delete mode 100644 slides/reveal/css/theme/source/serif.scss delete mode 100644 slides/reveal/css/theme/source/simple.scss delete mode 100644 slides/reveal/css/theme/source/sky.scss delete mode 100644 slides/reveal/css/theme/source/solarized.scss delete mode 100644 slides/reveal/css/theme/template/mixins.scss delete mode 100644 slides/reveal/css/theme/template/settings.scss delete mode 100644 slides/reveal/css/theme/template/theme.scss delete mode 100644 slides/reveal/index.html delete mode 100644 slides/reveal/js/reveal.js delete mode 100644 slides/reveal/js/reveal.min.js delete mode 100644 slides/reveal/lib/css/zenburn.css delete mode 100755 slides/reveal/lib/font/league_gothic-webfont.eot delete mode 100644 slides/reveal/lib/font/league_gothic-webfont.svg delete mode 100644 slides/reveal/lib/font/league_gothic-webfont.ttf delete mode 100644 slides/reveal/lib/font/league_gothic-webfont.woff delete mode 100644 slides/reveal/lib/font/league_gothic_license delete mode 100644 slides/reveal/lib/js/classList.js delete mode 100644 slides/reveal/lib/js/head.min.js delete mode 100644 slides/reveal/lib/js/html5shiv.js delete mode 100644 slides/reveal/package.json delete mode 100644 slides/reveal/plugin/highlight/highlight.js delete mode 100644 slides/reveal/plugin/leap/leap.js delete mode 100644 slides/reveal/plugin/markdown/example.html delete mode 100644 slides/reveal/plugin/markdown/example.md delete mode 100755 slides/reveal/plugin/markdown/markdown.js delete mode 100644 slides/reveal/plugin/markdown/marked.js delete mode 100755 slides/reveal/plugin/math/math.js delete mode 100644 slides/reveal/plugin/multiplex/client.js delete mode 100644 slides/reveal/plugin/multiplex/index.js delete mode 100644 slides/reveal/plugin/multiplex/master.js delete mode 100644 slides/reveal/plugin/notes-server/client.js delete mode 100644 slides/reveal/plugin/notes-server/index.js delete mode 100644 slides/reveal/plugin/notes-server/notes.html delete mode 100644 slides/reveal/plugin/notes/notes.html delete mode 100644 slides/reveal/plugin/notes/notes.js delete mode 100644 slides/reveal/plugin/postmessage/example.html delete mode 100644 slides/reveal/plugin/postmessage/postmessage.js delete mode 100644 slides/reveal/plugin/print-pdf/print-pdf.js delete mode 100644 slides/reveal/plugin/remotes/remotes.js delete mode 100644 slides/reveal/plugin/search/search.js delete mode 100644 slides/reveal/plugin/zoom-js/zoom.js delete mode 100644 slides/reveal/test/examples/assets/image1.png delete mode 100644 slides/reveal/test/examples/assets/image2.png delete mode 100644 slides/reveal/test/examples/barebones.html delete mode 100644 slides/reveal/test/examples/embedded-media.html delete mode 100644 slides/reveal/test/examples/math.html delete mode 100644 slides/reveal/test/examples/slide-backgrounds.html delete mode 100644 slides/reveal/test/qunit-1.12.0.css delete mode 100644 slides/reveal/test/qunit-1.12.0.js delete mode 100644 slides/reveal/test/test-markdown.html delete mode 100644 slides/reveal/test/test-markdown.js delete mode 100644 slides/reveal/test/test.html delete mode 100644 slides/reveal/test/test.js diff --git a/slides/opensourcedcommunity.html b/slides/opensourcedcommunity.html deleted file mode 100644 index 045a2f4..0000000 --- a/slides/opensourcedcommunity.html +++ /dev/null @@ -1,247 +0,0 @@ - - - - - - - 上海Ruby用户组 - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
-

上海Ruby用户组

-

开源型社区

-

- By Gene Wu / @gene_wu -

-
- -
-
-

Ruby与我

-
    -
  • 2004年写下了我第一行Ruby
  • -
  • 2011年重新开始学习Ruby和Rails
  • -
  • 现在,大部分技术工作是在Ruby上
  • -
-
-
-

社区与我

-
    -
  • 2011年开始参与活动
  • -
  • 得到了很多社区的帮助
  • -
  • 有时间回报社区
  • -
-
-
-
-
-

开源型社区

-
-
-

开放的社区

-

技术中立不排斥其他社区

-

信息公开

-

自治

-
-
-

自由再散布

-

衍生著作

-
-
-

没有特权

-

不对任何人 或 团体有差别待遇

-
-
- -
-
-

上海 AKA 魔都

-
    -
  • 长三角地区以及中国第一大城市
  • -
  • 拥有大量学校及研究机构
  • -
  • 众多应用Ruby的企业,如EMC/VMware/SAP/DeNA/eBay
  • -
  • 国际化大都市,大量的国际友人
  • -
  • 创业气氛浓郁
  • -
-
-
-

Ruby User Group之前世

-
    -
  • 08年创立shanghaionrails
  • -
  • 举办了09-12年的RubyConf China
  • -
  • 人来人往
  • -
  • 培育了很多优秀的Ruby开发人员
  • -
-
-
-

Ruby User Group

-
    -
  • 我们用 Ruby 不仅仅是 Rails
  • -
  • Ruby Motion, Sinatra..只要写的是 Ruby :)
  • -
  • 欢迎其他社区来砸场子,兼容并蓄,海纳百川
  • -
-
-
-

活动计划

-
    -
  • 每月的第二个周二晚上,周中聚会
  • -
  • 每月的第四个周日下午,周末聚会
  • -
  • 浦东/浦西
  • -
-
-
-

shruby.github.io

-
    -
  • 活动召集
  • -
  • 纪录活动
  • -
  • 感谢赞助方
  • -
  • 成员目录
  • -
  • 组织介绍
  • -
-
-
-

你也是站长!

-
    -
  • Fork
  • -
  • Add / Edit
  • -
  • Push
  • -
  • Pull Request
  • -
  • Merge
  • -
-
-
-

目的

-
    -
  • 人人是站长
  • -
  • 平等互助
  • -
  • 人走了,茶不凉
  • -
  • Git / Github
  • -
-
-
-
-
-

我能做什么

-
-
-

我是高手

-
    -
  • 论践
  • -
  • 布道
  • -
  • 长人气
  • -
  • 拉队伍
  • -
-
-
-

我是中手

-
    -
  • 讲一遍 > 写两遍
  • -
  • 开拓视野
  • -
  • 你的经验=他人的财富
  • -
  • 找队伍
  • -
-
-
-

我是新手

-
    -
  • Light Talk
  • -
  • No Simple! No Naive!
  • -
  • 拜师学艺
  • -
-
-
-

不论你是谁

-
    -
  • 参与活动
  • -
  • 参与讨论
  • -
  • 相互学习
  • -
  • Thank You!
  • -
-
-
-
-
-

社区 - 人

-
-
-

权利与义务

-
-
-

人人为我

-
-
-

我为人人

-
-
-

Be Social

-
-
-
-

Thank You

-

BY Gene Wu (Twitter:gene_wu)

-
- -
- -
- - - - - - - - diff --git a/slides/reveal/.gitignore b/slides/reveal/.gitignore deleted file mode 100644 index 9ffdbc7..0000000 --- a/slides/reveal/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.DS_Store -.svn -log/*.log -tmp/** -node_modules/ -.sass-cache \ No newline at end of file diff --git a/slides/reveal/.travis.yml b/slides/reveal/.travis.yml deleted file mode 100644 index 2d6cd8f..0000000 --- a/slides/reveal/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -node_js: - - 0.8 -before_script: - - npm install -g grunt-cli \ No newline at end of file diff --git a/slides/reveal/Gruntfile.js b/slides/reveal/Gruntfile.js deleted file mode 100644 index 124a78d..0000000 --- a/slides/reveal/Gruntfile.js +++ /dev/null @@ -1,136 +0,0 @@ -/* global module:false */ -module.exports = function(grunt) { - var port = grunt.option('port') || 8000; - // Project configuration - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - meta: { - banner: - '/*!\n' + - ' * reveal.js <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd, HH:MM") %>)\n' + - ' * http://lab.hakim.se/reveal-js\n' + - ' * MIT licensed\n' + - ' *\n' + - ' * Copyright (C) 2013 Hakim El Hattab, http://hakim.se\n' + - ' */' - }, - - // Tests will be added soon - qunit: { - files: [ 'test/*.html' ] - }, - - uglify: { - options: { - banner: '<%= meta.banner %>\n' - }, - build: { - src: 'js/reveal.js', - dest: 'js/reveal.min.js' - } - }, - - cssmin: { - compress: { - files: { - 'css/reveal.min.css': [ 'css/reveal.css' ] - } - } - }, - - sass: { - main: { - files: { - 'css/theme/default.css': 'css/theme/source/default.scss', - 'css/theme/beige.css': 'css/theme/source/beige.scss', - 'css/theme/night.css': 'css/theme/source/night.scss', - 'css/theme/serif.css': 'css/theme/source/serif.scss', - 'css/theme/simple.css': 'css/theme/source/simple.scss', - 'css/theme/sky.css': 'css/theme/source/sky.scss', - 'css/theme/moon.css': 'css/theme/source/moon.scss', - 'css/theme/solarized.css': 'css/theme/source/solarized.scss' - } - } - }, - - jshint: { - options: { - curly: false, - eqeqeq: true, - immed: true, - latedef: true, - newcap: true, - noarg: true, - sub: true, - undef: true, - eqnull: true, - browser: true, - expr: true, - globals: { - head: false, - module: false, - console: false - } - }, - files: [ 'Gruntfile.js', 'js/reveal.js' ] - }, - - connect: { - server: { - options: { - port: port, - base: '.' - } - } - }, - - zip: { - 'reveal-js-presentation.zip': [ - 'index.html', - 'css/**', - 'js/**', - 'lib/**', - 'images/**', - 'plugin/**' - ] - }, - - watch: { - main: { - files: [ 'Gruntfile.js', 'js/reveal.js', 'css/reveal.css' ], - tasks: 'default' - }, - theme: { - files: [ 'css/theme/source/*.scss', 'css/theme/template/*.scss' ], - tasks: 'themes' - } - } - - }); - - // Dependencies - grunt.loadNpmTasks( 'grunt-contrib-qunit' ); - grunt.loadNpmTasks( 'grunt-contrib-jshint' ); - grunt.loadNpmTasks( 'grunt-contrib-cssmin' ); - grunt.loadNpmTasks( 'grunt-contrib-uglify' ); - grunt.loadNpmTasks( 'grunt-contrib-watch' ); - grunt.loadNpmTasks( 'grunt-contrib-sass' ); - grunt.loadNpmTasks( 'grunt-contrib-connect' ); - grunt.loadNpmTasks( 'grunt-zip' ); - - // Default task - grunt.registerTask( 'default', [ 'jshint', 'cssmin', 'uglify', 'qunit' ] ); - - // Theme task - grunt.registerTask( 'themes', [ 'sass' ] ); - - // Package presentation to archive - grunt.registerTask( 'package', [ 'default', 'zip' ] ); - - // Serve presentation locally - grunt.registerTask( 'serve', [ 'connect', 'watch' ] ); - - // Run tests - grunt.registerTask( 'test', [ 'jshint', 'qunit' ] ); - -}; diff --git a/slides/reveal/LICENSE b/slides/reveal/LICENSE deleted file mode 100644 index e1e8bf7..0000000 --- a/slides/reveal/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (C) 2013 Hakim El Hattab, http://hakim.se - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/slides/reveal/README.md b/slides/reveal/README.md deleted file mode 100644 index 9b377fb..0000000 --- a/slides/reveal/README.md +++ /dev/null @@ -1,813 +0,0 @@ -# reveal.js [![Build Status](https://travis-ci.org/hakimel/reveal.js.png?branch=master)](https://travis-ci.org/hakimel/reveal.js) - -A framework for easily creating beautiful presentations using HTML. [Check out the live demo](http://lab.hakim.se/reveal-js/). - -reveal.js comes with a broad range of features including [nested slides](https://github.com/hakimel/reveal.js#markup), [markdown contents](https://github.com/hakimel/reveal.js#markdown), [PDF export](https://github.com/hakimel/reveal.js#pdf-export), [speaker notes](https://github.com/hakimel/reveal.js#speaker-notes) and a [JavaScript API](https://github.com/hakimel/reveal.js#api). It's best viewed in a browser with support for CSS 3D transforms but [fallbacks](https://github.com/hakimel/reveal.js/wiki/Browser-Support) are available to make sure your presentation can still be viewed elsewhere. - - -#### More reading: -- [Installation](#installation): Step-by-step instructions for getting reveal.js running on your computer. -- [Changelog](https://github.com/hakimel/reveal.js/releases): Up-to-date version history. -- [Examples](https://github.com/hakimel/reveal.js/wiki/Example-Presentations): Presentations created with reveal.js, add your own! -- [Browser Support](https://github.com/hakimel/reveal.js/wiki/Browser-Support): Explanation of browser support and fallbacks. - -## Slides - -Presentations are written using HTML or markdown but there's also an online editor for those of you who prefer a graphical interface. Give it a try at [http://slid.es](http://slid.es). - - -## Instructions - -### Markup - -Markup hierarchy needs to be ``
`` where the ``
`` represents one slide and can be repeated indefinitely. If you place multiple ``
``'s inside of another ``
`` they will be shown as vertical slides. The first of the vertical slides is the "root" of the others (at the top), and it will be included in the horizontal sequence. For example: - -```html -
-
-
Single Horizontal Slide
-
-
Vertical Slide 1
-
Vertical Slide 2
-
-
-
-``` - -### Markdown - -It's possible to write your slides using Markdown. To enable Markdown, add the ```data-markdown``` attribute to your ```
``` elements and wrap the contents in a ``` -
-``` - -#### External Markdown - -You can write your content as a separate file and have reveal.js load it at runtime. Note the separator arguments which determine how slides are delimited in the external file. The ```data-charset``` attribute is optional and specifies which charset to use when loading the external file. - -```html -
-``` - -### Configuration - -At the end of your page you need to initialize reveal by running the following code. Note that all config values are optional and will default as specified below. - -```javascript -Reveal.initialize({ - - // Display controls in the bottom right corner - controls: true, - - // Display a presentation progress bar - progress: true, - - // Push each slide change to the browser history - history: false, - - // Enable keyboard shortcuts for navigation - keyboard: true, - - // Enable touch events for navigation - touch: true, - - // Enable the slide overview mode - overview: true, - - // Vertical centering of slides - center: true, - - // Loop the presentation - loop: false, - - // Change the presentation direction to be RTL - rtl: false, - - // Number of milliseconds between automatically proceeding to the - // next slide, disabled when set to 0, this value can be overwritten - // by using a data-autoslide attribute on your slides - autoSlide: 0, - - // Enable slide navigation via mouse wheel - mouseWheel: false, - - // Transition style - transition: 'default', // default/cube/page/concave/zoom/linear/fade/none - - // Transition speed - transitionSpeed: 'default', // default/fast/slow - - // Transition style for full page backgrounds - backgroundTransition: 'default' // default/linear/none - -}); -``` - -Note that the new default vertical centering option will break compatibility with slides that were using transitions with backgrounds (`cube` and `page`). To restore the previous behavior, set `center` to `false`. - - -The configuration can be updated after initialization using the ```configure``` method: - -```javascript -// Turn autoSlide off -Reveal.configure({ autoSlide: 0 }); - -// Start auto-sliding every 5s -Reveal.configure({ autoSlide: 5000 }); -``` - - -### Dependencies - -Reveal.js doesn't _rely_ on any third party scripts to work but a few optional libraries are included by default. These libraries are loaded as dependencies in the order they appear, for example: - -```javascript -Reveal.initialize({ - dependencies: [ - // Cross-browser shim that fully implements classList - https://github.com/eligrey/classList.js/ - { src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } }, - - // Interpret Markdown in
elements - { src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } }, - { src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } }, - - // Syntax highlight for elements - { src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }, - - // Zoom in and out with Alt+click - { src: 'plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } }, - - // Speaker notes - { src: 'plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }, - - // Remote control your reveal.js presentation using a touch device - { src: 'plugin/remotes/remotes.js', async: true, condition: function() { return !!document.body.classList; } }, - - // MathJax - { src: 'plugin/math/math.js', async: true } - ] -}); -``` - -You can add your own extensions using the same syntax. The following properties are available for each dependency object: -- **src**: Path to the script to load -- **async**: [optional] Flags if the script should load after reveal.js has started, defaults to false -- **callback**: [optional] Function to execute when the script has loaded -- **condition**: [optional] Function which must return true for the script to be loaded - - -### Presentation Size - -All presentations have a normal size, that is the resolution at which they are authored. The framework will automatically scale presentations uniformly based on this size to ensure that everything fits on any given display or viewport. - -See below for a list of configuration options related to sizing, including default values: - -```javascript -Reveal.initialize({ - - ... - - // The "normal" size of the presentation, aspect ratio will be preserved - // when the presentation is scaled to fit different resolutions. Can be - // specified using percentage units. - width: 960, - height: 700, - - // Factor of the display size that should remain empty around the content - margin: 0.1, - - // Bounds for smallest/largest possible scale to apply to content - minScale: 0.2, - maxScale: 1.0 - -}); -``` - -### Keyboard Bindings - -If you're unhappy with any of the default keyboard bindings you can override them using the ```keyboard``` config option: - -```javascript -Reveal.configure({ - keyboard: { - 13: 'next', // go to the next slide when the ENTER key is pressed - 27: function() {}, // do something custom when ESC is pressed - 32: null // don't do anything when SPACE is pressed (i.e. disable a reveal.js default binding) - } -}); -``` - - -### API - -The ``Reveal`` class provides a JavaScript API for controlling navigation and reading state: - -```javascript -// Navigation -Reveal.slide( indexh, indexv, indexf ); -Reveal.left(); -Reveal.right(); -Reveal.up(); -Reveal.down(); -Reveal.prev(); -Reveal.next(); -Reveal.prevFragment(); -Reveal.nextFragment(); -Reveal.toggleOverview(); -Reveal.togglePause(); - -// Retrieves the previous and current slide elements -Reveal.getPreviousSlide(); -Reveal.getCurrentSlide(); - -Reveal.getIndices(); // { h: 0, v: 0 } } - -// State checks -Reveal.isFirstSlide(); -Reveal.isLastSlide(); -Reveal.isOverview(); -Reveal.isPaused(); -``` - -### Ready Event - -The 'ready' event is fired when reveal.js has loaded all (synchronous) dependencies and is ready to start navigating. - -```javascript -Reveal.addEventListener( 'ready', function( event ) { - // event.currentSlide, event.indexh, event.indexv -} ); -``` - -### Slide Changed Event - -An 'slidechanged' event is fired each time the slide is changed (regardless of state). The event object holds the index values of the current slide as well as a reference to the previous and current slide HTML nodes. - -Some libraries, like MathJax (see [#226](https://github.com/hakimel/reveal.js/issues/226#issuecomment-10261609)), get confused by the transforms and display states of slides. Often times, this can be fixed by calling their update or render function from this callback. - -```javascript -Reveal.addEventListener( 'slidechanged', function( event ) { - // event.previousSlide, event.currentSlide, event.indexh, event.indexv -} ); -``` - - -### States - -If you set ``data-state="somestate"`` on a slide ``
``, "somestate" will be applied as a class on the document element when that slide is opened. This allows you to apply broad style changes to the page based on the active slide. - -Furthermore you can also listen to these changes in state via JavaScript: - -```javascript -Reveal.addEventListener( 'somestate', function() { - // TODO: Sprinkle magic -}, false ); -``` - -### Slide Backgrounds - -Slides are contained within a limited portion of the screen by default to allow them to fit any display and scale uniformly. You can apply full page background colors or images by applying a ```data-background``` attribute to your ```
``` elements. Below are a few examples. - -```html -
-

All CSS color formats are supported, like rgba() or hsl().

-
-
-

This slide will have a full-size background image.

-
-
-

This background image will be sized to 100px and repeated.

-
-``` - -Backgrounds transition using a fade animation by default. This can be changed to a linear sliding transition by passing ```backgroundTransition: 'slide'``` to the ```Reveal.initialize()``` call. Alternatively you can set ```data-background-transition``` on any section with a background to override that specific transition. - - -### Slide Transitions -The global presentation transition is set using the ```transition``` config value. You can override the global transition for a specific slide by using the ```data-transition``` attribute: - -```html -
-

This slide will override the presentation transition and zoom!

-
- -
-

Choose from three transition speeds: default, fast or slow!

-
-``` - -Note that this does not work with the page and cube transitions. - - -### Internal links - -It's easy to link between slides. The first example below targets the index of another slide whereas the second targets a slide with an ID attribute (```
```): - -```html -Link -Link -``` - -You can also add relative navigation links, similar to the built in reveal.js controls, by appending one of the following classes on any element. Note that each element is automatically given an ```enabled``` class when it's a valid navigation route based on the current slide. - -```html - - - - - - -``` - - -### Fragments -Fragments are used to highlight individual elements on a slide. Every element with the class ```fragment``` will be stepped through before moving on to the next slide. Here's an example: http://lab.hakim.se/reveal-js/#/16 - -The default fragment style is to start out invisible and fade in. This style can be changed by appending a different class to the fragment: - -```html -
-

grow

-

shrink

-

roll-in

-

fade-out

-

highlight-red

-

highlight-green

-

highlight-blue

-
-``` - -Multiple fragments can be applied to the same element sequentially by wrapping it, this will fade in the text on the first step and fade it back out on the second. - -```html -
- - I'll fade in, then out - -
-``` - -The display order of fragments can be controlled using the ```data-fragment-index``` attribute. - -```html -
-

Appears last

-

Appears first

-

Appears second

-
-``` - -### Fragment events - -When a slide fragment is either shown or hidden reveal.js will dispatch an event. - -Some libraries, like MathJax (see #505), get confused by the initially hidden fragment elements. Often times this can be fixed by calling their update or render function from this callback. - -```javascript -Reveal.addEventListener( 'fragmentshown', function( event ) { - // event.fragment = the fragment DOM element -} ); -Reveal.addEventListener( 'fragmenthidden', function( event ) { - // event.fragment = the fragment DOM element -} ); -``` - -### Code syntax highlighting - -By default, Reveal is configured with [highlight.js](http://softwaremaniacs.org/soft/highlight/en/) for code syntax highlighting. Below is an example with clojure code that will be syntax highlighted. When the `data-trim` attribute is present surrounding whitespace is automatically removed. - -```html -
-

-(def lazy-fib
-  (concat
-   [0 1]
-   ((fn rfib [a b]
-        (lazy-cons (+ a b) (rfib b (+ a b)))) 0 1)))
-	
-
-``` - - -### Overview mode - -Press "Esc" or "o" keys to toggle the overview mode on and off. While you're in this mode, you can still navigate between slides, -as if you were at 1,000 feet above your presentation. The overview mode comes with a few API hooks: - -```javascript -Reveal.addEventListener( 'overviewshown', function( event ) { /* ... */ } ); -Reveal.addEventListener( 'overviewhidden', function( event ) { /* ... */ } ); - -// Toggle the overview mode programmatically -Reveal.toggleOverview(); -``` - -### Fullscreen mode -Just press »F« on your keyboard to show your presentation in fullscreen mode. Press the »ESC« key to exit fullscreen mode. - - -### Embedded media -Embedded HTML5 `
+ + +
diff --git a/_posts/2013-09-13-call-2013-sept-weekend.markdown b/_posts/2013-09-13-call-2013-sept-weekend.markdown index 6c9b0f0..a504e04 100644 --- a/_posts/2013-09-13-call-2013-sept-weekend.markdown +++ b/_posts/2013-09-13-call-2013-sept-weekend.markdown @@ -60,11 +60,12 @@ Eric和大家分享了最近学习的Scala的兴得体会,估计Ruby Developer ## 现场照片 -![image](http://farm8.staticflickr.com/7311/9981979535_36219938ca_z.jpg) -![image](http://farm3.staticflickr.com/2874/9982002134_479f44c43a_z.jpg) -![image](http://farm8.staticflickr.com/7339/9982123153_8eebf92ea4_z.jpg) -![image](http://farm8.staticflickr.com/7336/9982005195_766b086137_z.jpg) - +
+ + + + +
[discuss]: http://ruby-china.org/topics/14059 [mvc]: https://github.com/camsong/yui.rocket diff --git a/assets/galleria/LICENSE b/assets/galleria/LICENSE new file mode 100644 index 0000000..4531e29 --- /dev/null +++ b/assets/galleria/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013 Aino http://aino.se + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/assets/galleria/galleria-1.3.js b/assets/galleria/galleria-1.3.js new file mode 100644 index 0000000..0c2c0e4 --- /dev/null +++ b/assets/galleria/galleria-1.3.js @@ -0,0 +1,6999 @@ +/** + * Galleria v 1.3 2013-11-18 + * http://galleria.io + * + * Licensed under the MIT license + * https://raw.github.com/aino/galleria/master/LICENSE + * + */ + +(function( $, Galleria, undef ) { + +/*global jQuery, navigator, Image */ + +// some references +var window = this, + doc = window.document, + $doc = $( doc ), + $win = $( window ), + +// native prototypes + protoArray = Array.prototype, + +// internal constants + VERSION = 1.3, + DEBUG = true, + TIMEOUT = 30000, + DUMMY = false, + NAV = navigator.userAgent.toLowerCase(), + HASH = window.location.hash.replace(/#\//, ''), + PROT = window.location.protocol, + F = function(){}, + FALSE = function() { return false; }, + IE = (function() { + + var v = 3, + div = doc.createElement( 'div' ), + all = div.getElementsByTagName( 'i' ); + + do { + div.innerHTML = ''; + } while ( all[0] ); + + return v > 4 ? v : undef; + + }() ), + DOM = function() { + return { + html: doc.documentElement, + body: doc.body, + head: doc.getElementsByTagName('head')[0], + title: doc.title + }; + }, + IFRAME = window.parent !== window.self, + + // list of Galleria events + _eventlist = 'data ready thumbnail loadstart loadfinish image play pause progress ' + + 'fullscreen_enter fullscreen_exit idle_enter idle_exit rescale ' + + 'lightbox_open lightbox_close lightbox_image', + + _events = (function() { + + var evs = []; + + $.each( _eventlist.split(' '), function( i, ev ) { + evs.push( ev ); + + // legacy events + if ( /_/.test( ev ) ) { + evs.push( ev.replace( /_/g, '' ) ); + } + }); + + return evs; + + }()), + + // legacy options + // allows the old my_setting syntax and converts it to camel case + + _legacyOptions = function( options ) { + + var n; + + if ( typeof options !== 'object' ) { + + // return whatever it was... + return options; + } + + $.each( options, function( key, value ) { + if ( /^[a-z]+_/.test( key ) ) { + n = ''; + $.each( key.split('_'), function( i, k ) { + n += i > 0 ? k.substr( 0, 1 ).toUpperCase() + k.substr( 1 ) : k; + }); + options[ n ] = value; + delete options[ key ]; + } + }); + + return options; + }, + + _patchEvent = function( type ) { + + // allow 'image' instead of Galleria.IMAGE + if ( $.inArray( type, _events ) > -1 ) { + return Galleria[ type.toUpperCase() ]; + } + + return type; + }, + + // video providers + _video = { + youtube: { + reg: /https?:\/\/(?:[a-zA_Z]{2,3}.)?(?:youtube\.com\/watch\?)((?:[\w\d\-\_\=]+&(?:amp;)?)*v(?:<[A-Z]+>)?=([0-9a-zA-Z\-\_]+))/i, + embed: function() { + return 'http://www.youtube.com/embed/' + this.id; + }, + getUrl: function() { + return PROT + '//gdata.youtube.com/feeds/api/videos/' + this.id + '?v=2&alt=json-in-script&callback=?'; + }, + get_thumb: function(data) { + return data.entry.media$group.media$thumbnail[2].url; + }, + get_image: function(data) { + if ( data.entry.yt$hd ) { + return PROT + '//img.youtube.com/vi/'+this.id+'/maxresdefault.jpg'; + } + return data.entry.media$group.media$thumbnail[3].url; + } + }, + vimeo: { + reg: /https?:\/\/(?:www\.)?(vimeo\.com)\/(?:hd#)?([0-9]+)/i, + embed: function() { + return 'http://player.vimeo.com/video/' + this.id; + }, + getUrl: function() { + return PROT + '//vimeo.com/api/v2/video/' + this.id + '.json?callback=?'; + }, + get_thumb: function( data ) { + return data[0].thumbnail_medium; + }, + get_image: function( data ) { + return data[0].thumbnail_large; + } + }, + dailymotion: { + reg: /https?:\/\/(?:www\.)?(dailymotion\.com)\/video\/([^_]+)/, + embed: function() { + return PROT + '//www.dailymotion.com/embed/video/' + this.id; + }, + getUrl: function() { + return 'https://api.dailymotion.com/video/' + this.id + '?fields=thumbnail_240_url,thumbnail_720_url&callback=?'; + }, + get_thumb: function( data ) { + return data.thumbnail_240_url; + }, + get_image: function( data ) { + return data.thumbnail_720_url; + } + }, + _inst: [] + }, + Video = function( type, id ) { + + for( var i=0; i<_video._inst.length; i++ ) { + if ( _video._inst[i].id === id && _video._inst[i].type == type ) { + return _video._inst[i]; + } + } + + this.type = type; + this.id = id; + this.readys = []; + + _video._inst.push(this); + + var self = this; + + $.extend( this, _video[type] ); + + $.getJSON( this.getUrl(), function(data) { + self.data = data; + $.each( self.readys, function( i, fn ) { + fn( self.data ); + }); + self.readys = []; + }); + + this.getMedia = function( type, callback, fail ) { + fail = fail || F; + var self = this; + var success = function( data ) { + callback( self['get_'+type]( data ) ); + }; + try { + if ( self.data ) { + success( self.data ); + } else { + self.readys.push( success ); + } + } catch(e) { + fail(); + } + }; + /*, + vimeo: { + reg: /https?:\/\/(?:www\.)?(vimeo\.com)\/(?:hd#)?([0-9]+)/i, + embed: function(id) { + return 'http://player.vimeo.com/video/'+id; + }, + getThumb: function( id, success, fail ) { + fail = fail || F; + $.getJSON('http://vimeo.com/api/v2/video/' + id + '.json?callback=?', function(data) { + try { + success( data[0].thumbnail_medium ); + } catch(e) { + fail(); + } + }); + } + }, + dailymotion: { + reg: /https?:\/\/(?:www\.)?(dailymotion\.com)\/video\/([^_]+)/, + embed: function(id) { + return 'http://www.dailymotion.com/embed/video/'+id; + }, + getThumb: function( id, success, fail ) { + fail = fail || F; + $.getJSON('https://api.dailymotion.com/video/'+id+'?fields=thumbnail_medium_url&callback=?', function(data) { + try { + success( data.thumbnail_medium_url ); + } catch(e) { + fail(); + } + }); + } + }*/ + }, + + // utility for testing the video URL and getting the video ID + _videoTest = function( url ) { + var match; + for ( var v in _video ) { + match = url && _video[v].reg && url.match( _video[v].reg ); + if( match && match.length ) { + return { + id: match[2], + provider: v + }; + } + } + return false; + }, + + // native fullscreen handler + _nativeFullscreen = { + + support: (function() { + var html = DOM().html; + return !IFRAME && ( html.requestFullscreen || html.mozRequestFullScreen || html.webkitRequestFullScreen ); + }()), + + callback: F, + + enter: function( instance, callback, elem ) { + + this.instance = instance; + + this.callback = callback || F; + + elem = elem || DOM().html; + if ( elem.requestFullscreen ) { + elem.requestFullscreen(); + } + else if ( elem.mozRequestFullScreen ) { + elem.mozRequestFullScreen(); + } + else if ( elem.webkitRequestFullScreen ) { + elem.webkitRequestFullScreen(); + } + }, + + exit: function( callback ) { + + this.callback = callback || F; + + if ( doc.exitFullscreen ) { + doc.exitFullscreen(); + } + else if ( doc.mozCancelFullScreen ) { + doc.mozCancelFullScreen(); + } + else if ( doc.webkitCancelFullScreen ) { + doc.webkitCancelFullScreen(); + } + }, + + instance: null, + + listen: function() { + + if ( !this.support ) { + return; + } + + var handler = function() { + + if ( !_nativeFullscreen.instance ) { + return; + } + var fs = _nativeFullscreen.instance._fullscreen; + + if ( doc.fullscreen || doc.mozFullScreen || doc.webkitIsFullScreen ) { + fs._enter( _nativeFullscreen.callback ); + } else { + fs._exit( _nativeFullscreen.callback ); + } + }; + doc.addEventListener( 'fullscreenchange', handler, false ); + doc.addEventListener( 'mozfullscreenchange', handler, false ); + doc.addEventListener( 'webkitfullscreenchange', handler, false ); + } + }, + + // the internal gallery holder + _galleries = [], + + // the internal instance holder + _instances = [], + + // flag for errors + _hasError = false, + + // canvas holder + _canvas = false, + + // instance pool, holds the galleries until themeLoad is triggered + _pool = [], + + // themeLoad trigger + _themeLoad = function( theme ) { + + Galleria.theme = theme; + + // run the instances we have in the pool + $.each( _pool, function( i, instance ) { + if ( !instance._initialized ) { + instance._init.call( instance ); + } + }); + + _pool = []; + }, + + // the Utils singleton + Utils = (function() { + + return { + + // legacy support for clearTimer + clearTimer: function( id ) { + $.each( Galleria.get(), function() { + this.clearTimer( id ); + }); + }, + + // legacy support for addTimer + addTimer: function( id ) { + $.each( Galleria.get(), function() { + this.addTimer( id ); + }); + }, + + array : function( obj ) { + return protoArray.slice.call(obj, 0); + }, + + create : function( className, nodeName ) { + nodeName = nodeName || 'div'; + var elem = doc.createElement( nodeName ); + elem.className = className; + return elem; + }, + + removeFromArray : function( arr, elem ) { + $.each(arr, function(i, el) { + if ( el == elem ) { + arr.splice(i, 1); + return false; + } + }); + return arr; + }, + + getScriptPath : function( src ) { + + // the currently executing script is always the last + src = src || $('script:last').attr('src'); + var slices = src.split('/'); + + if (slices.length == 1) { + return ''; + } + + slices.pop(); + + return slices.join('/') + '/'; + }, + + // CSS3 transitions, added in 1.2.4 + animate : (function() { + + // detect transition + var transition = (function( style ) { + var props = 'transition WebkitTransition MozTransition OTransition'.split(' '), + i; + + // disable css3 animations in opera until stable + if ( window.opera ) { + return false; + } + + for ( i = 0; props[i]; i++ ) { + if ( typeof style[ props[ i ] ] !== 'undefined' ) { + return props[ i ]; + } + } + return false; + }(( doc.body || doc.documentElement).style )); + + // map transitionend event + var endEvent = { + MozTransition: 'transitionend', + OTransition: 'oTransitionEnd', + WebkitTransition: 'webkitTransitionEnd', + transition: 'transitionend' + }[ transition ]; + + // map bezier easing conversions + var easings = { + _default: [0.25, 0.1, 0.25, 1], + galleria: [0.645, 0.045, 0.355, 1], + galleriaIn: [0.55, 0.085, 0.68, 0.53], + galleriaOut: [0.25, 0.46, 0.45, 0.94], + ease: [0.25, 0, 0.25, 1], + linear: [0.25, 0.25, 0.75, 0.75], + 'ease-in': [0.42, 0, 1, 1], + 'ease-out': [0, 0, 0.58, 1], + 'ease-in-out': [0.42, 0, 0.58, 1] + }; + + // function for setting transition css for all browsers + var setStyle = function( elem, value, suffix ) { + var css = {}; + suffix = suffix || 'transition'; + $.each( 'webkit moz ms o'.split(' '), function() { + css[ '-' + this + '-' + suffix ] = value; + }); + elem.css( css ); + }; + + // clear styles + var clearStyle = function( elem ) { + setStyle( elem, 'none', 'transition' ); + }; + + // various variables + var change, strings, easing, syntax, revert, form, css; + + // the actual animation method + return function( elem, to, options ) { + + // extend defaults + options = $.extend({ + duration: 400, + complete: F, + stop: false + }, options); + + // cache jQuery instance + elem = $( elem ); + + if ( !options.duration ) { + elem.css( to ); + options.complete.call( elem[0] ); + return; + } + + // fallback to jQuery's animate if transition is not supported + if ( !transition ) { + elem.animate(to, options); + return; + } + + // stop + if ( options.stop ) { + // clear the animation + elem.unbind( endEvent ); + clearStyle( elem ); + } + + // see if there is a change + change = false; + $.each( to, function( key, val ) { + css = elem.css( key ); + if ( Utils.parseValue( css ) != Utils.parseValue( val ) ) { + change = true; + } + // also add computed styles for FF + elem.css( key, css ); + }); + if ( !change ) { + window.setTimeout( function() { + options.complete.call( elem[0] ); + }, options.duration ); + return; + } + + // the css strings to be applied + strings = []; + + // the easing bezier + easing = options.easing in easings ? easings[ options.easing ] : easings._default; + + // the syntax + syntax = ' ' + options.duration + 'ms' + ' cubic-bezier(' + easing.join(',') + ')'; + + // add a tiny timeout so that the browsers catches any css changes before animating + window.setTimeout( (function(elem, endEvent, to, syntax) { + return function() { + + // attach the end event + elem.one(endEvent, (function( elem ) { + return function() { + + // clear the animation + clearStyle(elem); + + // run the complete method + options.complete.call(elem[0]); + }; + }( elem ))); + + // do the webkit translate3d for better performance on iOS + if( Galleria.WEBKIT && Galleria.TOUCH ) { + + revert = {}; + form = [0,0,0]; + + $.each( ['left', 'top'], function(i, m) { + if ( m in to ) { + form[ i ] = ( Utils.parseValue( to[ m ] ) - Utils.parseValue(elem.css( m )) ) + 'px'; + revert[ m ] = to[ m ]; + delete to[ m ]; + } + }); + + if ( form[0] || form[1]) { + + elem.data('revert', revert); + + strings.push('-webkit-transform' + syntax); + + // 3d animate + setStyle( elem, 'translate3d(' + form.join(',') + ')', 'transform'); + } + } + + // push the animation props + $.each(to, function( p, val ) { + strings.push(p + syntax); + }); + + // set the animation styles + setStyle( elem, strings.join(',') ); + + // animate + elem.css( to ); + + }; + }(elem, endEvent, to, syntax)), 2); + }; + }()), + + removeAlpha : function( elem ) { + if ( elem instanceof jQuery ) { + elem = elem[0]; + } + if ( IE < 9 && elem ) { + + var style = elem.style, + currentStyle = elem.currentStyle, + filter = currentStyle && currentStyle.filter || style.filter || ""; + + if ( /alpha/.test( filter ) ) { + style.filter = filter.replace( /alpha\([^)]*\)/i, '' ); + } + } + }, + + forceStyles : function( elem, styles ) { + elem = $(elem); + if ( elem.attr( 'style' ) ) { + elem.data( 'styles', elem.attr( 'style' ) ).removeAttr( 'style' ); + } + elem.css( styles ); + }, + + revertStyles : function() { + $.each( Utils.array( arguments ), function( i, elem ) { + + elem = $( elem ); + elem.removeAttr( 'style' ); + + elem.attr('style',''); // "fixes" webkit bug + + if ( elem.data( 'styles' ) ) { + elem.attr( 'style', elem.data('styles') ).data( 'styles', null ); + } + }); + }, + + moveOut : function( elem ) { + Utils.forceStyles( elem, { + position: 'absolute', + left: -10000 + }); + }, + + moveIn : function() { + Utils.revertStyles.apply( Utils, Utils.array( arguments ) ); + }, + + hide : function( elem, speed, callback ) { + + callback = callback || F; + + var $elem = $(elem); + elem = $elem[0]; + + // save the value if not exist + if (! $elem.data('opacity') ) { + $elem.data('opacity', $elem.css('opacity') ); + } + + // always hide + var style = { opacity: 0 }; + + if (speed) { + + var complete = IE < 9 && elem ? function() { + Utils.removeAlpha( elem ); + elem.style.visibility = 'hidden'; + callback.call( elem ); + } : callback; + + Utils.animate( elem, style, { + duration: speed, + complete: complete, + stop: true + }); + } else { + if ( IE < 9 && elem ) { + Utils.removeAlpha( elem ); + elem.style.visibility = 'hidden'; + } else { + $elem.css( style ); + } + } + }, + + show : function( elem, speed, callback ) { + + callback = callback || F; + + var $elem = $(elem); + elem = $elem[0]; + + // bring back saved opacity + var saved = parseFloat( $elem.data('opacity') ) || 1, + style = { opacity: saved }; + + // animate or toggle + if (speed) { + + if ( IE < 9 ) { + $elem.css('opacity', 0); + elem.style.visibility = 'visible'; + } + + var complete = IE < 9 && elem ? function() { + if ( style.opacity == 1 ) { + Utils.removeAlpha( elem ); + } + callback.call( elem ); + } : callback; + + Utils.animate( elem, style, { + duration: speed, + complete: complete, + stop: true + }); + } else { + if ( IE < 9 && style.opacity == 1 && elem ) { + Utils.removeAlpha( elem ); + elem.style.visibility = 'visible'; + } else { + $elem.css( style ); + } + } + }, + + wait : function(options) { + + Galleria._waiters = Galleria._waiters || []; + + options = $.extend({ + until : FALSE, + success : F, + error : function() { Galleria.raise('Could not complete wait function.'); }, + timeout: 3000 + }, options); + + var start = Utils.timestamp(), + elapsed, + now, + tid, + fn = function() { + now = Utils.timestamp(); + elapsed = now - start; + Utils.removeFromArray( Galleria._waiters, tid ); + if ( options.until( elapsed ) ) { + options.success(); + return false; + } + if (typeof options.timeout == 'number' && now >= start + options.timeout) { + options.error(); + return false; + } + Galleria._waiters.push( tid = window.setTimeout(fn, 10) ); + }; + Galleria._waiters.push( tid = window.setTimeout(fn, 10) ); + }, + + toggleQuality : function( img, force ) { + + if ( ( IE !== 7 && IE !== 8 ) || !img || img.nodeName.toUpperCase() != 'IMG' ) { + return; + } + + if ( typeof force === 'undefined' ) { + force = img.style.msInterpolationMode === 'nearest-neighbor'; + } + + img.style.msInterpolationMode = force ? 'bicubic' : 'nearest-neighbor'; + }, + + insertStyleTag : function( styles, id ) { + + if ( id && $( '#'+id ).length ) { + return; + } + + var style = doc.createElement( 'style' ); + if ( id ) { + style.id = id; + } + + DOM().head.appendChild( style ); + + if ( style.styleSheet ) { // IE + style.styleSheet.cssText = styles; + } else { + var cssText = doc.createTextNode( styles ); + style.appendChild( cssText ); + } + }, + + // a loadscript method that works for local scripts + loadScript: function( url, callback ) { + + var done = false, + script = $('').attr({ + src: url, + async: true + }).get(0); + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function() { + if ( !done && (!this.readyState || + this.readyState === 'loaded' || this.readyState === 'complete') ) { + + done = true; + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + + if (typeof callback === 'function') { + callback.call( this, this ); + } + } + }; + + DOM().head.appendChild( script ); + }, + + // parse anything into a number + parseValue: function( val ) { + if (typeof val === 'number') { + return val; + } else if (typeof val === 'string') { + var arr = val.match(/\-?\d|\./g); + return arr && arr.constructor === Array ? arr.join('')*1 : 0; + } else { + return 0; + } + }, + + // timestamp abstraction + timestamp: function() { + return new Date().getTime(); + }, + + loadCSS : function( href, id, callback ) { + + var link, + length; + + // look for manual css + $('link[rel=stylesheet]').each(function() { + if ( new RegExp( href ).test( this.href ) ) { + link = this; + return false; + } + }); + + if ( typeof id === 'function' ) { + callback = id; + id = undef; + } + + callback = callback || F; // dirty + + // if already present, return + if ( link ) { + callback.call( link, link ); + return link; + } + + // save the length of stylesheets to check against + length = doc.styleSheets.length; + + // check for existing id + if( $( '#' + id ).length ) { + + $( '#' + id ).attr( 'href', href ); + length--; + + } else { + link = $( '' ).attr({ + rel: 'stylesheet', + href: href, + id: id + }).get(0); + + var styles = $('link[rel="stylesheet"], style'); + if ( styles.length ) { + styles.get(0).parentNode.insertBefore( link, styles[0] ); + } else { + DOM().head.appendChild( link ); + } + + if ( IE && length >= 31 ) { + Galleria.raise( 'You have reached the browser stylesheet limit (31)', true ); + return; + } + } + + if ( typeof callback === 'function' ) { + + // First check for dummy element (new in 1.2.8) + var $loader = $('').attr( 'id', 'galleria-loader' ).hide().appendTo( DOM().body ); + + Utils.wait({ + until: function() { + return $loader.height() == 1; + }, + success: function() { + $loader.remove(); + callback.call( link, link ); + }, + error: function() { + $loader.remove(); + + // If failed, tell the dev to download the latest theme + Galleria.raise( 'Theme CSS could not load after 20 sec. ' + ( Galleria.QUIRK ? + 'Your browser is in Quirks Mode, please add a correct doctype.' : + 'Please download the latest theme at http://galleria.io/customer/.' ), true ); + }, + timeout: 5000 + }); + } + return link; + } + }; + }()), + + // play icon + _playIcon = function( container ) { + + var css = '.galleria-videoicon{width:60px;height:60px;position:absolute;top:50%;left:50%;z-index:1;' + + 'margin:-30px 0 0 -30px;cursor:pointer;background:#000;background:rgba(0,0,0,.8);border-radius:3px;-webkit-transition:all 150ms}' + + '.galleria-videoicon i{width:0px;height:0px;border-style:solid;border-width:10px 0 10px 16px;display:block;' + + 'border-color:transparent transparent transparent #ffffff;margin:20px 0 0 22px}.galleria-image:hover .galleria-videoicon{background:#000}'; + + Utils.insertStyleTag( css, 'galleria-videoicon' ); + + return $( Utils.create( 'galleria-videoicon' ) ).html( '' ).appendTo( container ) + .click( function() { $( this ).siblings( 'img' ).mouseup(); }); + }, + + // the transitions holder + _transitions = (function() { + + var _slide = function(params, complete, fade, door) { + + var easing = this.getOptions('easing'), + distance = this.getStageWidth(), + from = { left: distance * ( params.rewind ? -1 : 1 ) }, + to = { left: 0 }; + + if ( fade ) { + from.opacity = 0; + to.opacity = 1; + } else { + from.opacity = 1; + } + + $(params.next).css(from); + + Utils.animate(params.next, to, { + duration: params.speed, + complete: (function( elems ) { + return function() { + complete(); + elems.css({ + left: 0 + }); + }; + }( $( params.next ).add( params.prev ) )), + queue: false, + easing: easing + }); + + if (door) { + params.rewind = !params.rewind; + } + + if (params.prev) { + + from = { left: 0 }; + to = { left: distance * ( params.rewind ? 1 : -1 ) }; + + if ( fade ) { + from.opacity = 1; + to.opacity = 0; + } + + $(params.prev).css(from); + Utils.animate(params.prev, to, { + duration: params.speed, + queue: false, + easing: easing, + complete: function() { + $(this).css('opacity', 0); + } + }); + } + }; + + return { + + active: false, + + init: function( effect, params, complete ) { + if ( _transitions.effects.hasOwnProperty( effect ) ) { + _transitions.effects[ effect ].call( this, params, complete ); + } + }, + + effects: { + + fade: function(params, complete) { + $(params.next).css({ + opacity: 0, + left: 0 + }); + Utils.animate(params.next, { + opacity: 1 + },{ + duration: params.speed, + complete: complete + }); + if (params.prev) { + $(params.prev).css('opacity',1).show(); + Utils.animate(params.prev, { + opacity: 0 + },{ + duration: params.speed + }); + } + }, + + flash: function(params, complete) { + $(params.next).css({ + opacity: 0, + left: 0 + }); + if (params.prev) { + Utils.animate( params.prev, { + opacity: 0 + },{ + duration: params.speed/2, + complete: function() { + Utils.animate( params.next, { + opacity:1 + },{ + duration: params.speed, + complete: complete + }); + } + }); + } else { + Utils.animate( params.next, { + opacity: 1 + },{ + duration: params.speed, + complete: complete + }); + } + }, + + pulse: function(params, complete) { + if (params.prev) { + $(params.prev).hide(); + } + $(params.next).css({ + opacity: 0, + left: 0 + }).show(); + Utils.animate(params.next, { + opacity:1 + },{ + duration: params.speed, + complete: complete + }); + }, + + slide: function(params, complete) { + _slide.apply( this, Utils.array( arguments ) ); + }, + + fadeslide: function(params, complete) { + _slide.apply( this, Utils.array( arguments ).concat( [true] ) ); + }, + + doorslide: function(params, complete) { + _slide.apply( this, Utils.array( arguments ).concat( [false, true] ) ); + } + } + }; + }()); + +// listen to fullscreen +_nativeFullscreen.listen(); + +// trigger resize on orientationchange (IOS7) +$win.bind( 'orientationchange', function() { + $(this).resize(); +}); + +/** + The main Galleria class + + @class + @constructor + + @example var gallery = new Galleria(); + + @author http://aino.se + + @requires jQuery + +*/ + +Galleria = window.Galleria = function() { + + var self = this; + + // internal options + this._options = {}; + + // flag for controlling play/pause + this._playing = false; + + // internal interval for slideshow + this._playtime = 5000; + + // internal variable for the currently active image + this._active = null; + + // the internal queue, arrayified + this._queue = { length: 0 }; + + // the internal data array + this._data = []; + + // the internal dom collection + this._dom = {}; + + // the internal thumbnails array + this._thumbnails = []; + + // the internal layers array + this._layers = []; + + // internal init flag + this._initialized = false; + + // internal firstrun flag + this._firstrun = false; + + // global stagewidth/height + this._stageWidth = 0; + this._stageHeight = 0; + + // target holder + this._target = undef; + + // bind hashes + this._binds = []; + + // instance id + this._id = parseInt(Math.random()*10000, 10); + + // add some elements + var divs = 'container stage images image-nav image-nav-left image-nav-right ' + + 'info info-text info-title info-description ' + + 'thumbnails thumbnails-list thumbnails-container thumb-nav-left thumb-nav-right ' + + 'loader counter tooltip', + spans = 'current total'; + + $.each( divs.split(' '), function( i, elemId ) { + self._dom[ elemId ] = Utils.create( 'galleria-' + elemId ); + }); + + $.each( spans.split(' '), function( i, elemId ) { + self._dom[ elemId ] = Utils.create( 'galleria-' + elemId, 'span' ); + }); + + // the internal keyboard object + // keeps reference of the keybinds and provides helper methods for binding keys + var keyboard = this._keyboard = { + + keys : { + 'UP': 38, + 'DOWN': 40, + 'LEFT': 37, + 'RIGHT': 39, + 'RETURN': 13, + 'ESCAPE': 27, + 'BACKSPACE': 8, + 'SPACE': 32 + }, + + map : {}, + + bound: false, + + press: function(e) { + var key = e.keyCode || e.which; + if ( key in keyboard.map && typeof keyboard.map[key] === 'function' ) { + keyboard.map[key].call(self, e); + } + }, + + attach: function(map) { + + var key, up; + + for( key in map ) { + if ( map.hasOwnProperty( key ) ) { + up = key.toUpperCase(); + if ( up in keyboard.keys ) { + keyboard.map[ keyboard.keys[up] ] = map[key]; + } else { + keyboard.map[ up ] = map[key]; + } + } + } + if ( !keyboard.bound ) { + keyboard.bound = true; + $doc.bind('keydown', keyboard.press); + } + }, + + detach: function() { + keyboard.bound = false; + keyboard.map = {}; + $doc.unbind('keydown', keyboard.press); + } + }; + + // internal controls for keeping track of active / inactive images + var controls = this._controls = { + + 0: undef, + + 1: undef, + + active : 0, + + swap : function() { + controls.active = controls.active ? 0 : 1; + }, + + getActive : function() { + return self._options.swipe ? controls.slides[ self._active ] : controls[ controls.active ]; + }, + + getNext : function() { + return self._options.swipe ? controls.slides[ self.getNext( self._active ) ] : controls[ 1 - controls.active ]; + }, + + slides : [], + + frames: [], + + layers: [] + }; + + // internal carousel object + var carousel = this._carousel = { + + // shortcuts + next: self.$('thumb-nav-right'), + prev: self.$('thumb-nav-left'), + + // cache the width + width: 0, + + // track the current position + current: 0, + + // cache max value + max: 0, + + // save all hooks for each width in an array + hooks: [], + + // update the carousel + // you can run this method anytime, f.ex on window.resize + update: function() { + var w = 0, + h = 0, + hooks = [0]; + + $.each( self._thumbnails, function( i, thumb ) { + if ( thumb.ready ) { + w += thumb.outerWidth || $( thumb.container ).outerWidth( true ); + // Due to a bug in jquery, outerwidth() returns the floor of the actual outerwidth, + // if the browser is zoom to a value other than 100%. height() returns the floating point value. + var containerWidth = $( thumb.container).width(); + w += containerWidth - Math.floor(containerWidth); + + hooks[ i+1 ] = w; + h = Math.max( h, thumb.outerHeight || $( thumb.container).outerHeight( true ) ); + } + }); + + self.$( 'thumbnails' ).css({ + width: w, + height: h + }); + + carousel.max = w; + carousel.hooks = hooks; + carousel.width = self.$( 'thumbnails-list' ).width(); + carousel.setClasses(); + + self.$( 'thumbnails-container' ).toggleClass( 'galleria-carousel', w > carousel.width ); + + // one extra calculation + carousel.width = self.$( 'thumbnails-list' ).width(); + + // todo: fix so the carousel moves to the left + }, + + bindControls: function() { + + var i; + + carousel.next.bind( 'click', function(e) { + e.preventDefault(); + + if ( self._options.carouselSteps === 'auto' ) { + + for ( i = carousel.current; i < carousel.hooks.length; i++ ) { + if ( carousel.hooks[i] - carousel.hooks[ carousel.current ] > carousel.width ) { + carousel.set(i - 2); + break; + } + } + + } else { + carousel.set( carousel.current + self._options.carouselSteps); + } + }); + + carousel.prev.bind( 'click', function(e) { + e.preventDefault(); + + if ( self._options.carouselSteps === 'auto' ) { + + for ( i = carousel.current; i >= 0; i-- ) { + if ( carousel.hooks[ carousel.current ] - carousel.hooks[i] > carousel.width ) { + carousel.set( i + 2 ); + break; + } else if ( i === 0 ) { + carousel.set( 0 ); + break; + } + } + } else { + carousel.set( carousel.current - self._options.carouselSteps ); + } + }); + }, + + // calculate and set positions + set: function( i ) { + i = Math.max( i, 0 ); + while ( carousel.hooks[i - 1] + carousel.width >= carousel.max && i >= 0 ) { + i--; + } + carousel.current = i; + carousel.animate(); + }, + + // get the last position + getLast: function(i) { + return ( i || carousel.current ) - 1; + }, + + // follow the active image + follow: function(i) { + + //don't follow if position fits + if ( i === 0 || i === carousel.hooks.length - 2 ) { + carousel.set( i ); + return; + } + + // calculate last position + var last = carousel.current; + while( carousel.hooks[last] - carousel.hooks[ carousel.current ] < + carousel.width && last <= carousel.hooks.length ) { + last ++; + } + + // set position + if ( i - 1 < carousel.current ) { + carousel.set( i - 1 ); + } else if ( i + 2 > last) { + carousel.set( i - last + carousel.current + 2 ); + } + }, + + // helper for setting disabled classes + setClasses: function() { + carousel.prev.toggleClass( 'disabled', !carousel.current ); + carousel.next.toggleClass( 'disabled', carousel.hooks[ carousel.current ] + carousel.width >= carousel.max ); + }, + + // the animation method + animate: function(to) { + carousel.setClasses(); + var num = carousel.hooks[ carousel.current ] * -1; + + if ( isNaN( num ) ) { + return; + } + + // FF 24 bug + self.$( 'thumbnails' ).css('left', function() { + return $(this).css('left'); + }); + + Utils.animate(self.get( 'thumbnails' ), { + left: num + },{ + duration: self._options.carouselSpeed, + easing: self._options.easing, + queue: false + }); + } + }; + + // tooltip control + // added in 1.2 + var tooltip = this._tooltip = { + + initialized : false, + + open: false, + + timer: 'tooltip' + self._id, + + swapTimer: 'swap' + self._id, + + init: function() { + + tooltip.initialized = true; + + var css = '.galleria-tooltip{padding:3px 8px;max-width:50%;background:#ffe;color:#000;z-index:3;position:absolute;font-size:11px;line-height:1.3;' + + 'opacity:0;box-shadow:0 0 2px rgba(0,0,0,.4);-moz-box-shadow:0 0 2px rgba(0,0,0,.4);-webkit-box-shadow:0 0 2px rgba(0,0,0,.4);}'; + + Utils.insertStyleTag( css, 'galleria-tooltip' ); + + self.$( 'tooltip' ).css({ + opacity: 0.8, + visibility: 'visible', + display: 'none' + }); + + }, + + // move handler + move: function( e ) { + var mouseX = self.getMousePosition(e).x, + mouseY = self.getMousePosition(e).y, + $elem = self.$( 'tooltip' ), + x = mouseX, + y = mouseY, + height = $elem.outerHeight( true ) + 1, + width = $elem.outerWidth( true ), + limitY = height + 15; + + var maxX = self.$( 'container' ).width() - width - 2, + maxY = self.$( 'container' ).height() - height - 2; + + if ( !isNaN(x) && !isNaN(y) ) { + + x += 10; + y -= ( height+8 ); + + x = Math.max( 0, Math.min( maxX, x ) ); + y = Math.max( 0, Math.min( maxY, y ) ); + + if( mouseY < limitY ) { + y = limitY; + } + + $elem.css({ left: x, top: y }); + } + }, + + // bind elements to the tooltip + // you can bind multiple elementIDs using { elemID : function } or { elemID : string } + // you can also bind single DOM elements using bind(elem, string) + bind: function( elem, value ) { + + // todo: revise if alternative tooltip is needed for mobile devices + if (Galleria.TOUCH) { + return; + } + + if (! tooltip.initialized ) { + tooltip.init(); + } + + var mouseout = function() { + self.$( 'container' ).unbind( 'mousemove', tooltip.move ); + self.clearTimer( tooltip.timer ); + + self.$( 'tooltip' ).stop().animate({ + opacity: 0 + }, 200, function() { + + self.$( 'tooltip' ).hide(); + + self.addTimer( tooltip.swapTimer, function() { + tooltip.open = false; + }, 1000); + }); + }; + + var hover = function( elem, value) { + + tooltip.define( elem, value ); + + $( elem ).hover(function() { + + self.clearTimer( tooltip.swapTimer ); + self.$('container').unbind( 'mousemove', tooltip.move ).bind( 'mousemove', tooltip.move ).trigger( 'mousemove' ); + tooltip.show( elem ); + + self.addTimer( tooltip.timer, function() { + self.$( 'tooltip' ).stop().show().animate({ + opacity: 1 + }); + tooltip.open = true; + + }, tooltip.open ? 0 : 500); + + }, mouseout).click(mouseout); + }; + + if ( typeof value === 'string' ) { + hover( ( elem in self._dom ? self.get( elem ) : elem ), value ); + } else { + // asume elemID here + $.each( elem, function( elemID, val ) { + hover( self.get(elemID), val ); + }); + } + }, + + show: function( elem ) { + + elem = $( elem in self._dom ? self.get(elem) : elem ); + + var text = elem.data( 'tt' ), + mouseup = function( e ) { + + // attach a tiny settimeout to make sure the new tooltip is filled + window.setTimeout( (function( ev ) { + return function() { + tooltip.move( ev ); + }; + }( e )), 10); + + elem.unbind( 'mouseup', mouseup ); + + }; + + text = typeof text === 'function' ? text() : text; + + if ( ! text ) { + return; + } + + self.$( 'tooltip' ).html( text.replace(/\s/, ' ') ); + + // trigger mousemove on mouseup in case of click + elem.bind( 'mouseup', mouseup ); + }, + + define: function( elem, value ) { + + // we store functions, not strings + if (typeof value !== 'function') { + var s = value; + value = function() { + return s; + }; + } + + elem = $( elem in self._dom ? self.get(elem) : elem ).data('tt', value); + + tooltip.show( elem ); + + } + }; + + // internal fullscreen control + var fullscreen = this._fullscreen = { + + scrolled: 0, + + crop: undef, + + active: false, + + prev: $(), + + beforeEnter: function(fn){ fn(); }, + beforeExit: function(fn){ fn(); }, + + keymap: self._keyboard.map, + + parseCallback: function( callback, enter ) { + + return _transitions.active ? function() { + if ( typeof callback == 'function' ) { + callback.call(self); + } + var active = self._controls.getActive(), + next = self._controls.getNext(); + + self._scaleImage( next ); + self._scaleImage( active ); + + if ( enter && self._options.trueFullscreen ) { + // Firefox bug, revise later + $( active.container ).add( next.container ).trigger( 'transitionend' ); + } + + } : callback; + + }, + + enter: function( callback ) { + + fullscreen.beforeEnter(function() { + + callback = fullscreen.parseCallback( callback, true ); + + if ( self._options.trueFullscreen && _nativeFullscreen.support ) { + + // do some stuff prior animation for wmoother transitions + + fullscreen.active = true; + + Utils.forceStyles( self.get('container'), { + width: '100%', + height: '100%' + }); + + self.rescale(); + + if ( Galleria.MAC ) { + if ( !( Galleria.SAFARI && /version\/[1-5]/.test(NAV)) ) { + self.$('container').css('opacity', 0).addClass('fullscreen'); + window.setTimeout(function() { + fullscreen.scale(); + self.$('container').css('opacity', 1); + }, 50); + } else { + self.$('stage').css('opacity', 0); + window.setTimeout(function() { + fullscreen.scale(); + self.$('stage').css('opacity', 1); + },4); + } + } else { + self.$('container').addClass('fullscreen'); + } + + $win.resize( fullscreen.scale ); + + _nativeFullscreen.enter( self, callback, self.get('container') ); + + } else { + + fullscreen.scrolled = $win.scrollTop(); + if( !Galleria.TOUCH ) { + window.scrollTo(0, 0); + } + + fullscreen._enter( callback ); + } + }); + + }, + + _enter: function( callback ) { + + fullscreen.active = true; + + if ( IFRAME ) { + + fullscreen.iframe = (function() { + + var elem, + refer = doc.referrer, + test = doc.createElement('a'), + loc = window.location; + + test.href = refer; + + if( test.protocol != loc.protocol || + test.hostname != loc.hostname || + test.port != loc.port ) { + Galleria.raise('Parent fullscreen not available. Iframe protocol, domains and ports must match.'); + return false; + } + + fullscreen.pd = window.parent.document; + + $( fullscreen.pd ).find('iframe').each(function() { + var idoc = this.contentDocument || this.contentWindow.document; + if ( idoc === doc ) { + elem = this; + return false; + } + }); + + return elem; + }()); + + } + + // hide the image until rescale is complete + Utils.hide( self.getActiveImage() ); + + if ( IFRAME && fullscreen.iframe ) { + fullscreen.iframe.scrolled = $( window.parent ).scrollTop(); + window.parent.scrollTo(0, 0); + } + + var data = self.getData(), + options = self._options, + inBrowser = !self._options.trueFullscreen || !_nativeFullscreen.support, + htmlbody = { + height: '100%', + overflow: 'hidden', + margin:0, + padding:0 + }; + + if (inBrowser) { + + self.$('container').addClass('fullscreen'); + fullscreen.prev = self.$('container').prev(); + + if ( !fullscreen.prev.length ) { + fullscreen.parent = self.$( 'container' ).parent(); + } + + // move + self.$( 'container' ).appendTo( 'body' ); + + // begin styleforce + + Utils.forceStyles(self.get('container'), { + position: Galleria.TOUCH ? 'absolute' : 'fixed', + top: 0, + left: 0, + width: '100%', + height: '100%', + zIndex: 10000 + }); + Utils.forceStyles( DOM().html, htmlbody ); + Utils.forceStyles( DOM().body, htmlbody ); + } + + if ( IFRAME && fullscreen.iframe ) { + Utils.forceStyles( fullscreen.pd.documentElement, htmlbody ); + Utils.forceStyles( fullscreen.pd.body, htmlbody ); + Utils.forceStyles( fullscreen.iframe, $.extend( htmlbody, { + width: '100%', + height: '100%', + top: 0, + left: 0, + position: 'fixed', + zIndex: 10000, + border: 'none' + })); + } + + // temporarily attach some keys + // save the old ones first in a cloned object + fullscreen.keymap = $.extend({}, self._keyboard.map); + + self.attachKeyboard({ + escape: self.exitFullscreen, + right: self.next, + left: self.prev + }); + + // temporarily save the crop + fullscreen.crop = options.imageCrop; + + // set fullscreen options + if ( options.fullscreenCrop != undef ) { + options.imageCrop = options.fullscreenCrop; + } + + // swap to big image if it's different from the display image + if ( data && data.big && data.image !== data.big ) { + var big = new Galleria.Picture(), + cached = big.isCached( data.big ), + index = self.getIndex(), + thumb = self._thumbnails[ index ]; + + self.trigger( { + type: Galleria.LOADSTART, + cached: cached, + rewind: false, + index: index, + imageTarget: self.getActiveImage(), + thumbTarget: thumb, + galleriaData: data + }); + + big.load( data.big, function( big ) { + self._scaleImage( big, { + complete: function( big ) { + self.trigger({ + type: Galleria.LOADFINISH, + cached: cached, + index: index, + rewind: false, + imageTarget: big.image, + thumbTarget: thumb + }); + var image = self._controls.getActive().image; + if ( image ) { + $( image ).width( big.image.width ).height( big.image.height ) + .attr( 'style', $( big.image ).attr('style') ) + .attr( 'src', big.image.src ); + } + } + }); + }); + } + + // init the first rescale and attach callbacks + + self.rescale(function() { + + self.addTimer(false, function() { + // show the image after 50 ms + if ( inBrowser ) { + Utils.show( self.getActiveImage() ); + } + + if (typeof callback === 'function') { + callback.call( self ); + } + self.rescale(); + + }, 100); + + self.trigger( Galleria.FULLSCREEN_ENTER ); + }); + + if ( !inBrowser ) { + Utils.show( self.getActiveImage() ); + } else { + $win.resize( fullscreen.scale ); + } + + }, + + scale : function() { + self.rescale(); + }, + + exit: function( callback ) { + + fullscreen.beforeExit(function() { + + callback = fullscreen.parseCallback( callback ); + + if ( self._options.trueFullscreen && _nativeFullscreen.support ) { + _nativeFullscreen.exit( callback ); + } else { + fullscreen._exit( callback ); + } + }); + }, + + _exit: function( callback ) { + + fullscreen.active = false; + + var inBrowser = !self._options.trueFullscreen || !_nativeFullscreen.support, + $container = self.$( 'container' ).removeClass( 'fullscreen' ); + + // move back + if ( fullscreen.parent ) { + fullscreen.parent.prepend( $container ); + } else { + $container.insertAfter( fullscreen.prev ); + } + + if ( inBrowser ) { + Utils.hide( self.getActiveImage() ); + + // revert all styles + Utils.revertStyles( self.get('container'), DOM().html, DOM().body ); + + // scroll back + if( !Galleria.TOUCH ) { + window.scrollTo(0, fullscreen.scrolled); + } + + // reload iframe src manually + var frame = self._controls.frames[ self._controls.active ]; + if ( frame ) { + frame.image.src = frame.image.src; + } + } + + if ( IFRAME && fullscreen.iframe ) { + Utils.revertStyles( fullscreen.pd.documentElement, fullscreen.pd.body, fullscreen.iframe ); + if ( fullscreen.iframe.scrolled ) { + window.parent.scrollTo(0, fullscreen.iframe.scrolled ); + } + } + + // detach all keyboard events and apply the old keymap + self.detachKeyboard(); + self.attachKeyboard( fullscreen.keymap ); + + // bring back cached options + self._options.imageCrop = fullscreen.crop; + + // return to original image + var big = self.getData().big, + image = self._controls.getActive().image; + + if ( !self.getData().iframe && image && big && big == image.src ) { + + window.setTimeout(function(src) { + return function() { + image.src = src; + }; + }( self.getData().image ), 1 ); + + } + + self.rescale(function() { + self.addTimer(false, function() { + + // show the image after 50 ms + if ( inBrowser ) { + Utils.show( self.getActiveImage() ); + } + + if ( typeof callback === 'function' ) { + callback.call( self ); + } + + $win.trigger( 'resize' ); + + }, 50); + self.trigger( Galleria.FULLSCREEN_EXIT ); + }); + + $win.unbind('resize', fullscreen.scale); + } + }; + + // the internal idle object for controlling idle states + var idle = this._idle = { + + trunk: [], + + bound: false, + + active: false, + + add: function(elem, to, from, hide) { + if (!elem) { + return; + } + if (!idle.bound) { + idle.addEvent(); + } + elem = $(elem); + + if ( typeof from == 'boolean' ) { + hide = from; + from = {}; + } + + from = from || {}; + + var extract = {}, + style; + + for ( style in to ) { + if ( to.hasOwnProperty( style ) ) { + extract[ style ] = elem.css( style ); + } + } + + elem.data('idle', { + from: $.extend( extract, from ), + to: to, + complete: true, + busy: false + }); + + if ( !hide ) { + idle.addTimer(); + } else { + elem.css( to ); + } + idle.trunk.push(elem); + }, + + remove: function(elem) { + + elem = $(elem); + + $.each(idle.trunk, function(i, el) { + if ( el && el.length && !el.not(elem).length ) { + elem.css( elem.data( 'idle' ).from ); + idle.trunk.splice(i, 1); + } + }); + + if (!idle.trunk.length) { + idle.removeEvent(); + self.clearTimer( idle.timer ); + } + }, + + addEvent : function() { + idle.bound = true; + self.$('container').bind( 'mousemove click', idle.showAll ); + if ( self._options.idleMode == 'hover' ) { + self.$('container').bind( 'mouseleave', idle.hide ); + } + }, + + removeEvent : function() { + idle.bound = false; + self.$('container').bind( 'mousemove click', idle.showAll ); + if ( self._options.idleMode == 'hover' ) { + self.$('container').unbind( 'mouseleave', idle.hide ); + } + }, + + addTimer : function() { + if( self._options.idleMode == 'hover' ) { + return; + } + self.addTimer( 'idle', function() { + idle.hide(); + }, self._options.idleTime ); + }, + + hide : function() { + + if ( !self._options.idleMode || self.getIndex() === false ) { + return; + } + + self.trigger( Galleria.IDLE_ENTER ); + + var len = idle.trunk.length; + + $.each( idle.trunk, function(i, elem) { + + var data = elem.data('idle'); + + if (! data) { + return; + } + + elem.data('idle').complete = false; + + Utils.animate( elem, data.to, { + duration: self._options.idleSpeed, + complete: function() { + if ( i == len-1 ) { + idle.active = false; + } + } + }); + }); + }, + + showAll : function() { + + self.clearTimer( 'idle' ); + + $.each( idle.trunk, function( i, elem ) { + idle.show( elem ); + }); + }, + + show: function(elem) { + + var data = elem.data('idle'); + + if ( !idle.active || ( !data.busy && !data.complete ) ) { + + data.busy = true; + + self.trigger( Galleria.IDLE_EXIT ); + + self.clearTimer( 'idle' ); + + Utils.animate( elem, data.from, { + duration: self._options.idleSpeed/2, + complete: function() { + idle.active = true; + $(elem).data('idle').busy = false; + $(elem).data('idle').complete = true; + } + }); + + } + idle.addTimer(); + } + }; + + // internal lightbox object + // creates a predesigned lightbox for simple popups of images in galleria + var lightbox = this._lightbox = { + + width : 0, + + height : 0, + + initialized : false, + + active : null, + + image : null, + + elems : {}, + + keymap: false, + + init : function() { + + if ( lightbox.initialized ) { + return; + } + lightbox.initialized = true; + + // create some elements to work with + var elems = 'overlay box content shadow title info close prevholder prev nextholder next counter image', + el = {}, + op = self._options, + css = '', + abs = 'position:absolute;', + prefix = 'lightbox-', + cssMap = { + overlay: 'position:fixed;display:none;opacity:'+op.overlayOpacity+';filter:alpha(opacity='+(op.overlayOpacity*100)+ + ');top:0;left:0;width:100%;height:100%;background:'+op.overlayBackground+';z-index:99990', + box: 'position:fixed;display:none;width:400px;height:400px;top:50%;left:50%;margin-top:-200px;margin-left:-200px;z-index:99991', + shadow: abs+'background:#000;width:100%;height:100%;', + content: abs+'background-color:#fff;top:10px;left:10px;right:10px;bottom:10px;overflow:hidden', + info: abs+'bottom:10px;left:10px;right:10px;color:#444;font:11px/13px arial,sans-serif;height:13px', + close: abs+'top:10px;right:10px;height:20px;width:20px;background:#fff;text-align:center;cursor:pointer;color:#444;font:16px/22px arial,sans-serif;z-index:99999', + image: abs+'top:10px;left:10px;right:10px;bottom:30px;overflow:hidden;display:block;', + prevholder: abs+'width:50%;top:0;bottom:40px;cursor:pointer;', + nextholder: abs+'width:50%;top:0;bottom:40px;right:-1px;cursor:pointer;', + prev: abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;left:20px;display:none;text-align:center;color:#000;font:bold 16px/36px arial,sans-serif', + next: abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;right:20px;left:auto;display:none;font:bold 16px/36px arial,sans-serif;text-align:center;color:#000', + title: 'float:left', + counter: 'float:right;margin-left:8px;' + }, + hover = function(elem) { + return elem.hover( + function() { $(this).css( 'color', '#bbb' ); }, + function() { $(this).css( 'color', '#444' ); } + ); + }, + appends = {}; + + // IE8 fix for IE's transparent background event "feature" + if ( IE && IE > 7 ) { + cssMap.nextholder += 'background:#000;filter:alpha(opacity=0);'; + cssMap.prevholder += 'background:#000;filter:alpha(opacity=0);'; + } + + // create and insert CSS + $.each(cssMap, function( key, value ) { + css += '.galleria-'+prefix+key+'{'+value+'}'; + }); + + css += '.galleria-'+prefix+'box.iframe .galleria-'+prefix+'prevholder,'+ + '.galleria-'+prefix+'box.iframe .galleria-'+prefix+'nextholder{'+ + 'width:100px;height:100px;top:50%;margin-top:-70px}'; + + Utils.insertStyleTag( css, 'galleria-lightbox' ); + + // create the elements + $.each(elems.split(' '), function( i, elemId ) { + self.addElement( 'lightbox-' + elemId ); + el[ elemId ] = lightbox.elems[ elemId ] = self.get( 'lightbox-' + elemId ); + }); + + // initiate the image + lightbox.image = new Galleria.Picture(); + + // append the elements + $.each({ + box: 'shadow content close prevholder nextholder', + info: 'title counter', + content: 'info image', + prevholder: 'prev', + nextholder: 'next' + }, function( key, val ) { + var arr = []; + $.each( val.split(' '), function( i, prop ) { + arr.push( prefix + prop ); + }); + appends[ prefix+key ] = arr; + }); + + self.append( appends ); + + $( el.image ).append( lightbox.image.container ); + + $( DOM().body ).append( el.overlay, el.box ); + + // add the prev/next nav and bind some controls + + hover( $( el.close ).bind( 'click', lightbox.hide ).html('×') ); + + $.each( ['Prev','Next'], function(i, dir) { + + var $d = $( el[ dir.toLowerCase() ] ).html( /v/.test( dir ) ? '‹ ' : ' ›' ), + $e = $( el[ dir.toLowerCase()+'holder'] ); + + $e.bind( 'click', function() { + lightbox[ 'show' + dir ](); + }); + + // IE7 and touch devices will simply show the nav + if ( IE < 8 || Galleria.TOUCH ) { + $d.show(); + return; + } + + $e.hover( function() { + $d.show(); + }, function(e) { + $d.stop().fadeOut( 200 ); + }); + + }); + $( el.overlay ).bind( 'click', lightbox.hide ); + + // the lightbox animation is slow on ipad + if ( Galleria.IPAD ) { + self._options.lightboxTransitionSpeed = 0; + } + + }, + + rescale: function(event) { + + // calculate + var width = Math.min( $win.width()-40, lightbox.width ), + height = Math.min( $win.height()-60, lightbox.height ), + ratio = Math.min( width / lightbox.width, height / lightbox.height ), + destWidth = Math.round( lightbox.width * ratio ) + 40, + destHeight = Math.round( lightbox.height * ratio ) + 60, + to = { + width: destWidth, + height: destHeight, + 'margin-top': Math.ceil( destHeight / 2 ) *- 1, + 'margin-left': Math.ceil( destWidth / 2 ) *- 1 + }; + + // if rescale event, don't animate + if ( event ) { + $( lightbox.elems.box ).css( to ); + } else { + $( lightbox.elems.box ).animate( to, { + duration: self._options.lightboxTransitionSpeed, + easing: self._options.easing, + complete: function() { + var image = lightbox.image, + speed = self._options.lightboxFadeSpeed; + + self.trigger({ + type: Galleria.LIGHTBOX_IMAGE, + imageTarget: image.image + }); + + $( image.container ).show(); + + $( image.image ).animate({ opacity: 1 }, speed); + Utils.show( lightbox.elems.info, speed ); + } + }); + } + }, + + hide: function() { + + // remove the image + lightbox.image.image = null; + + $win.unbind('resize', lightbox.rescale); + + $( lightbox.elems.box ).hide().find( 'iframe' ).remove(); + + Utils.hide( lightbox.elems.info ); + + self.detachKeyboard(); + self.attachKeyboard( lightbox.keymap ); + + lightbox.keymap = false; + + Utils.hide( lightbox.elems.overlay, 200, function() { + $( this ).hide().css( 'opacity', self._options.overlayOpacity ); + self.trigger( Galleria.LIGHTBOX_CLOSE ); + }); + }, + + showNext: function() { + lightbox.show( self.getNext( lightbox.active ) ); + }, + + showPrev: function() { + lightbox.show( self.getPrev( lightbox.active ) ); + }, + + show: function(index) { + + lightbox.active = index = typeof index === 'number' ? index : self.getIndex() || 0; + + if ( !lightbox.initialized ) { + lightbox.init(); + } + + // trigger the event + self.trigger( Galleria.LIGHTBOX_OPEN ); + + // temporarily attach some keys + // save the old ones first in a cloned object + if ( !lightbox.keymap ) { + + lightbox.keymap = $.extend({}, self._keyboard.map); + + self.attachKeyboard({ + escape: lightbox.hide, + right: lightbox.showNext, + left: lightbox.showPrev + }); + } + + $win.unbind('resize', lightbox.rescale ); + + var data = self.getData(index), + total = self.getDataLength(), + n = self.getNext( index ), + ndata, p, i; + + Utils.hide( lightbox.elems.info ); + + try { + for ( i = self._options.preload; i > 0; i-- ) { + p = new Galleria.Picture(); + ndata = self.getData( n ); + p.preload( ndata.big ? ndata.big : ndata.image ); + n = self.getNext( n ); + } + } catch(e) {} + + lightbox.image.isIframe = ( data.iframe && !data.image ); + + $( lightbox.elems.box ).toggleClass( 'iframe', lightbox.image.isIframe ); + + $( lightbox.image.container ).find( '.galleria-videoicon' ).remove(); + + lightbox.image.load( data.big || data.image || data.iframe, function( image ) { + + if ( image.isIframe ) { + + var cw = $(window).width(), + ch = $(window).height(); + + if ( image.video && self._options.maxVideoSize ) { + var r = Math.min( self._options.maxVideoSize/cw, self._options.maxVideoSize/ch ); + if ( r < 1 ) { + cw *= r; + ch *= r; + } + } + lightbox.width = cw; + lightbox.height = ch; + + } else { + lightbox.width = image.original.width; + lightbox.height = image.original.height; + } + + $( image.image ).css({ + width: image.isIframe ? '100%' : '100.1%', + height: image.isIframe ? '100%' : '100.1%', + top: 0, + bottom: 0, + zIndex: 99998, + opacity: 0, + visibility: 'visible' + }).parent().height('100%'); + + lightbox.elems.title.innerHTML = data.title || ''; + lightbox.elems.counter.innerHTML = (index + 1) + ' / ' + total; + $win.resize( lightbox.rescale ); + lightbox.rescale(); + + if( data.image && data.iframe ) { + + $( lightbox.elems.box ).addClass('iframe'); + + if ( data.video ) { + var $icon = _playIcon( image.container ).hide(); + window.setTimeout(function() { + $icon.fadeIn(200); + }, 200); + } + + $( image.image ).css( 'cursor', 'pointer' ).mouseup((function(data, image) { + return function(e) { + $( lightbox.image.container ).find( '.galleria-videoicon' ).remove(); + e.preventDefault(); + image.isIframe = true; + image.load( data.iframe + ( data.video ? '&autoplay=1' : '' ), { + width: '100%', + height: IE < 8 ? $( lightbox.image.container ).height() : '100%' + }); + }; + }(data, image))); + } + }); + + $( lightbox.elems.overlay ).show().css( 'visibility', 'visible' ); + $( lightbox.elems.box ).show(); + } + }; + + // the internal timeouts object + // provides helper methods for controlling timeouts + + var _timer = this._timer = { + + trunk: {}, + + add: function( id, fn, delay, loop ) { + id = id || new Date().getTime(); + loop = loop || false; + this.clear( id ); + if ( loop ) { + var old = fn; + fn = function() { + old(); + _timer.add( id, fn, delay ); + }; + } + this.trunk[ id ] = window.setTimeout( fn, delay ); + }, + + clear: function( id ) { + + var del = function( i ) { + window.clearTimeout( this.trunk[ i ] ); + delete this.trunk[ i ]; + }, i; + + if ( !!id && id in this.trunk ) { + del.call( this, id ); + + } else if ( typeof id === 'undefined' ) { + for ( i in this.trunk ) { + if ( this.trunk.hasOwnProperty( i ) ) { + del.call( this, i ); + } + } + } + } + }; + + return this; +}; + +// end Galleria constructor + +Galleria.prototype = { + + // bring back the constructor reference + + constructor: Galleria, + + /** + Use this function to initialize the gallery and start loading. + Should only be called once per instance. + + @param {HTMLElement} target The target element + @param {Object} options The gallery options + + @returns Instance + */ + + init: function( target, options ) { + + options = _legacyOptions( options ); + + // save the original ingredients + this._original = { + target: target, + options: options, + data: null + }; + + // save the target here + this._target = this._dom.target = target.nodeName ? target : $( target ).get(0); + + // save the original content for destruction + this._original.html = this._target.innerHTML; + + // push the instance + _instances.push( this ); + + // raise error if no target is detected + if ( !this._target ) { + Galleria.raise('Target not found', true); + return; + } + + // apply options + this._options = { + autoplay: false, + carousel: true, + carouselFollow: true, // legacy, deprecate at 1.3 + carouselSpeed: 400, + carouselSteps: 'auto', + clicknext: false, + dailymotion: { + foreground: '%23EEEEEE', + highlight: '%235BCEC5', + background: '%23222222', + logo: 0, + hideInfos: 1 + }, + dataConfig : function( elem ) { return {}; }, + dataSelector: 'img', + dataSort: false, + dataSource: this._target, + debug: undef, + dummy: undef, // 1.2.5 + easing: 'galleria', + extend: function(options) {}, + fullscreenCrop: undef, // 1.2.5 + fullscreenDoubleTap: true, // 1.2.4 toggles fullscreen on double-tap for touch devices + fullscreenTransition: undef, // 1.2.6 + height: 0, + idleMode: true, // 1.2.4 toggles idleMode + idleTime: 3000, + idleSpeed: 200, + imageCrop: false, + imageMargin: 0, + imagePan: false, + imagePanSmoothness: 12, + imagePosition: '50%', + imageTimeout: undef, // 1.2.5 + initialTransition: undef, // 1.2.4, replaces transitionInitial + keepSource: false, + layerFollow: true, // 1.2.5 + lightbox: false, // 1.2.3 + lightboxFadeSpeed: 200, + lightboxTransitionSpeed: 200, + linkSourceImages: true, + maxScaleRatio: undef, + maxVideoSize: undef, // 1.2.9 + minScaleRatio: undef, // deprecated in 1.2.9 + overlayOpacity: 0.85, + overlayBackground: '#0b0b0b', + pauseOnInteraction: true, + popupLinks: false, + preload: 2, + queue: true, + responsive: true, + show: 0, + showInfo: true, + showCounter: true, + showImagenav: true, + swipe: true, // 1.2.4 -> revised in 1.3 + thumbCrop: true, + thumbEventType: 'click', + thumbMargin: 0, + thumbQuality: 'auto', + thumbDisplayOrder: true, // 1.2.8 + thumbPosition: '50%', // 1.3 + thumbnails: true, + touchTransition: undef, // 1.2.6 + transition: 'fade', + transitionInitial: undef, // legacy, deprecate in 1.3. Use initialTransition instead. + transitionSpeed: 400, + trueFullscreen: true, // 1.2.7 + useCanvas: false, // 1.2.4 + videoPoster: true, // 1.3 + vimeo: { + title: 0, + byline: 0, + portrait: 0, + color: 'aaaaaa' + }, + wait: 5000, // 1.2.7 + width: 'auto', + youtube: { + modestbranding: 1, + autohide: 1, + color: 'white', + hd: 1, + rel: 0, + showinfo: 0 + } + }; + + // legacy support for transitionInitial + this._options.initialTransition = this._options.initialTransition || this._options.transitionInitial; + + // turn off debug + if ( options && options.debug === false ) { + DEBUG = false; + } + + // set timeout + if ( options && typeof options.imageTimeout === 'number' ) { + TIMEOUT = options.imageTimeout; + } + + // set dummy + if ( options && typeof options.dummy === 'string' ) { + DUMMY = options.dummy; + } + + // disable swipe if no touch + if ( !Galleria.TOUCH ) { + this._options.swipe = false; + } + + // hide all content + $( this._target ).children().hide(); + + // Warn for quirks mode + if ( Galleria.QUIRK ) { + Galleria.raise('Your page is in Quirks mode, Galleria may not render correctly. Please validate your HTML and add a correct doctype.'); + } + + // now we just have to wait for the theme... + if ( typeof Galleria.theme === 'object' ) { + this._init(); + } else { + // push the instance into the pool and run it when the theme is ready + _pool.push( this ); + } + + return this; + }, + + // this method should only be called once per instance + // for manipulation of data, use the .load method + + _init: function() { + + var self = this, + options = this._options; + + if ( this._initialized ) { + Galleria.raise( 'Init failed: Gallery instance already initialized.' ); + return this; + } + + this._initialized = true; + + if ( !Galleria.theme ) { + Galleria.raise( 'Init failed: No theme found.', true ); + return this; + } + + // merge the theme & caller options + $.extend( true, options, Galleria.theme.defaults, this._original.options, Galleria.configure.options ); + + // disable options that arent compatible with swipe + if ( options.swipe ) { + options.clicknext = false; + options.imagePan = false; + } + + // check for canvas support + (function( can ) { + + if ( !( 'getContext' in can ) ) { + can = null; + return; + } + + _canvas = _canvas || { + elem: can, + context: can.getContext( '2d' ), + cache: {}, + length: 0 + }; + + }( doc.createElement( 'canvas' ) ) ); + + + Galleria.Fastclick.init( this.get('target' )); + + // bind the gallery to run when data is ready + this.bind( Galleria.DATA, function() { + + // remove big if total pixels are less than 1024 (most phones) + if ( window.screen && window.screen.width && Array.prototype.forEach ) { + + this._data.forEach(function(data) { + + var density = 'devicePixelRatio' in window ? window.devicePixelRatio : 1, + m = Math.max( window.screen.width, window.screen.height ); + + if ( m*density < 1024 ) { + data.big = data.image; + } + }); + } + + // save the new data + this._original.data = this._data; + + // lets show the counter here + this.get('total').innerHTML = this.getDataLength(); + + // cache the container + var $container = this.$( 'container' ); + + // set ratio if height is < 2 + if ( self._options.height < 2 ) { + self._userRatio = self._ratio = self._options.height; + } + + // the gallery is ready, let's just wait for the css + var num = { width: 0, height: 0 }; + var testHeight = function() { + return self.$( 'stage' ).height(); + }; + + // check container and thumbnail height + Utils.wait({ + until: function() { + + // keep trying to get the value + num = self._getWH(); + $container.width( num.width ).height( num.height ); + return testHeight() && num.width && num.height > 50; + + }, + success: function() { + + self._width = num.width; + self._height = num.height; + self._ratio = self._ratio || num.height/num.width; + + // for some strange reason, webkit needs a single setTimeout to play ball + if ( Galleria.WEBKIT ) { + window.setTimeout( function() { + self._run(); + }, 1); + } else { + self._run(); + } + }, + error: function() { + + // Height was probably not set, raise hard errors + + if ( testHeight() ) { + Galleria.raise('Could not extract sufficient width/height of the gallery container. Traced measures: width:' + num.width + 'px, height: ' + num.height + 'px.', true); + } else { + Galleria.raise('Could not extract a stage height from the CSS. Traced height: ' + testHeight() + 'px.', true); + } + }, + timeout: typeof this._options.wait == 'number' ? this._options.wait : false + }); + }); + + // build the gallery frame + this.append({ + 'info-text' : + ['info-title', 'info-description'], + 'info' : + ['info-text'], + 'image-nav' : + ['image-nav-right', 'image-nav-left'], + 'stage' : + ['images', 'loader', 'counter', 'image-nav'], + 'thumbnails-list' : + ['thumbnails'], + 'thumbnails-container' : + ['thumb-nav-left', 'thumbnails-list', 'thumb-nav-right'], + 'container' : + ['stage', 'thumbnails-container', 'info', 'tooltip'] + }); + + Utils.hide( this.$( 'counter' ).append( + this.get( 'current' ), + doc.createTextNode(' / '), + this.get( 'total' ) + ) ); + + this.setCounter('–'); + + Utils.hide( self.get('tooltip') ); + + // add a notouch class on the container to prevent unwanted :hovers on touch devices + this.$( 'container' ).addClass( Galleria.TOUCH ? 'touch' : 'notouch' ); + + // add images to the controls + if ( !this._options.swipe ) { + $.each( new Array(2), function( i ) { + + // create a new Picture instance + var image = new Galleria.Picture(); + + // apply some styles, create & prepend overlay + $( image.container ).css({ + position: 'absolute', + top: 0, + left: 0 + }).prepend( self._layers[i] = $( Utils.create('galleria-layer') ).css({ + position: 'absolute', + top:0, left:0, right:0, bottom:0, + zIndex:2 + })[0] ); + + // append the image + self.$( 'images' ).append( image.container ); + + // reload the controls + self._controls[i] = image; + + // build a frame + var frame = new Galleria.Picture(); + frame.isIframe = true; + + $( frame.container ).attr('class', 'galleria-frame').css({ + position: 'absolute', + top: 0, + left: 0, + zIndex: 4, + background: '#000', + display: 'none' + }).appendTo( image.container ); + + self._controls.frames[i] = frame; + + }); + } + + // some forced generic styling + this.$( 'images' ).css({ + position: 'relative', + top: 0, + left: 0, + width: '100%', + height: '100%' + }); + + if ( options.swipe ) { + this.$( 'images' ).css({ + position: 'absolute', + top: 0, + left: 0, + width: 0, + height: '100%' + }); + this.finger = new Galleria.Finger(this.get('stage'), { + onchange: function(page) { + self.setCounter( page ); + self.pause(); + self.show(page); + }, + oncomplete: function(page) { + + var index = Math.max( 0, Math.min( parseInt( page, 10 ), self.getDataLength() - 1 ) ), + data = self.getData(index); + + if ( !data ) { + return; + } + + self.$( 'images' ).find( 'iframe' ).remove(); + self.$( 'images' ).find( '.galleria-frame' ).css('opacity', 0); + + var src = self.isFullscreen() && data.big ? data.big : ( data.image || data.iframe ), + image = self._controls.slides[index], + cached = image.isCached( src ), + thumb = self._thumbnails[ index ]; + + self.trigger({ + type: Galleria.IMAGE, + cached: cached, + index: index, + imageTarget: image.image, + thumbTarget: thumb.image, + galleriaData: data + }); + + if ( self._options.carousel && self._options.carouselFollow ) { + self._carousel.follow( index ); + } + } + }); + this.bind( Galleria.RESCALE, function() { + this.finger.setup(); + }); + this.$('stage').bind('click', function(e) { + var data = self.getData(); + if ( !data ) { + return; + } + if ( data.iframe ) { + + if ( self.isPlaying() ) { + self.pause(); + } + var frame = self._controls.frames[ self._active ], + w = self._stageWidth, + h = self._stageHeight; + + if ( $( frame.container ).find( 'iframe' ).length ) { + return; + } + + $( frame.container ).css({ + width: w, + height: h, + opacity: 0 + }).show().animate({ + opacity: 1 + }, 200); + + window.setTimeout(function() { + frame.load( data.iframe + ( data.video ? '&autoplay=1' : '' ), { + width: w, + height: h + }, function( frame ) { + self.$( 'container' ).addClass( 'videoplay' ); + frame.scale({ + width: self._stageWidth, + height: self._stageHeight, + iframelimit: data.video ? self._options.maxVideoSize : undef + }); + }); + }, 100); + + return; + } + + if ( data.link ) { + if ( self._options.popupLinks ) { + var win = window.open( data.link, '_blank' ); + } else { + window.location.href = data.link; + } + return; + } + }); + this.bind( Galleria.IMAGE, function(e) { + + self.setCounter( e.index ); + self.setInfo( e.index ); + + var next = this.getNext(), + prev = this.getPrev(); + + var preloads = [prev,next]; + preloads.push(this.getNext(next), this.getPrev(prev), self._controls.slides.length-1); + + var filtered = []; + + $.each(preloads, function(i, val) { + if ( $.inArray(val, filtered) == -1 ) { + filtered.push(val); + } + }); + + $.each(filtered, function(i, loadme) { + var d = self.getData(loadme), + img = self._controls.slides[loadme], + src = self.isFullscreen() && d.big ? d.big : ( d.image || d.iframe ); + + if ( d.iframe && !d.image ) { + img.isIframe = true; + } + + if ( !img.ready ) { + self._controls.slides[loadme].load(src, function(img) { + if ( !img.isIframe ) { + $(img.image).css('visibility', 'hidden'); + } + self._scaleImage(img, { + complete: function(img) { + if ( !img.isIframe ) { + $(img.image).css({ + opacity: 0, + visibility: 'visible' + }).animate({ + opacity: 1 + }, 200); + } + } + }); + }); + } + }); + }); + } + + this.$( 'thumbnails, thumbnails-list' ).css({ + overflow: 'hidden', + position: 'relative' + }); + + // bind image navigation arrows + this.$( 'image-nav-right, image-nav-left' ).bind( 'click', function(e) { + + // tune the clicknext option + if ( options.clicknext ) { + e.stopPropagation(); + } + + // pause if options is set + if ( options.pauseOnInteraction ) { + self.pause(); + } + + // navigate + var fn = /right/.test( this.className ) ? 'next' : 'prev'; + self[ fn ](); + + }); + + // hide controls if chosen to + $.each( ['info','counter','image-nav'], function( i, el ) { + if ( options[ 'show' + el.substr(0,1).toUpperCase() + el.substr(1).replace(/-/,'') ] === false ) { + Utils.moveOut( self.get( el.toLowerCase() ) ); + } + }); + + // load up target content + this.load(); + + // now it's usually safe to remove the content + // IE will never stop loading if we remove it, so let's keep it hidden for IE (it's usually fast enough anyway) + if ( !options.keepSource && !IE ) { + this._target.innerHTML = ''; + } + + // re-append the errors, if they happened before clearing + if ( this.get( 'errors' ) ) { + this.appendChild( 'target', 'errors' ); + } + + // append the gallery frame + this.appendChild( 'target', 'container' ); + + // parse the carousel on each thumb load + if ( options.carousel ) { + var count = 0, + show = options.show; + this.bind( Galleria.THUMBNAIL, function() { + this.updateCarousel(); + if ( ++count == this.getDataLength() && typeof show == 'number' && show > 0 ) { + this._carousel.follow( show ); + } + }); + } + + // bind window resize for responsiveness + if ( options.responsive ) { + $win.bind( 'resize', function() { + if ( !self.isFullscreen() ) { + self.resize(); + } + }); + } + + // double-tap/click fullscreen toggle + + if ( options.fullscreenDoubleTap ) { + + this.$( 'stage' ).bind( 'touchstart', (function() { + var last, cx, cy, lx, ly, now, + getData = function(e) { + return e.originalEvent.touches ? e.originalEvent.touches[0] : e; + }; + self.$( 'stage' ).bind('touchmove', function() { + last = 0; + }); + return function(e) { + if( /(-left|-right)/.test(e.target.className) ) { + return; + } + now = Utils.timestamp(); + cx = getData(e).pageX; + cy = getData(e).pageY; + if ( e.originalEvent.touches.length < 2 && ( now - last < 300 ) && ( cx - lx < 20) && ( cy - ly < 20) ) { + self.toggleFullscreen(); + e.preventDefault(); + return; + } + last = now; + lx = cx; + ly = cy; + }; + }())); + } + + // bind the ons + $.each( Galleria.on.binds, function(i, bind) { + // check if already bound + if ( $.inArray( bind.hash, self._binds ) == -1 ) { + self.bind( bind.type, bind.callback ); + } + }); + + return this; + }, + + addTimer : function() { + this._timer.add.apply( this._timer, Utils.array( arguments ) ); + return this; + }, + + clearTimer : function() { + this._timer.clear.apply( this._timer, Utils.array( arguments ) ); + return this; + }, + + // parse width & height from CSS or options + + _getWH : function() { + + var $container = this.$( 'container' ), + $target = this.$( 'target' ), + self = this, + num = {}, + arr; + + $.each(['width', 'height'], function( i, m ) { + + // first check if options is set + if ( self._options[ m ] && typeof self._options[ m ] === 'number') { + num[ m ] = self._options[ m ]; + } else { + + arr = [ + Utils.parseValue( $container.css( m ) ), // the container css height + Utils.parseValue( $target.css( m ) ), // the target css height + $container[ m ](), // the container jQuery method + $target[ m ]() // the target jQuery method + ]; + + // if first time, include the min-width & min-height + if ( !self[ '_'+m ] ) { + arr.splice(arr.length, + Utils.parseValue( $container.css( 'min-'+m ) ), + Utils.parseValue( $target.css( 'min-'+m ) ) + ); + } + + // else extract the measures from different sources and grab the highest value + num[ m ] = Math.max.apply( Math, arr ); + } + }); + + // allow setting a height ratio instead of exact value + // useful when doing responsive galleries + + if ( self._userRatio ) { + num.height = num.width * self._userRatio; + } + + return num; + }, + + // Creates the thumbnails and carousel + // can be used at any time, f.ex when the data object is manipulated + // push is an optional argument with pushed images + + _createThumbnails : function( push ) { + + this.get( 'total' ).innerHTML = this.getDataLength(); + + var src, + thumb, + data, + + $container, + + self = this, + o = this._options, + + i = push ? this._data.length - push.length : 0, + chunk = i, + + thumbchunk = [], + loadindex = 0, + + gif = IE < 8 ? 'http://upload.wikimedia.org/wikipedia/commons/c/c0/Blank.gif' : + 'data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw%3D%3D', + + // get previously active thumbnail, if exists + active = (function() { + var a = self.$('thumbnails').find('.active'); + if ( !a.length ) { + return false; + } + return a.find('img').attr('src'); + }()), + + // cache the thumbnail option + optval = typeof o.thumbnails === 'string' ? o.thumbnails.toLowerCase() : null, + + // move some data into the instance + // for some reason, jQuery cant handle css(property) when zooming in FF, breaking the gallery + // so we resort to getComputedStyle for browsers who support it + getStyle = function( prop ) { + return doc.defaultView && doc.defaultView.getComputedStyle ? + doc.defaultView.getComputedStyle( thumb.container, null )[ prop ] : + $container.css( prop ); + }, + + fake = function(image, index, container) { + return function() { + $( container ).append( image ); + self.trigger({ + type: Galleria.THUMBNAIL, + thumbTarget: image, + index: index, + galleriaData: self.getData( index ) + }); + }; + }, + + onThumbEvent = function( e ) { + + // pause if option is set + if ( o.pauseOnInteraction ) { + self.pause(); + } + + // extract the index from the data + var index = $( e.currentTarget ).data( 'index' ); + if ( self.getIndex() !== index ) { + self.show( index ); + } + + e.preventDefault(); + }, + + thumbComplete = function( thumb, callback ) { + + $( thumb.container ).css( 'visibility', 'visible' ); + self.trigger({ + type: Galleria.THUMBNAIL, + thumbTarget: thumb.image, + index: thumb.data.order, + galleriaData: self.getData( thumb.data.order ) + }); + + if ( typeof callback == 'function' ) { + callback.call( self, thumb ); + } + }, + + onThumbLoad = function( thumb, callback ) { + + // scale when ready + thumb.scale({ + width: thumb.data.width, + height: thumb.data.height, + crop: o.thumbCrop, + margin: o.thumbMargin, + canvas: o.useCanvas, + position: o.thumbPosition, + complete: function( thumb ) { + + // shrink thumbnails to fit + var top = ['left', 'top'], + arr = ['Width', 'Height'], + m, + css, + data = self.getData( thumb.index ); + + // calculate shrinked positions + $.each(arr, function( i, measure ) { + m = measure.toLowerCase(); + if ( (o.thumbCrop !== true || o.thumbCrop === m ) ) { + css = {}; + css[ m ] = thumb[ m ]; + $( thumb.container ).css( css ); + css = {}; + css[ top[ i ] ] = 0; + $( thumb.image ).css( css ); + } + + // cache outer measures + thumb[ 'outer' + measure ] = $( thumb.container )[ 'outer' + measure ]( true ); + }); + + // set high quality if downscale is moderate + Utils.toggleQuality( thumb.image, + o.thumbQuality === true || + ( o.thumbQuality === 'auto' && thumb.original.width < thumb.width * 3 ) + ); + + if ( o.thumbDisplayOrder && !thumb.lazy ) { + + $.each( thumbchunk, function( i, th ) { + if ( i === loadindex && th.ready && !th.displayed ) { + + loadindex++; + th.displayed = true; + + thumbComplete( th, callback ); + + return; + } + }); + } else { + thumbComplete( thumb, callback ); + } + } + }); + }; + + if ( !push ) { + this._thumbnails = []; + this.$( 'thumbnails' ).empty(); + } + + // loop through data and create thumbnails + for( ; this._data[ i ]; i++ ) { + + data = this._data[ i ]; + + // get source from thumb or image + src = data.thumb || data.image; + + if ( ( o.thumbnails === true || optval == 'lazy' ) && ( data.thumb || data.image ) ) { + + // add a new Picture instance + thumb = new Galleria.Picture(i); + + // save the index + thumb.index = i; + + // flag displayed + thumb.displayed = false; + + // flag lazy + thumb.lazy = false; + + // flag video + thumb.video = false; + + // append the thumbnail + this.$( 'thumbnails' ).append( thumb.container ); + + // cache the container + $container = $( thumb.container ); + + // hide it + $container.css( 'visibility', 'hidden' ); + + thumb.data = { + width : Utils.parseValue( getStyle( 'width' ) ), + height : Utils.parseValue( getStyle( 'height' ) ), + order : i, + src : src + }; + + // grab & reset size for smoother thumbnail loads + if ( o.thumbCrop !== true ) { + $container.css( { width: 'auto', height: 'auto' } ); + } else { + $container.css( { width: thumb.data.width, height: thumb.data.height } ); + } + + // load the thumbnail + if ( optval == 'lazy' ) { + + $container.addClass( 'lazy' ); + + thumb.lazy = true; + + thumb.load( gif, { + height: thumb.data.height, + width: thumb.data.width + }); + + } else { + thumb.load( src, onThumbLoad ); + } + + // preload all images here + if ( o.preload === 'all' ) { + thumb.preload( data.image ); + } + + // create empty spans if thumbnails is set to 'empty' + } else if ( data.iframe || optval === 'empty' || optval === 'numbers' ) { + + thumb = { + container: Utils.create( 'galleria-image' ), + image: Utils.create( 'img', 'span' ), + ready: true, + data: { + order: i + } + }; + + // create numbered thumbnails + if ( optval === 'numbers' ) { + $( thumb.image ).text( i + 1 ); + } + + if ( data.iframe ) { + $( thumb.image ).addClass( 'iframe' ); + } + + this.$( 'thumbnails' ).append( thumb.container ); + + // we need to "fake" a loading delay before we append and trigger + // 50+ should be enough + + window.setTimeout( ( fake )( thumb.image, i, thumb.container ), 50 + ( i*20 ) ); + + // create null object to silent errors + } else { + thumb = { + container: null, + image: null + }; + } + + // add events for thumbnails + // you can control the event type using thumb_event_type + // we'll add the same event to the source if it's kept + + $( thumb.container ).add( o.keepSource && o.linkSourceImages ? data.original : null ) + .data('index', i).bind( o.thumbEventType, onThumbEvent ) + .data('thumbload', onThumbLoad); + + if (active === src) { + $( thumb.container ).addClass( 'active' ); + } + + this._thumbnails.push( thumb ); + } + + thumbchunk = this._thumbnails.slice( chunk ); + + return this; + }, + + /** + Lazy-loads thumbnails. + You can call this method to load lazy thumbnails at run time + + @param {Array|Number} index Index or array of indexes of thumbnails to be loaded + @param {Function} complete Callback that is called when all lazy thumbnails have been loaded + + @returns Instance + */ + + lazyLoad: function( index, complete ) { + + var arr = index.constructor == Array ? index : [ index ], + self = this, + loaded = 0; + + $.each( arr, function(i, ind) { + + if ( ind > self._thumbnails.length - 1 ) { + return; + } + + var thumb = self._thumbnails[ ind ], + data = thumb.data, + callback = function() { + if ( ++loaded == arr.length && typeof complete == 'function' ) { + complete.call( self ); + } + }, + thumbload = $( thumb.container ).data( 'thumbload' ); + if ( thumb.video ) { + thumbload.call( self, thumb, callback ); + } else { + thumb.load( data.src , function( thumb ) { + thumbload.call( self, thumb, callback ); + }); + } + }); + + return this; + + }, + + /** + Lazy-loads thumbnails in chunks. + This method automatcally chops up the loading process of many thumbnails into chunks + + @param {Number} size Size of each chunk to be loaded + @param {Number} [delay] Delay between each loads + + @returns Instance + */ + + lazyLoadChunks: function( size, delay ) { + + var len = this.getDataLength(), + i = 0, + n = 0, + arr = [], + temp = [], + self = this; + + delay = delay || 0; + + for( ; i 50 ); // what is an acceptable height? + }, + + success: function() { + + // save the instance + _galleries.push( self ); + + // postrun some stuff after the gallery is ready + + // create the touch slider + if ( self._options.swipe ) { + + var $images = self.$( 'images' ).width( self.getDataLength() * self._stageWidth ); + $.each( new Array( self.getDataLength() ), function(i) { + + var image = new Galleria.Picture(), + data = self.getData(i); + + $( image.container ).css({ + position: 'absolute', + top: 0, + left: self._stageWidth*i + }).prepend( self._layers[i] = $( Utils.create('galleria-layer') ).css({ + position: 'absolute', + top:0, left:0, right:0, bottom:0, + zIndex:2 + })[0] ).appendTo( $images ); + + if( data.video ) { + _playIcon( image.container ); + } + + self._controls.slides.push(image); + + var frame = new Galleria.Picture(); + frame.isIframe = true; + + $( frame.container ).attr('class', 'galleria-frame').css({ + position: 'absolute', + top: 0, + left: 0, + zIndex: 4, + background: '#000', + display: 'none' + }).appendTo( image.container ); + + self._controls.frames.push(frame); + }); + + self.finger.setup(); + } + + // show counter + Utils.show( self.get('counter') ); + + // bind carousel nav + if ( self._options.carousel ) { + self._carousel.bindControls(); + } + + // start autoplay + if ( self._options.autoplay ) { + + self.pause(); + + if ( typeof self._options.autoplay === 'number' ) { + self._playtime = self._options.autoplay; + } + + self._playing = true; + } + // if second load, just do the show and return + if ( self._firstrun ) { + + if ( self._options.autoplay ) { + self.trigger( Galleria.PLAY ); + } + + if ( typeof self._options.show === 'number' ) { + self.show( self._options.show ); + } + return; + } + + self._firstrun = true; + + // initialize the History plugin + if ( Galleria.History ) { + + // bind the show method + Galleria.History.change(function( value ) { + + // if ID is NaN, the user pressed back from the first image + // return to previous address + if ( isNaN( value ) ) { + window.history.go(-1); + + // else show the image + } else { + self.show( value, undef, true ); + } + }); + } + + self.trigger( Galleria.READY ); + + // call the theme init method + Galleria.theme.init.call( self, self._options ); + + // Trigger Galleria.ready + $.each( Galleria.ready.callbacks, function(i ,fn) { + if ( typeof fn == 'function' ) { + fn.call( self, self._options ); + } + }); + Galleria.ready.callbacks = []; + + // call the extend option + self._options.extend.call( self, self._options ); + + // show the initial image + // first test for permalinks in history + if ( /^[0-9]{1,4}$/.test( HASH ) && Galleria.History ) { + self.show( HASH, undef, true ); + + } else if( self._data[ self._options.show ] ) { + self.show( self._options.show ); + } + + // play trigger + if ( self._options.autoplay ) { + self.trigger( Galleria.PLAY ); + } + }, + + error: function() { + Galleria.raise('Stage width or height is too small to show the gallery. Traced measures: width:' + self._stageWidth + 'px, height: ' + self._stageHeight + 'px.', true); + } + + }); + }, + + /** + Loads data into the gallery. + You can call this method on an existing gallery to reload the gallery with new data. + + @param {Array|string} [source] Optional JSON array of data or selector of where to find data in the document. + Defaults to the Galleria target or dataSource option. + + @param {string} [selector] Optional element selector of what elements to parse. + Defaults to 'img'. + + @param {Function} [config] Optional function to modify the data extraction proceedure from the selector. + See the dataConfig option for more information. + + @returns Instance + */ + + load : function( source, selector, config ) { + + var self = this, + o = this._options; + + // empty the data array + this._data = []; + + // empty the thumbnails + this._thumbnails = []; + this.$('thumbnails').empty(); + + // shorten the arguments + if ( typeof selector === 'function' ) { + config = selector; + selector = null; + } + + // use the source set by target + source = source || o.dataSource; + + // use selector set by option + selector = selector || o.dataSelector; + + // use the dataConfig set by option + config = config || o.dataConfig; + + // if source is a true object, make it into an array + if( $.isPlainObject( source ) ) { + source = [source]; + } + + // check if the data is an array already + if ( $.isArray( source ) ) { + if ( this.validate( source ) ) { + this._data = source; + } else { + Galleria.raise( 'Load failed: JSON Array not valid.' ); + } + } else { + + // add .video and .iframe to the selector (1.2.7) + selector += ',.video,.iframe'; + + // loop through images and set data + $( source ).find( selector ).each( function( i, elem ) { + + elem = $( elem ); + var data = {}, + parent = elem.parent(), + href = parent.attr( 'href' ), + rel = parent.attr( 'rel' ); + + if( href && ( elem[0].nodeName == 'IMG' || elem.hasClass('video') ) && _videoTest( href ) ) { + data.video = href; + } else if( href && elem.hasClass('iframe') ) { + data.iframe = href; + } else { + data.image = data.big = href; + } + + if ( rel ) { + data.big = rel; + } + + // alternative extraction from HTML5 data attribute, added in 1.2.7 + $.each( 'big title description link layer image'.split(' '), function( i, val ) { + if ( elem.data(val) ) { + data[ val ] = elem.data(val).toString(); + } + }); + + if ( !data.big ) { + data.big = data.image; + } + + // mix default extractions with the hrefs and config + // and push it into the data array + self._data.push( $.extend({ + + title: elem.attr('title') || '', + thumb: elem.attr('src'), + image: elem.attr('src'), + big: elem.attr('src'), + description: elem.attr('alt') || '', + link: elem.attr('longdesc'), + original: elem.get(0) // saved as a reference + + }, data, config( elem ) ) ); + + }); + } + + if ( typeof o.dataSort == 'function' ) { + protoArray.sort.call( this._data, o.dataSort ); + } else if ( o.dataSort == 'random' ) { + this._data.sort( function() { + return Math.round(Math.random())-0.5; + }); + } + + // trigger the DATA event and return + if ( this.getDataLength() ) { + this._parseData( function() { + this.trigger( Galleria.DATA ); + } ); + } + return this; + + }, + + // make sure the data works properly + _parseData : function( callback ) { + + var self = this, + current, + ready = false, + onload = function() { + var complete = true; + $.each( self._data, function( i, data ) { + if ( data.loading ) { + complete = false; + return false; + } + }); + if ( complete && !ready ) { + ready = true; + callback.call( self ); + } + }; + + $.each( this._data, function( i, data ) { + + current = self._data[ i ]; + + // copy image as thumb if no thumb exists + if ( 'thumb' in data === false ) { + current.thumb = data.image; + } + // copy image as big image if no biggie exists + if ( !data.big ) { + current.big = data.image; + } + // parse video + if ( 'video' in data ) { + var result = _videoTest( data.video ); + + if ( result ) { + current.iframe = new Video(result.provider, result.id ).embed() + (function() { + + // add options + if ( typeof self._options[ result.provider ] == 'object' ) { + var str = '?', arr = []; + $.each( self._options[ result.provider ], function( key, val ) { + arr.push( key + '=' + val ); + }); + + // small youtube specifics, perhaps move to _video later + if ( result.provider == 'youtube' ) { + arr = ['wmode=opaque'].concat(arr); + } + return str + arr.join('&'); + } + return ''; + }()); + + // pre-fetch video providers media + + if( !current.thumb || !current.image ) { + $.each( ['thumb', 'image'], function( i, type ) { + if ( type == 'image' && !self._options.videoPoster ) { + current.image = undef; + return; + } + var video = new Video( result.provider, result.id ); + if ( !current[ type ] ) { + current.loading = true; + video.getMedia( type, (function(current, type) { + return function(src) { + current[ type ] = src; + if ( type == 'image' && !current.big ) { + current.big = current.image; + } + delete current.loading; + onload(); + }; + }( current, type ))); + } + }); + } + } + } + }); + + onload(); + + return this; + }, + + /** + Destroy the Galleria instance and recover the original content + + @example this.destroy(); + + @returns Instance + */ + + destroy : function() { + this.$( 'target' ).data( 'galleria', null ); + this.$( 'container' ).unbind( 'galleria' ); + this.get( 'target' ).innerHTML = this._original.html; + this.clearTimer(); + Utils.removeFromArray( _instances, this ); + Utils.removeFromArray( _galleries, this ); + if ( Galleria._waiters.length ) { + $.each( Galleria._waiters, function( i, w ) { + if ( w ) window.clearTimeout( w ); + }); + } + return this; + }, + + /** + Adds and/or removes images from the gallery + Works just like Array.splice + https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice + + @example this.splice( 2, 4 ); // removes 4 images after the second image + + @returns Instance + */ + + splice : function() { + var self = this, + args = Utils.array( arguments ); + window.setTimeout(function() { + protoArray.splice.apply( self._data, args ); + self._parseData( function() { + self._createThumbnails(); + }); + },2); + return self; + }, + + /** + Append images to the gallery + Works just like Array.push + https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push + + @example this.push({ image: 'image1.jpg' }); // appends the image to the gallery + + @returns Instance + */ + + push : function() { + var self = this, + args = Utils.array( arguments ); + + if ( args.length == 1 && args[0].constructor == Array ) { + args = args[0]; + } + + window.setTimeout(function() { + protoArray.push.apply( self._data, args ); + self._parseData( function() { + self._createThumbnails( args ); + }); + }, 2); + return self; + }, + + _getActive : function() { + return this._controls.getActive(); + }, + + validate : function( data ) { + // todo: validate a custom data array + return true; + }, + + /** + Bind any event to Galleria + + @param {string} type The Event type to listen for + @param {Function} fn The function to execute when the event is triggered + + @example this.bind( 'image', function() { Galleria.log('image shown') }); + + @returns Instance + */ + + bind : function(type, fn) { + + // allow 'image' instead of Galleria.IMAGE + type = _patchEvent( type ); + + this.$( 'container' ).bind( type, this.proxy(fn) ); + return this; + }, + + /** + Unbind any event to Galleria + + @param {string} type The Event type to forget + + @returns Instance + */ + + unbind : function(type) { + + type = _patchEvent( type ); + + this.$( 'container' ).unbind( type ); + return this; + }, + + /** + Manually trigger a Galleria event + + @param {string} type The Event to trigger + + @returns Instance + */ + + trigger : function( type ) { + + type = typeof type === 'object' ? + $.extend( type, { scope: this } ) : + { type: _patchEvent( type ), scope: this }; + + this.$( 'container' ).trigger( type ); + + return this; + }, + + /** + Assign an "idle state" to any element. + The idle state will be applied after a certain amount of idle time + Useful to hide f.ex navigation when the gallery is inactive + + @param {HTMLElement|string} elem The Dom node or selector to apply the idle state to + @param {Object} styles the CSS styles to apply when in idle mode + @param {Object} [from] the CSS styles to apply when in normal + @param {Boolean} [hide] set to true if you want to hide it first + + @example addIdleState( this.get('image-nav'), { opacity: 0 }); + @example addIdleState( '.galleria-image-nav', { top: -200 }, true); + + @returns Instance + */ + + addIdleState: function( elem, styles, from, hide ) { + this._idle.add.apply( this._idle, Utils.array( arguments ) ); + return this; + }, + + /** + Removes any idle state previously set using addIdleState() + + @param {HTMLElement|string} elem The Dom node or selector to remove the idle state from. + + @returns Instance + */ + + removeIdleState: function( elem ) { + this._idle.remove.apply( this._idle, Utils.array( arguments ) ); + return this; + }, + + /** + Force Galleria to enter idle mode. + + @returns Instance + */ + + enterIdleMode: function() { + this._idle.hide(); + return this; + }, + + /** + Force Galleria to exit idle mode. + + @returns Instance + */ + + exitIdleMode: function() { + this._idle.showAll(); + return this; + }, + + /** + Enter FullScreen mode + + @param {Function} callback the function to be executed when the fullscreen mode is fully applied. + + @returns Instance + */ + + enterFullscreen: function( callback ) { + this._fullscreen.enter.apply( this, Utils.array( arguments ) ); + return this; + }, + + /** + Exits FullScreen mode + + @param {Function} callback the function to be executed when the fullscreen mode is fully applied. + + @returns Instance + */ + + exitFullscreen: function( callback ) { + this._fullscreen.exit.apply( this, Utils.array( arguments ) ); + return this; + }, + + /** + Toggle FullScreen mode + + @param {Function} callback the function to be executed when the fullscreen mode is fully applied or removed. + + @returns Instance + */ + + toggleFullscreen: function( callback ) { + this._fullscreen[ this.isFullscreen() ? 'exit' : 'enter'].apply( this, Utils.array( arguments ) ); + return this; + }, + + /** + Adds a tooltip to any element. + You can also call this method with an object as argument with elemID:value pairs to apply tooltips to (see examples) + + @param {HTMLElement} elem The DOM Node to attach the event to + @param {string|Function} value The tooltip message. Can also be a function that returns a string. + + @example this.bindTooltip( this.get('thumbnails'), 'My thumbnails'); + @example this.bindTooltip( this.get('thumbnails'), function() { return 'My thumbs' }); + @example this.bindTooltip( { image_nav: 'Navigation' }); + + @returns Instance + */ + + bindTooltip: function( elem, value ) { + this._tooltip.bind.apply( this._tooltip, Utils.array(arguments) ); + return this; + }, + + /** + Note: this method is deprecated. Use refreshTooltip() instead. + + Redefine a tooltip. + Use this if you want to re-apply a tooltip value to an already bound tooltip element. + + @param {HTMLElement} elem The DOM Node to attach the event to + @param {string|Function} value The tooltip message. Can also be a function that returns a string. + + @returns Instance + */ + + defineTooltip: function( elem, value ) { + this._tooltip.define.apply( this._tooltip, Utils.array(arguments) ); + return this; + }, + + /** + Refresh a tooltip value. + Use this if you want to change the tooltip value at runtime, f.ex if you have a play/pause toggle. + + @param {HTMLElement} elem The DOM Node that has a tooltip that should be refreshed + + @returns Instance + */ + + refreshTooltip: function( elem ) { + this._tooltip.show.apply( this._tooltip, Utils.array(arguments) ); + return this; + }, + + /** + Open a pre-designed lightbox with the currently active image. + You can control some visuals using gallery options. + + @returns Instance + */ + + openLightbox: function() { + this._lightbox.show.apply( this._lightbox, Utils.array( arguments ) ); + return this; + }, + + /** + Close the lightbox. + + @returns Instance + */ + + closeLightbox: function() { + this._lightbox.hide.apply( this._lightbox, Utils.array( arguments ) ); + return this; + }, + + /** + Get the currently active image element. + + @returns {HTMLElement} The image element + */ + + getActiveImage: function() { + var active = this._getActive(); + return active ? active.image : undef; + }, + + /** + Get the currently active thumbnail element. + + @returns {HTMLElement} The thumbnail element + */ + + getActiveThumb: function() { + return this._thumbnails[ this._active ].image || undef; + }, + + /** + Get the mouse position relative to the gallery container + + @param e The mouse event + + @example + +var gallery = this; +$(document).mousemove(function(e) { + console.log( gallery.getMousePosition(e).x ); +}); + + @returns {Object} Object with x & y of the relative mouse postion + */ + + getMousePosition : function(e) { + return { + x: e.pageX - this.$( 'container' ).offset().left, + y: e.pageY - this.$( 'container' ).offset().top + }; + }, + + /** + Adds a panning effect to the image + + @param [img] The optional image element. If not specified it takes the currently active image + + @returns Instance + */ + + addPan : function( img ) { + + if ( this._options.imageCrop === false ) { + return; + } + + img = $( img || this.getActiveImage() ); + + // define some variables and methods + var self = this, + x = img.width() / 2, + y = img.height() / 2, + destX = parseInt( img.css( 'left' ), 10 ), + destY = parseInt( img.css( 'top' ), 10 ), + curX = destX || 0, + curY = destY || 0, + distX = 0, + distY = 0, + active = false, + ts = Utils.timestamp(), + cache = 0, + move = 0, + + // positions the image + position = function( dist, cur, pos ) { + if ( dist > 0 ) { + move = Math.round( Math.max( dist * -1, Math.min( 0, cur ) ) ); + if ( cache !== move ) { + + cache = move; + + if ( IE === 8 ) { // scroll is faster for IE + img.parent()[ 'scroll' + pos ]( move * -1 ); + } else { + var css = {}; + css[ pos.toLowerCase() ] = move; + img.css(css); + } + } + } + }, + + // calculates mouse position after 50ms + calculate = function(e) { + if (Utils.timestamp() - ts < 50) { + return; + } + active = true; + x = self.getMousePosition(e).x; + y = self.getMousePosition(e).y; + }, + + // the main loop to check + loop = function(e) { + + if (!active) { + return; + } + + distX = img.width() - self._stageWidth; + distY = img.height() - self._stageHeight; + destX = x / self._stageWidth * distX * -1; + destY = y / self._stageHeight * distY * -1; + curX += ( destX - curX ) / self._options.imagePanSmoothness; + curY += ( destY - curY ) / self._options.imagePanSmoothness; + + position( distY, curY, 'Top' ); + position( distX, curX, 'Left' ); + + }; + + // we need to use scroll in IE8 to speed things up + if ( IE === 8 ) { + + img.parent().scrollTop( curY * -1 ).scrollLeft( curX * -1 ); + img.css({ + top: 0, + left: 0 + }); + + } + + // unbind and bind event + this.$( 'stage' ).unbind( 'mousemove', calculate ).bind( 'mousemove', calculate ); + + // loop the loop + this.addTimer( 'pan' + self._id, loop, 50, true); + + return this; + }, + + /** + Brings the scope into any callback + + @param fn The callback to bring the scope into + @param [scope] Optional scope to bring + + @example $('#fullscreen').click( this.proxy(function() { this.enterFullscreen(); }) ) + + @returns {Function} Return the callback with the gallery scope + */ + + proxy : function( fn, scope ) { + if ( typeof fn !== 'function' ) { + return F; + } + scope = scope || this; + return function() { + return fn.apply( scope, Utils.array( arguments ) ); + }; + }, + + /** + Removes the panning effect set by addPan() + + @returns Instance + */ + + removePan: function() { + + // todo: doublecheck IE8 + + this.$( 'stage' ).unbind( 'mousemove' ); + + this.clearTimer( 'pan' + this._id ); + + return this; + }, + + /** + Adds an element to the Galleria DOM array. + When you add an element here, you can access it using element ID in many API calls + + @param {string} id The element ID you wish to use. You can add many elements by adding more arguments. + + @example addElement('mybutton'); + @example addElement('mybutton','mylink'); + + @returns Instance + */ + + addElement : function( id ) { + + var dom = this._dom; + + $.each( Utils.array(arguments), function( i, blueprint ) { + dom[ blueprint ] = Utils.create( 'galleria-' + blueprint ); + }); + + return this; + }, + + /** + Attach keyboard events to Galleria + + @param {Object} map The map object of events. + Possible keys are 'UP', 'DOWN', 'LEFT', 'RIGHT', 'RETURN', 'ESCAPE', 'BACKSPACE', and 'SPACE'. + + @example + +this.attachKeyboard({ + right: this.next, + left: this.prev, + up: function() { + console.log( 'up key pressed' ) + } +}); + + @returns Instance + */ + + attachKeyboard : function( map ) { + this._keyboard.attach.apply( this._keyboard, Utils.array( arguments ) ); + return this; + }, + + /** + Detach all keyboard events to Galleria + + @returns Instance + */ + + detachKeyboard : function() { + this._keyboard.detach.apply( this._keyboard, Utils.array( arguments ) ); + return this; + }, + + /** + Fast helper for appending galleria elements that you added using addElement() + + @param {string} parentID The parent element ID where the element will be appended + @param {string} childID the element ID that should be appended + + @example this.addElement('myElement'); + this.appendChild( 'info', 'myElement' ); + + @returns Instance + */ + + appendChild : function( parentID, childID ) { + this.$( parentID ).append( this.get( childID ) || childID ); + return this; + }, + + /** + Fast helper for prepending galleria elements that you added using addElement() + + @param {string} parentID The parent element ID where the element will be prepended + @param {string} childID the element ID that should be prepended + + @example + +this.addElement('myElement'); +this.prependChild( 'info', 'myElement' ); + + @returns Instance + */ + + prependChild : function( parentID, childID ) { + this.$( parentID ).prepend( this.get( childID ) || childID ); + return this; + }, + + /** + Remove an element by blueprint + + @param {string} elemID The element to be removed. + You can remove multiple elements by adding arguments. + + @returns Instance + */ + + remove : function( elemID ) { + this.$( Utils.array( arguments ).join(',') ).remove(); + return this; + }, + + // a fast helper for building dom structures + // leave this out of the API for now + + append : function( data ) { + var i, j; + for( i in data ) { + if ( data.hasOwnProperty( i ) ) { + if ( data[i].constructor === Array ) { + for( j = 0; data[i][j]; j++ ) { + this.appendChild( i, data[i][j] ); + } + } else { + this.appendChild( i, data[i] ); + } + } + } + return this; + }, + + // an internal helper for scaling according to options + _scaleImage : function( image, options ) { + + image = image || this._controls.getActive(); + + // janpub (JH) fix: + // image might be unselected yet + // e.g. when external logics rescales the gallery on window resize events + if( !image ) { + return; + } + + var complete, + + scaleLayer = function( img ) { + $( img.container ).children(':first').css({ + top: Math.max(0, Utils.parseValue( img.image.style.top )), + left: Math.max(0, Utils.parseValue( img.image.style.left )), + width: Utils.parseValue( img.image.width ), + height: Utils.parseValue( img.image.height ) + }); + }; + + options = $.extend({ + width: this._stageWidth, + height: this._stageHeight, + crop: this._options.imageCrop, + max: this._options.maxScaleRatio, + min: this._options.minScaleRatio, + margin: this._options.imageMargin, + position: this._options.imagePosition, + iframelimit: this._options.maxVideoSize + }, options ); + + if ( this._options.layerFollow && this._options.imageCrop !== true ) { + + if ( typeof options.complete == 'function' ) { + complete = options.complete; + options.complete = function() { + complete.call( image, image ); + scaleLayer( image ); + }; + } else { + options.complete = scaleLayer; + } + + } else { + $( image.container ).children(':first').css({ top: 0, left: 0 }); + } + + image.scale( options ); + return this; + }, + + /** + Updates the carousel, + useful if you resize the gallery and want to re-check if the carousel nav is needed. + + @returns Instance + */ + + updateCarousel : function() { + this._carousel.update(); + return this; + }, + + /** + Resize the entire gallery container + + @param {Object} [measures] Optional object with width/height specified + @param {Function} [complete] The callback to be called when the scaling is complete + + @returns Instance + */ + + resize : function( measures, complete ) { + + if ( typeof measures == 'function' ) { + complete = measures; + measures = undef; + } + + measures = $.extend( { width:0, height:0 }, measures ); + + var self = this, + $container = this.$( 'container' ); + + $.each( measures, function( m, val ) { + if ( !val ) { + $container[ m ]( 'auto' ); + measures[ m ] = self._getWH()[ m ]; + } + }); + + $.each( measures, function( m, val ) { + $container[ m ]( val ); + }); + + return this.rescale( complete ); + + }, + + /** + Rescales the gallery + + @param {number} width The target width + @param {number} height The target height + @param {Function} complete The callback to be called when the scaling is complete + + @returns Instance + */ + + rescale : function( width, height, complete ) { + + var self = this; + + // allow rescale(fn) + if ( typeof width === 'function' ) { + complete = width; + width = undef; + } + + var scale = function() { + + // set stagewidth + self._stageWidth = width || self.$( 'stage' ).width(); + self._stageHeight = height || self.$( 'stage' ).height(); + + if ( self._options.swipe ) { + $.each( self._controls.slides, function(i, img) { + self._scaleImage( img ); + $( img.container ).css('left', self._stageWidth * i); + }); + self.$('images').css('width', self._stageWidth * self.getDataLength()); + } else { + // scale the active image + self._scaleImage(); + } + + if ( self._options.carousel ) { + self.updateCarousel(); + } + + self._controls.frames[ self._controls.active ].scale({ + width: self._stageWidth, + height: self._stageHeight, + iframelimit: self._options.maxVideoSize + }); + + self.trigger( Galleria.RESCALE ); + + if ( typeof complete === 'function' ) { + complete.call( self ); + } + }; + + scale.call( self ); + + return this; + }, + + /** + Refreshes the gallery. + Useful if you change image options at runtime and want to apply the changes to the active image. + + @returns Instance + */ + + refreshImage : function() { + this._scaleImage(); + if ( this._options.imagePan ) { + this.addPan(); + } + return this; + }, + + /** + Shows an image by index + + @param {number|boolean} index The index to show + @param {Boolean} rewind A boolean that should be true if you want the transition to go back + + @returns Instance + */ + + show : function( index, rewind, _history ) { + + var swipe = this._options.swipe; + + // do nothing queue is long || index is false || queue is false and transition is in progress + if ( !swipe && + ( this._queue.length > 3 || index === false || ( !this._options.queue && this._queue.stalled ) ) ) { + return; + } + + index = Math.max( 0, Math.min( parseInt( index, 10 ), this.getDataLength() - 1 ) ); + + rewind = typeof rewind !== 'undefined' ? !!rewind : index < this.getIndex(); + + _history = _history || false; + + // do the history thing and return + if ( !_history && Galleria.History ) { + Galleria.History.set( index.toString() ); + return; + } + + if ( this.finger && index !== this._active ) { + this.finger.to = -( index*this.finger.width ); + this.finger.index = index; + } + + this._active = index; + + // we do things a bit simpler in swipe: + if ( swipe ) { + + var data = this.getData(index), + self = this; + if ( !data ) { + return; + } + + var src = this.isFullscreen() && data.big ? data.big : ( data.image || data.iframe ), + image = this._controls.slides[index], + cached = image.isCached( src ), + thumb = this._thumbnails[ index ]; + + var evObj = { + cached: cached, + index: index, + rewind: rewind, + imageTarget: image.image, + thumbTarget: thumb.image, + galleriaData: data + }; + + this.trigger($.extend(evObj, { + type: Galleria.LOADSTART + })); + + $( self._thumbnails[ index ].container ) + .addClass( 'active' ) + .siblings( '.active' ) + .removeClass( 'active' ); + + self.$('container').removeClass( 'videoplay' ); + + var complete = function() { + + self._layers[index].innerHTML = self.getData().layer || ''; + + self.trigger($.extend(evObj, { + type: Galleria.LOADFINISH + })); + self._playCheck(); + }; + + window.setTimeout(function() { + + // load if not ready + if ( !image.ready ) { + if ( data.iframe && !data.image ) { + image.isIframe = true; + } + image.load(src, function(image) { + self._scaleImage(image, complete).trigger($.extend(evObj, { + type: Galleria.IMAGE + })); + complete(); + }); + } else { + self.trigger($.extend(evObj, { + type: Galleria.IMAGE + })); + complete(); + } + },800); + + } else { + protoArray.push.call( this._queue, { + index : index, + rewind : rewind + }); + if ( !this._queue.stalled ) { + this._show(); + } + } + + return this; + }, + + // the internal _show method does the actual showing + _show : function() { + + // shortcuts + var self = this, + queue = this._queue[ 0 ], + data = this.getData( queue.index ); + + if ( !data ) { + return; + } + + var src = this.isFullscreen() && data.big ? data.big : ( data.image || data.iframe ), + active = this._controls.getActive(), + next = this._controls.getNext(), + cached = next.isCached( src ), + thumb = this._thumbnails[ queue.index ], + mousetrigger = function() { + $( next.image ).trigger( 'mouseup' ); + }; + + self.$('container').toggleClass('iframe', !!data.isIframe).removeClass( 'videoplay' ); + + // to be fired when loading & transition is complete: + var complete = (function( data, next, active, queue, thumb ) { + + return function() { + + var win; + + _transitions.active = false; + + // optimize quality + Utils.toggleQuality( next.image, self._options.imageQuality ); + + // remove old layer + self._layers[ self._controls.active ].innerHTML = ''; + + // swap + $( active.container ).css({ + zIndex: 0, + opacity: 0 + }).show(); + + $( active.container ).find( 'iframe, .galleria-videoicon' ).remove(); + $( self._controls.frames[ self._controls.active ].container ).hide(); + + $( next.container ).css({ + zIndex: 1, + left: 0, + top: 0 + }).show(); + + self._controls.swap(); + + // add pan according to option + if ( self._options.imagePan ) { + self.addPan( next.image ); + } + + // make the image clickable + // order of precedence: iframe, link, lightbox, clicknext + if ( ( data.iframe && data.image ) || data.link || self._options.lightbox || self._options.clicknext ) { + + $( next.image ).css({ + cursor: 'pointer' + }).bind( 'mouseup', function( e ) { + + // non-left click + if ( typeof e.which == 'number' && e.which > 1 ) { + return; + } + + // iframe / video + if ( data.iframe ) { + + if ( self.isPlaying() ) { + self.pause(); + } + var frame = self._controls.frames[ self._controls.active ], + w = self._stageWidth, + h = self._stageHeight; + + $( frame.container ).css({ + width: w, + height: h, + opacity: 0 + }).show().animate({ + opacity: 1 + }, 200); + + window.setTimeout(function() { + frame.load( data.iframe + ( data.video ? '&autoplay=1' : '' ), { + width: w, + height: h + }, function( frame ) { + self.$( 'container' ).addClass( 'videoplay' ); + frame.scale({ + width: self._stageWidth, + height: self._stageHeight, + iframelimit: data.video ? self._options.maxVideoSize : undef + }); + }); + }, 100); + + return; + } + + // clicknext + if ( self._options.clicknext && !Galleria.TOUCH ) { + if ( self._options.pauseOnInteraction ) { + self.pause(); + } + self.next(); + return; + } + + // popup link + if ( data.link ) { + if ( self._options.popupLinks ) { + win = window.open( data.link, '_blank' ); + } else { + window.location.href = data.link; + } + return; + } + + if ( self._options.lightbox ) { + self.openLightbox(); + } + + }); + } + + // check if we are playing + self._playCheck(); + + // trigger IMAGE event + self.trigger({ + type: Galleria.IMAGE, + index: queue.index, + imageTarget: next.image, + thumbTarget: thumb.image, + galleriaData: data + }); + + // remove the queued image + protoArray.shift.call( self._queue ); + + // remove stalled + self._queue.stalled = false; + + // if we still have images in the queue, show it + if ( self._queue.length ) { + self._show(); + } + + }; + }( data, next, active, queue, thumb )); + + // let the carousel follow + if ( this._options.carousel && this._options.carouselFollow ) { + this._carousel.follow( queue.index ); + } + + // preload images + if ( this._options.preload ) { + + var p, i, + n = this.getNext(), + ndata; + + try { + for ( i = this._options.preload; i > 0; i-- ) { + p = new Galleria.Picture(); + ndata = self.getData( n ); + p.preload( this.isFullscreen() && ndata.big ? ndata.big : ndata.image ); + n = self.getNext( n ); + } + } catch(e) {} + } + + // show the next image, just in case + Utils.show( next.container ); + + next.isIframe = data.iframe && !data.image; + + // add active classes + $( self._thumbnails[ queue.index ].container ) + .addClass( 'active' ) + .siblings( '.active' ) + .removeClass( 'active' ); + + // trigger the LOADSTART event + self.trigger( { + type: Galleria.LOADSTART, + cached: cached, + index: queue.index, + rewind: queue.rewind, + imageTarget: next.image, + thumbTarget: thumb.image, + galleriaData: data + }); + + // stall the queue + self._queue.stalled = true; + + // begin loading the next image + next.load( src, function( next ) { + + // add layer HTML + var layer = $( self._layers[ 1-self._controls.active ] ).html( data.layer || '' ).hide(); + + self._scaleImage( next, { + + complete: function( next ) { + + // toggle low quality for IE + if ( 'image' in active ) { + Utils.toggleQuality( active.image, false ); + } + Utils.toggleQuality( next.image, false ); + + // remove the image panning, if applied + // TODO: rethink if this is necessary + self.removePan(); + + // set the captions and counter + self.setInfo( queue.index ); + self.setCounter( queue.index ); + + // show the layer now + if ( data.layer ) { + layer.show(); + // inherit click events set on image + if ( ( data.iframe && data.image ) || data.link || self._options.lightbox || self._options.clicknext ) { + layer.css( 'cursor', 'pointer' ).unbind( 'mouseup' ).mouseup( mousetrigger ); + } + } + + // add play icon + if( data.video && data.image ) { + _playIcon( next.container ); + } + + var transition = self._options.transition; + + // can JavaScript loop through objects in order? yes. + $.each({ + initial: active.image === null, + touch: Galleria.TOUCH, + fullscreen: self.isFullscreen() + }, function( type, arg ) { + if ( arg && self._options[ type + 'Transition' ] !== undef ) { + transition = self._options[ type + 'Transition' ]; + return false; + } + }); + + // validate the transition + if ( transition in _transitions.effects === false ) { + complete(); + } else { + var params = { + prev: active.container, + next: next.container, + rewind: queue.rewind, + speed: self._options.transitionSpeed || 400 + }; + + _transitions.active = true; + + // call the transition function and send some stuff + _transitions.init.call( self, transition, params, complete ); + + } + + // trigger the LOADFINISH event + self.trigger({ + type: Galleria.LOADFINISH, + cached: cached, + index: queue.index, + rewind: queue.rewind, + imageTarget: next.image, + thumbTarget: self._thumbnails[ queue.index ].image, + galleriaData: self.getData( queue.index ) + }); + } + }); + }); + }, + + /** + Gets the next index + + @param {number} [base] Optional starting point + + @returns {number} the next index, or the first if you are at the first (looping) + */ + + getNext : function( base ) { + base = typeof base === 'number' ? base : this.getIndex(); + return base === this.getDataLength() - 1 ? 0 : base + 1; + }, + + /** + Gets the previous index + + @param {number} [base] Optional starting point + + @returns {number} the previous index, or the last if you are at the first (looping) + */ + + getPrev : function( base ) { + base = typeof base === 'number' ? base : this.getIndex(); + return base === 0 ? this.getDataLength() - 1 : base - 1; + }, + + /** + Shows the next image in line + + @returns Instance + */ + + next : function() { + if ( this.getDataLength() > 1 ) { + this.show( this.getNext(), false ); + } + return this; + }, + + /** + Shows the previous image in line + + @returns Instance + */ + + prev : function() { + if ( this.getDataLength() > 1 ) { + this.show( this.getPrev(), true ); + } + return this; + }, + + /** + Retrieve a DOM element by element ID + + @param {string} elemId The delement ID to fetch + + @returns {HTMLElement} The elements DOM node or null if not found. + */ + + get : function( elemId ) { + return elemId in this._dom ? this._dom[ elemId ] : null; + }, + + /** + Retrieve a data object + + @param {number} index The data index to retrieve. + If no index specified it will take the currently active image + + @returns {Object} The data object + */ + + getData : function( index ) { + return index in this._data ? + this._data[ index ] : this._data[ this._active ]; + }, + + /** + Retrieve the number of data items + + @returns {number} The data length + */ + getDataLength : function() { + return this._data.length; + }, + + /** + Retrieve the currently active index + + @returns {number|boolean} The active index or false if none found + */ + + getIndex : function() { + return typeof this._active === 'number' ? this._active : false; + }, + + /** + Retrieve the stage height + + @returns {number} The stage height + */ + + getStageHeight : function() { + return this._stageHeight; + }, + + /** + Retrieve the stage width + + @returns {number} The stage width + */ + + getStageWidth : function() { + return this._stageWidth; + }, + + /** + Retrieve the option + + @param {string} key The option key to retrieve. If no key specified it will return all options in an object. + + @returns option or options + */ + + getOptions : function( key ) { + return typeof key === 'undefined' ? this._options : this._options[ key ]; + }, + + /** + Set options to the instance. + You can set options using a key & value argument or a single object argument (see examples) + + @param {string} key The option key + @param {string} value the the options value + + @example setOptions( 'autoplay', true ) + @example setOptions({ autoplay: true }); + + @returns Instance + */ + + setOptions : function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._options, key ); + } else { + this._options[ key ] = value; + } + return this; + }, + + /** + Starts playing the slideshow + + @param {number} delay Sets the slideshow interval in milliseconds. + If you set it once, you can just call play() and get the same interval the next time. + + @returns Instance + */ + + play : function( delay ) { + + this._playing = true; + + this._playtime = delay || this._playtime; + + this._playCheck(); + + this.trigger( Galleria.PLAY ); + + return this; + }, + + /** + Stops the slideshow if currently playing + + @returns Instance + */ + + pause : function() { + + this._playing = false; + + this.trigger( Galleria.PAUSE ); + + return this; + }, + + /** + Toggle between play and pause events. + + @param {number} delay Sets the slideshow interval in milliseconds. + + @returns Instance + */ + + playToggle : function( delay ) { + return ( this._playing ) ? this.pause() : this.play( delay ); + }, + + /** + Checks if the gallery is currently playing + + @returns {Boolean} + */ + + isPlaying : function() { + return this._playing; + }, + + /** + Checks if the gallery is currently in fullscreen mode + + @returns {Boolean} + */ + + isFullscreen : function() { + return this._fullscreen.active; + }, + + _playCheck : function() { + var self = this, + played = 0, + interval = 20, + now = Utils.timestamp(), + timer_id = 'play' + this._id; + + if ( this._playing ) { + + this.clearTimer( timer_id ); + + var fn = function() { + + played = Utils.timestamp() - now; + if ( played >= self._playtime && self._playing ) { + self.clearTimer( timer_id ); + self.next(); + return; + } + if ( self._playing ) { + + // trigger the PROGRESS event + self.trigger({ + type: Galleria.PROGRESS, + percent: Math.ceil( played / self._playtime * 100 ), + seconds: Math.floor( played / 1000 ), + milliseconds: played + }); + + self.addTimer( timer_id, fn, interval ); + } + }; + self.addTimer( timer_id, fn, interval ); + } + }, + + /** + Modify the slideshow delay + + @param {number} delay the number of milliseconds between slides, + + @returns Instance + */ + + setPlaytime: function( delay ) { + this._playtime = delay; + return this; + }, + + setIndex: function( val ) { + this._active = val; + return this; + }, + + /** + Manually modify the counter + + @param {number} [index] Optional data index to fectch, + if no index found it assumes the currently active index + + @returns Instance + */ + + setCounter: function( index ) { + + if ( typeof index === 'number' ) { + index++; + } else if ( typeof index === 'undefined' ) { + index = this.getIndex()+1; + } + + this.get( 'current' ).innerHTML = index; + + if ( IE ) { // weird IE bug + + var count = this.$( 'counter' ), + opacity = count.css( 'opacity' ); + + if ( parseInt( opacity, 10 ) === 1) { + Utils.removeAlpha( count[0] ); + } else { + this.$( 'counter' ).css( 'opacity', opacity ); + } + + } + + return this; + }, + + /** + Manually set captions + + @param {number} [index] Optional data index to fectch and apply as caption, + if no index found it assumes the currently active index + + @returns Instance + */ + + setInfo : function( index ) { + + var self = this, + data = this.getData( index ); + + $.each( ['title','description'], function( i, type ) { + + var elem = self.$( 'info-' + type ); + + if ( !!data[type] ) { + elem[ data[ type ].length ? 'show' : 'hide' ]().html( data[ type ] ); + } else { + elem.empty().hide(); + } + }); + + return this; + }, + + /** + Checks if the data contains any captions + + @param {number} [index] Optional data index to fectch, + if no index found it assumes the currently active index. + + @returns {boolean} + */ + + hasInfo : function( index ) { + + var check = 'title description'.split(' '), + i; + + for ( i = 0; check[i]; i++ ) { + if ( !!this.getData( index )[ check[i] ] ) { + return true; + } + } + return false; + + }, + + jQuery : function( str ) { + + var self = this, + ret = []; + + $.each( str.split(','), function( i, elemId ) { + elemId = $.trim( elemId ); + + if ( self.get( elemId ) ) { + ret.push( elemId ); + } + }); + + var jQ = $( self.get( ret.shift() ) ); + + $.each( ret, function( i, elemId ) { + jQ = jQ.add( self.get( elemId ) ); + }); + + return jQ; + + }, + + /** + Converts element IDs into a jQuery collection + You can call for multiple IDs separated with commas. + + @param {string} str One or more element IDs (comma-separated) + + @returns jQuery + + @example this.$('info,container').hide(); + */ + + $ : function( str ) { + return this.jQuery.apply( this, Utils.array( arguments ) ); + } + +}; + +// End of Galleria prototype + +// Add events as static variables +$.each( _events, function( i, ev ) { + + // legacy events + var type = /_/.test( ev ) ? ev.replace( /_/g, '' ) : ev; + + Galleria[ ev.toUpperCase() ] = 'galleria.'+type; + +} ); + +$.extend( Galleria, { + + // Browser helpers + IE9: IE === 9, + IE8: IE === 8, + IE7: IE === 7, + IE6: IE === 6, + IE: IE, + WEBKIT: /webkit/.test( NAV ), + CHROME: /chrome/.test( NAV ), + SAFARI: /safari/.test( NAV ) && !(/chrome/.test( NAV )), + QUIRK: ( IE && doc.compatMode && doc.compatMode === "BackCompat" ), + MAC: /mac/.test( navigator.platform.toLowerCase() ), + OPERA: !!window.opera, + IPHONE: /iphone/.test( NAV ), + IPAD: /ipad/.test( NAV ), + ANDROID: /android/.test( NAV ), + TOUCH: ('ontouchstart' in doc) + +}); + +// Galleria static methods + +/** + Adds a theme that you can use for your Gallery + + @param {Object} theme Object that should contain all your theme settings. +
    +
  • name - name of the theme
  • +
  • author - name of the author
  • +
  • css - css file name (not path)
  • +
  • defaults - default options to apply, including theme-specific options
  • +
  • init - the init function
  • +
+ + @returns {Object} theme +*/ + +Galleria.addTheme = function( theme ) { + + // make sure we have a name + if ( !theme.name ) { + Galleria.raise('No theme name specified'); + } + + if ( typeof theme.defaults !== 'object' ) { + theme.defaults = {}; + } else { + theme.defaults = _legacyOptions( theme.defaults ); + } + + var css = false, + reg; + + if ( typeof theme.css === 'string' ) { + + // look for manually added CSS + $('link').each(function( i, link ) { + reg = new RegExp( theme.css ); + if ( reg.test( link.href ) ) { + + // we found the css + css = true; + + // the themeload trigger + _themeLoad( theme ); + + return false; + } + }); + + // else look for the absolute path and load the CSS dynamic + if ( !css ) { + + $(function() { + + $('script').each(function( i, script ) { + // look for the theme script + reg = new RegExp( 'galleria\\.' + theme.name.toLowerCase() + '\\.' ); + if( reg.test( script.src )) { + + // we have a match + css = script.src.replace(/[^\/]*$/, '') + theme.css; + + window.setTimeout(function() { + Utils.loadCSS( css, 'galleria-theme', function() { + + // the themeload trigger + _themeLoad( theme ); + + }); + }, 1); + } + }); + if ( !css ) { + Galleria.raise('No theme CSS loaded'); + } + }); + } + + } else { + + // pass + _themeLoad( theme ); + } + return theme; +}; + +/** + loadTheme loads a theme js file and attaches a load event to Galleria + + @param {string} src The relative path to the theme source file + + @param {Object} [options] Optional options you want to apply + + @returns Galleria +*/ + +Galleria.loadTheme = function( src, options ) { + + // Don't load if theme is already loaded + if( $('script').filter(function() { return $(this).attr('src') == src; }).length ) { + return; + } + + var loaded = false, + err; + + // start listening for the timeout onload + $( window ).load( function() { + if ( !loaded ) { + // give it another 20 seconds + err = window.setTimeout(function() { + if ( !loaded && !Galleria.theme ) { + Galleria.raise( "Galleria had problems loading theme at " + src + ". Please check theme path or load manually.", true ); + } + }, 20000); + } + }); + + // first clear the current theme, if exists + Galleria.unloadTheme(); + + // load the theme + Utils.loadScript( src, function() { + loaded = true; + window.clearTimeout( err ); + }); + + return Galleria; +}; + +/** + unloadTheme unloads the Galleria theme and prepares for a new theme + + @returns Galleria +*/ + +Galleria.unloadTheme = function() { + + if ( typeof Galleria.theme == 'object' ) { + + $('script').each(function( i, script ) { + + if( new RegExp( 'galleria\\.' + Galleria.theme.name + '\\.' ).test( script.src ) ) { + $( script ).remove(); + } + }); + + Galleria.theme = undef; + } + + return Galleria; +}; + +/** + Retrieves a Galleria instance. + + @param {number} [index] Optional index to retrieve. + If no index is supplied, the method will return all instances in an array. + + @returns Instance or Array of instances +*/ + +Galleria.get = function( index ) { + if ( !!_instances[ index ] ) { + return _instances[ index ]; + } else if ( typeof index !== 'number' ) { + return _instances; + } else { + Galleria.raise('Gallery index ' + index + ' not found'); + } +}; + +/** + + Configure Galleria options via a static function. + The options will be applied to all instances + + @param {string|object} key The options to apply or a key + + @param [value] If key is a string, this is the value + + @returns Galleria + +*/ + +Galleria.configure = function( key, value ) { + + var opts = {}; + + if( typeof key == 'string' && value ) { + opts[key] = value; + key = opts; + } else { + $.extend( opts, key ); + } + + Galleria.configure.options = opts; + + $.each( Galleria.get(), function(i, instance) { + instance.setOptions( opts ); + }); + + return Galleria; +}; + +Galleria.configure.options = {}; + +/** + + Bind a Galleria event to the gallery + + @param {string} type A string representing the galleria event + + @param {function} callback The function that should run when the event is triggered + + @returns Galleria + +*/ + +Galleria.on = function( type, callback ) { + if ( !type ) { + return; + } + + callback = callback || F; + + // hash the bind + var hash = type + callback.toString().replace(/\s/g,'') + Utils.timestamp(); + + // for existing instances + $.each( Galleria.get(), function(i, instance) { + instance._binds.push( hash ); + instance.bind( type, callback ); + }); + + // for future instances + Galleria.on.binds.push({ + type: type, + callback: callback, + hash: hash + }); + + return Galleria; +}; + +Galleria.on.binds = []; + +/** + + Run Galleria + Alias for $(selector).galleria(options) + + @param {string} selector A selector of element(s) to intialize galleria to + + @param {object} options The options to apply + + @returns Galleria + +*/ + +Galleria.run = function( selector, options ) { + if ( $.isFunction( options ) ) { + options = { extend: options }; + } + $( selector || '#galleria' ).galleria( options ); + return Galleria; +}; + +/** + Creates a transition to be used in your gallery + + @param {string} name The name of the transition that you will use as an option + + @param {Function} fn The function to be executed in the transition. + The function contains two arguments, params and complete. + Use the params Object to integrate the transition, and then call complete when you are done. + + @returns Galleria + +*/ + +Galleria.addTransition = function( name, fn ) { + _transitions.effects[name] = fn; + return Galleria; +}; + +/** + The Galleria utilites +*/ + +Galleria.utils = Utils; + +/** + A helper metod for cross-browser logging. + It uses the console log if available otherwise it falls back to alert + + @example Galleria.log("hello", document.body, [1,2,3]); +*/ + +Galleria.log = function() { + var args = Utils.array( arguments ); + if( 'console' in window && 'log' in window.console ) { + try { + return window.console.log.apply( window.console, args ); + } catch( e ) { + $.each( args, function() { + window.console.log(this); + }); + } + } else { + return window.alert( args.join('
') ); + } +}; + +/** + A ready method for adding callbacks when a gallery is ready + Each method is call before the extend option for every instance + + @param {function} callback The function to call + + @returns Galleria +*/ + +Galleria.ready = function( fn ) { + if ( typeof fn != 'function' ) { + return Galleria; + } + $.each( _galleries, function( i, gallery ) { + fn.call( gallery, gallery._options ); + }); + Galleria.ready.callbacks.push( fn ); + return Galleria; +}; + +Galleria.ready.callbacks = []; + +/** + Method for raising errors + + @param {string} msg The message to throw + + @param {boolean} [fatal] Set this to true to override debug settings and display a fatal error +*/ + +Galleria.raise = function( msg, fatal ) { + + var type = fatal ? 'Fatal error' : 'Error', + + css = { + color: '#fff', + position: 'absolute', + top: 0, + left: 0, + zIndex: 100000 + }, + + echo = function( msg ) { + + var html = '
' + + ( fatal ? '' + type + ': ' : '' ) + + msg + '
'; + + $.each( _instances, function() { + + var cont = this.$( 'errors' ), + target = this.$( 'target' ); + + if ( !cont.length ) { + + target.css( 'position', 'relative' ); + + cont = this.addElement( 'errors' ).appendChild( 'target', 'errors' ).$( 'errors' ).css(css); + } + cont.append( html ); + + }); + + if ( !_instances.length ) { + $('
').css( $.extend( css, { position: 'fixed' } ) ).append( html ).appendTo( DOM().body ); + } + }; + + // if debug is on, display errors and throw exception if fatal + if ( DEBUG ) { + echo( msg ); + if ( fatal ) { + throw new Error(type + ': ' + msg); + } + + // else just echo a silent generic error if fatal + } else if ( fatal ) { + if ( _hasError ) { + return; + } + _hasError = true; + fatal = false; + echo( 'Gallery could not load.' ); + } +}; + +// Add the version +Galleria.version = VERSION; + +/** + A method for checking what version of Galleria the user has installed and throws a readable error if the user needs to upgrade. + Useful when building plugins that requires a certain version to function. + + @param {number} version The minimum version required + + @param {string} [msg] Optional message to display. If not specified, Galleria will throw a generic error. + + @returns Galleria +*/ + +Galleria.requires = function( version, msg ) { + msg = msg || 'You need to upgrade Galleria to version ' + version + ' to use one or more components.'; + if ( Galleria.version < version ) { + Galleria.raise(msg, true); + } + return Galleria; +}; + +/** + Adds preload, cache, scale and crop functionality + + @constructor + + @requires jQuery + + @param {number} [id] Optional id to keep track of instances +*/ + +Galleria.Picture = function( id ) { + + // save the id + this.id = id || null; + + // the image should be null until loaded + this.image = null; + + // Create a new container + this.container = Utils.create('galleria-image'); + + // add container styles + $( this.container ).css({ + overflow: 'hidden', + position: 'relative' // for IE Standards mode + }); + + // saves the original measurements + this.original = { + width: 0, + height: 0 + }; + + // flag when the image is ready + this.ready = false; + + // flag for iframe Picture + this.isIframe = false; + +}; + +Galleria.Picture.prototype = { + + // the inherited cache object + cache: {}, + + // show the image on stage + show: function() { + Utils.show( this.image ); + }, + + // hide the image + hide: function() { + Utils.moveOut( this.image ); + }, + + clear: function() { + this.image = null; + }, + + /** + Checks if an image is in cache + + @param {string} src The image source path, ex '/path/to/img.jpg' + + @returns {boolean} + */ + + isCached: function( src ) { + return !!this.cache[src]; + }, + + /** + Preloads an image into the cache + + @param {string} src The image source path, ex '/path/to/img.jpg' + + @returns Galleria.Picture + */ + + preload: function( src ) { + $( new Image() ).load((function(src, cache) { + return function() { + cache[ src ] = src; + }; + }( src, this.cache ))).attr( 'src', src ); + }, + + /** + Loads an image and call the callback when ready. + Will also add the image to cache. + + @param {string} src The image source path, ex '/path/to/img.jpg' + @param {Object} [size] The forced size of the image, defined as an object { width: xx, height:xx } + @param {Function} callback The function to be executed when the image is loaded & scaled + + @returns The image container (jQuery object) + */ + + load: function(src, size, callback) { + + if ( typeof size == 'function' ) { + callback = size; + size = null; + } + + if( this.isIframe ) { + var id = 'if'+new Date().getTime(); + + var iframe = this.image = $('