slideoff 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +218 -0
  5. data/bin/slideoff +127 -0
  6. data/lib/slideoff/config_builder.rb +60 -0
  7. data/lib/slideoff/flickr_api.rb +50 -0
  8. data/lib/slideoff/flickr_image.rb +92 -0
  9. data/lib/slideoff/markdown.rb +145 -0
  10. data/lib/slideoff/presentation.rb +90 -0
  11. data/lib/slideoff/remote_api.rb +53 -0
  12. data/lib/slideoff/routes.rb +20 -0
  13. data/lib/slideoff/server.rb +50 -0
  14. data/lib/slideoff/slides_api.rb +59 -0
  15. data/lib/slideoff/utils.rb +453 -0
  16. data/lib/slideoff.rb +15 -0
  17. data/themes/3d_slideshow/README +2 -0
  18. data/themes/3d_slideshow/css/main.css +232 -0
  19. data/themes/3d_slideshow/css/reset.css +57 -0
  20. data/themes/3d_slideshow/index.erb +31 -0
  21. data/themes/3d_slideshow/js/slideshow.js +288 -0
  22. data/themes/CSSS/README +2 -0
  23. data/themes/CSSS/css/slideshow.css +304 -0
  24. data/themes/CSSS/css/theme.css +250 -0
  25. data/themes/CSSS/images/rainbow-wood.jpg +0 -0
  26. data/themes/CSSS/index.erb +36 -0
  27. data/themes/CSSS/js/classList.js +116 -0
  28. data/themes/CSSS/js/prefixfree.min.js +5 -0
  29. data/themes/CSSS/js/slideshow.js +621 -0
  30. data/themes/common/css/pygments/colorful.css +62 -0
  31. data/themes/common/css/pygments/manni.css +61 -0
  32. data/themes/common/css/pygments/native.css +70 -0
  33. data/themes/common/css/pygments/solarized.css +66 -0
  34. data/themes/common/fonts/DroidSansMono.svg +626 -0
  35. data/themes/common/fonts/DroidSansMono.ttf +0 -0
  36. data/themes/common/fonts/Lato-BoldItalic.woff +0 -0
  37. data/themes/common/fonts/Lato-Italic.woff +0 -0
  38. data/themes/common/fonts/Lato-bold.woff +0 -0
  39. data/themes/common/fonts/Lato.woff +0 -0
  40. data/themes/common/fonts/OpenSans-Bold.woff +0 -0
  41. data/themes/common/fonts/OpenSans-BoldItalic.woff +0 -0
  42. data/themes/common/fonts/OpenSans-Italic.woff +0 -0
  43. data/themes/common/fonts/OpenSans.woff +0 -0
  44. data/themes/common/fonts/PTMono.woff +0 -0
  45. data/themes/common/fonts/PTSans.Bold.Italic.woff +0 -0
  46. data/themes/common/fonts/PTSans.Bold.woff +0 -0
  47. data/themes/common/fonts/PTSans.Italic.woff +0 -0
  48. data/themes/common/fonts/PTSans.Narrow.Bold.woff +0 -0
  49. data/themes/common/fonts/PTSans.Narrow.woff +0 -0
  50. data/themes/common/fonts/PTSans.woff +0 -0
  51. data/themes/common/fonts/TargetBlank.otf +0 -0
  52. data/themes/common/fonts/TargetBlank.svg +14 -0
  53. data/themes/common/fonts/YanoneKaffeesatz-Bold.woff +0 -0
  54. data/themes/common/fonts/YanoneKaffeesatz-Regular.woff +0 -0
  55. data/themes/common/fonts/crimson_text.ttf +0 -0
  56. data/themes/common/fonts/crimson_text_bold.ttf +0 -0
  57. data/themes/common/fonts/crimson_text_semibold.ttf +0 -0
  58. data/themes/common/fonts/league_gothic-webfont.ttf +0 -0
  59. data/themes/html5rocks/README +2 -0
  60. data/themes/html5rocks/css/default.css +501 -0
  61. data/themes/html5rocks/css/moon.css +543 -0
  62. data/themes/html5rocks/css/sand.css +508 -0
  63. data/themes/html5rocks/css/sea_wave.css +492 -0
  64. data/themes/html5rocks/index.erb +64 -0
  65. data/themes/html5rocks/js/slides.js +464 -0
  66. data/themes/io2012/README +2 -0
  67. data/themes/io2012/css/default.css +1481 -0
  68. data/themes/io2012/css/fonts.css +24 -0
  69. data/themes/io2012/css/phone.css +27 -0
  70. data/themes/io2012/images/google_developers_icon_128.png +0 -0
  71. data/themes/io2012/images/io2012_logo.png +0 -0
  72. data/themes/io2012/index.erb +73 -0
  73. data/themes/io2012/js/hammer.js +586 -0
  74. data/themes/io2012/js/modernizr.custom.45394.js +4 -0
  75. data/themes/io2012/js/order.js +8 -0
  76. data/themes/io2012/js/polyfills/classList.min.js +2 -0
  77. data/themes/io2012/js/polyfills/dataset.min.js +2 -0
  78. data/themes/io2012/js/polyfills/history.min.js +1 -0
  79. data/themes/io2012/js/prettify/lang-apollo.js +2 -0
  80. data/themes/io2012/js/prettify/lang-clj.js +18 -0
  81. data/themes/io2012/js/prettify/lang-css.js +2 -0
  82. data/themes/io2012/js/prettify/lang-go.js +1 -0
  83. data/themes/io2012/js/prettify/lang-hs.js +2 -0
  84. data/themes/io2012/js/prettify/lang-lisp.js +3 -0
  85. data/themes/io2012/js/prettify/lang-lua.js +2 -0
  86. data/themes/io2012/js/prettify/lang-ml.js +2 -0
  87. data/themes/io2012/js/prettify/lang-n.js +4 -0
  88. data/themes/io2012/js/prettify/lang-proto.js +1 -0
  89. data/themes/io2012/js/prettify/lang-scala.js +2 -0
  90. data/themes/io2012/js/prettify/lang-sql.js +2 -0
  91. data/themes/io2012/js/prettify/lang-tex.js +1 -0
  92. data/themes/io2012/js/prettify/lang-vb.js +2 -0
  93. data/themes/io2012/js/prettify/lang-vhdl.js +3 -0
  94. data/themes/io2012/js/prettify/lang-wiki.js +2 -0
  95. data/themes/io2012/js/prettify/lang-xq.js +3 -0
  96. data/themes/io2012/js/prettify/lang-yaml.js +2 -0
  97. data/themes/io2012/js/prettify/prettify.css +1 -0
  98. data/themes/io2012/js/prettify/prettify.js +28 -0
  99. data/themes/io2012/js/require-1.0.8.min.js +33 -0
  100. data/themes/io2012/js/slide-controller.js +109 -0
  101. data/themes/io2012/js/slide-deck.js +768 -0
  102. data/themes/io2012/js/slides.js +5 -0
  103. data/themes/memories/README +5 -0
  104. data/themes/memories/css/fonts.css +25 -0
  105. data/themes/memories/css/slideshow.css +286 -0
  106. data/themes/memories/css/theme.css +183 -0
  107. data/themes/memories/index.erb +37 -0
  108. data/themes/memories/js/prefixfree.min.js +13 -0
  109. data/themes/memories/js/slideshow.js +577 -0
  110. data/themes/modern/LICENSE +20 -0
  111. data/themes/modern/README.md +52 -0
  112. data/themes/modern/css/GENERATED_CONTENT +0 -0
  113. data/themes/modern/css/defaults.css +0 -0
  114. data/themes/modern/css/fonts.css +166 -0
  115. data/themes/modern/css/pygments/colorful.css +215 -0
  116. data/themes/modern/css/pygments/github.css +208 -0
  117. data/themes/modern/css/pygments/solarized-dark.css +213 -0
  118. data/themes/modern/css/pygments/solarized-light.css +213 -0
  119. data/themes/modern/css/reset.css +41 -0
  120. data/themes/modern/css/screen.css +1183 -0
  121. data/themes/modern/fonts/Collegiate.woff +0 -0
  122. data/themes/modern/fonts/GoudyBookletter.1911.woff +0 -0
  123. data/themes/modern/fonts/Inconsolata.Bold.woff +0 -0
  124. data/themes/modern/fonts/Inconsolata.woff +0 -0
  125. data/themes/modern/fonts/Junction.woff +0 -0
  126. data/themes/modern/fonts/LiberationMono.Bold.woff +0 -0
  127. data/themes/modern/fonts/LiberationMono.BoldItalic.woff +0 -0
  128. data/themes/modern/fonts/LiberationMono.Italic.woff +0 -0
  129. data/themes/modern/fonts/LiberationMono.Regular.woff +0 -0
  130. data/themes/modern/fonts/OpenSans.Bold.woff +0 -0
  131. data/themes/modern/fonts/OpenSans.BoldItalic.woff +0 -0
  132. data/themes/modern/fonts/OpenSans.ExtraBold.woff +0 -0
  133. data/themes/modern/fonts/OpenSans.ExtraBoldItalic.woff +0 -0
  134. data/themes/modern/fonts/OpenSans.Italic.woff +0 -0
  135. data/themes/modern/fonts/OpenSans.Light.woff +0 -0
  136. data/themes/modern/fonts/OpenSans.LightItalic.woff +0 -0
  137. data/themes/modern/fonts/OpenSans.Regular.woff +0 -0
  138. data/themes/modern/fonts/OpenSans.Semibold.woff +0 -0
  139. data/themes/modern/fonts/OpenSans.SemiboldItalic.woff +0 -0
  140. data/themes/modern/fonts/PTMono.woff +0 -0
  141. data/themes/modern/fonts/PTSans.Bold.Italic.woff +0 -0
  142. data/themes/modern/fonts/PTSans.Bold.woff +0 -0
  143. data/themes/modern/fonts/PTSans.Italic.woff +0 -0
  144. data/themes/modern/fonts/PTSans.Narrow.Bold.woff +0 -0
  145. data/themes/modern/fonts/PTSans.woff +0 -0
  146. data/themes/modern/fonts/Raleway.Thin.woff +0 -0
  147. data/themes/modern/fonts/STIXGeneral.Bold.woff +0 -0
  148. data/themes/modern/fonts/STIXGeneral.BoldItalic.woff +0 -0
  149. data/themes/modern/fonts/STIXGeneral.Italic.woff +0 -0
  150. data/themes/modern/fonts/STIXGeneral.Regular.woff +0 -0
  151. data/themes/modern/fonts/YanoneKaffeesatz.Bold.woff +0 -0
  152. data/themes/modern/fonts/YanoneKaffeesatz.ExtraLight.woff +0 -0
  153. data/themes/modern/fonts/YanoneKaffeesatz.Light.woff +0 -0
  154. data/themes/modern/fonts/YanoneKaffeesatz.Regular.woff +0 -0
  155. data/themes/modern/images/cc/by.svg +29 -0
  156. data/themes/modern/images/cc/cc-white.svg +23 -0
  157. data/themes/modern/images/cc/cc.svg +23 -0
  158. data/themes/modern/images/cc/nc-eu.svg +21 -0
  159. data/themes/modern/images/cc/nc-jp.svg +18 -0
  160. data/themes/modern/images/cc/nc.svg +23 -0
  161. data/themes/modern/images/cc/nd.svg +20 -0
  162. data/themes/modern/images/cc/pd.svg +24 -0
  163. data/themes/modern/images/cc/remix.svg +20 -0
  164. data/themes/modern/images/cc/sa.svg +22 -0
  165. data/themes/modern/images/cc/sampling.plus.svg +33 -0
  166. data/themes/modern/images/cc/sampling.svg +36 -0
  167. data/themes/modern/images/cc/share.svg +22 -0
  168. data/themes/modern/images/cc/zero.svg +24 -0
  169. data/themes/modern/images/flickr.svg +65 -0
  170. data/themes/modern/images/mesh.png +0 -0
  171. data/themes/modern/images/mesh@2x.png +0 -0
  172. data/themes/modern/images/progress-grayDark.svg +55 -0
  173. data/themes/modern/images/progress.svg +3 -0
  174. data/themes/modern/index.erb +76 -0
  175. data/themes/modern/js/highcharts.js +294 -0
  176. data/themes/modern/js/jquery-2.1.0.min.js +4 -0
  177. data/themes/modern/js/script.coffee +480 -0
  178. data/themes/modern/js/script.js +678 -0
  179. data/themes/modern/styles/_box.scss +89 -0
  180. data/themes/modern/styles/_figure.scss +81 -0
  181. data/themes/modern/styles/_full.scss +74 -0
  182. data/themes/modern/styles/_increment.scss +38 -0
  183. data/themes/modern/styles/_list.scss +129 -0
  184. data/themes/modern/styles/_progress.scss +19 -0
  185. data/themes/modern/styles/defaults.scss +46 -0
  186. data/themes/modern/styles/fonts.scss +197 -0
  187. data/themes/modern/styles/pygments/_solarized.scss +82 -0
  188. data/themes/modern/styles/pygments/colorful.scss +67 -0
  189. data/themes/modern/styles/pygments/github.scss +67 -0
  190. data/themes/modern/styles/pygments/solarized-dark.scss +15 -0
  191. data/themes/modern/styles/pygments/solarized-light.scss +15 -0
  192. data/themes/modern/styles/reset.scss +46 -0
  193. data/themes/modern/styles/screen.scss +841 -0
  194. data/themes/reveal/README +2 -0
  195. data/themes/reveal/css/main.css +1029 -0
  196. data/themes/reveal/css/reset.css +57 -0
  197. data/themes/reveal/index.erb +102 -0
  198. data/themes/reveal/js/classList.js +2 -0
  199. data/themes/reveal/js/head.min.js +8 -0
  200. data/themes/reveal/js/reveal.js +951 -0
  201. data/themes/shower/README +2 -0
  202. data/themes/shower/css/fonts.css +50 -0
  203. data/themes/shower/css/reset.css +42 -0
  204. data/themes/shower/css/style.css +418 -0
  205. data/themes/shower/images/grid.png +0 -0
  206. data/themes/shower/images/linen.png +0 -0
  207. data/themes/shower/images/ribbon.svg +4 -0
  208. data/themes/shower/index.erb +35 -0
  209. data/themes/shower/js/script.js +325 -0
  210. data/themes/template/README +5 -0
  211. data/themes/template/css/reset.css +42 -0
  212. data/themes/template/index.erb +35 -0
  213. data/themes/template/js/prefixfree.min.js +13 -0
  214. metadata +343 -0
@@ -0,0 +1,145 @@
1
+ # Encoding: utf-8
2
+ require "pygments"
3
+ require "redcarpet"
4
+ require "cgi"
5
+
6
+ module Slideoff
7
+ class Markdown < Redcarpet::Render::HTML
8
+ PARSER_OPTIONS = {
9
+ :no_intra_emphasis => true,
10
+ :tables => true,
11
+ :fenced_code_blocks => true,
12
+ :disable_indented_code_blocks => true,
13
+ :autolink => true,
14
+ :strikethrough => true,
15
+ :superscript => true,
16
+ :quote => true,
17
+ :underline => true,
18
+ :lax_spacing => true,
19
+ :highlight => true
20
+ }
21
+
22
+ def self.render(text)
23
+ text ||= ""
24
+ markdown = Redcarpet::Markdown.new(self, PARSER_OPTIONS)
25
+ markdown.render(text)
26
+ end
27
+
28
+ def preprocess(text)
29
+ parse_highlight(text)
30
+ parse_box(text)
31
+ parse_colorizing(text)
32
+ parse_description(text)
33
+ parse_flickr_image(text)
34
+ parse_pause(text)
35
+ text
36
+ end
37
+
38
+ def parse_box(text)
39
+ text.gsub!(/(\+\+\+)(.*?)\n\1(.+?)\n\1(.+?)(\n|$)/m) do
40
+ %{<div class="box #{$2}"><div>#{$3}</div><div>#{$4}</div></div>}
41
+ end
42
+ end
43
+
44
+ def parse_highlight(text)
45
+ text.gsub!(/(==)(.*?)\1(.*?)\1/) do
46
+ %{<mark class="#{$2}">#{$3}</mark>}
47
+ end
48
+ end
49
+
50
+ def parse_colorizing(text)
51
+ text.gsub!(/(__)(.*?)\1(.*?)\1/) do
52
+ %{<span class="text-#{$2}">#{$3}</span>}
53
+ end
54
+ end
55
+
56
+ def parse_description(text)
57
+ item = /([^\n]+?)\n/
58
+ separator = /\s*:\s+/
59
+ text.gsub!(/(#{item}#{separator}#{item})+/m) do |m|
60
+ scanned = m.scan(/#{item}#{separator}#{item}/m)
61
+ html_list = scanned.map { |(word, desc)| %{<dt>#{word}</dt><dd>#{desc}</dd>} }.join
62
+ %Q{<dl>#{html_list}</dl>}
63
+ end
64
+ end
65
+
66
+ def parse_flickr_image(text)
67
+ text.gsub!(/!F\[(.+?)\](?:\[(.+?)\])?/) do
68
+ id = $1
69
+ size = $2 || :b
70
+ begin
71
+ flickr_image = FlickrImage.new(id)
72
+ src = flickr_image.image_src #(size)
73
+ author = flickr_image.author
74
+ license = flickr_image.license["name"]
75
+ license_url = flickr_image.license["url"]
76
+ cc_attributes = flickr_image.license["cc_attributes"]
77
+ alt = flickr_image.title
78
+ page = flickr_image.page
79
+ rescue Exception => e
80
+ title = "Specify Flickr API key!"
81
+ src = "http://www.placehold.it/1024x807&text=#{CGI.escape(title)}"
82
+ author = "No author"
83
+ license = "All Rights Reserved"
84
+ license_url = ""
85
+ cc_attributes = nil
86
+ alt = title
87
+ page = src
88
+ end
89
+
90
+ html = "<figure>"
91
+ html << %{<img alt="#{alt}" src="#{src}"/>}
92
+ html << "<figcaption>"
93
+ html << %{<span class="flickr"></span>}
94
+ html << %{<a href="#{page}" alt="#{author} on Flickr">#{author}</a>}
95
+ html << %{<a href="#{license_url}" alt="#{license}">}
96
+ if cc_attributes && cc_attributes.any?
97
+ html << %{<span class="license license-cc"></span>}
98
+ #cc_attributes.each do |cc|
99
+ #html << %{<span class="license license-#{cc}"></span>}
100
+ #end
101
+ end
102
+ html << %{</a>}
103
+ html << "</figcaption>"
104
+ html << "</figure>"
105
+ html
106
+ end
107
+ end
108
+
109
+ def parse_pause(text)
110
+ text.gsub!(/^!PAUSE\s*$/) { %{<p class="pause"></p>} }
111
+ end
112
+
113
+ def block_code(code, lang)
114
+ colorized = Pygments.highlight(code, :lexer => lang || "text", :options => {:nowrap => true})
115
+ code_lines = colorized.split("\n")
116
+ code_lines.map! do |line|
117
+ line = %{<span>&nbsp;</span>} if line.empty?
118
+ %{<code>#{line}</code>}
119
+ end
120
+ lang = "data-lang=\"#{lang}\"" if !lang.nil? && !lang.empty?
121
+ %{<pre #{lang}>#{code_lines.join}</pre>}
122
+ end
123
+
124
+ def codespan(code)
125
+ %{<code class="inline">#{code}</code>}
126
+ end
127
+
128
+ def table(header, body)
129
+ %{<table class="striped"><thead>#{header}</thead><tbody>#{body}</tbody></table>}
130
+ end
131
+
132
+ def strikethrough(text)
133
+ "<s>#{text}</s>"
134
+ end
135
+
136
+ def normal_text(text)
137
+ text.gsub!('« ', '«&nbsp;')
138
+ text.gsub!(/ ([:;»!?])/, '&nbsp;\1')
139
+ text.gsub!(' -- ', '—')
140
+ text.gsub!('...', '…')
141
+ text
142
+ end
143
+
144
+ end
145
+ end
@@ -0,0 +1,90 @@
1
+ require "erubis"
2
+
3
+ module Slideoff
4
+ class Presentation
5
+ Theme = Struct.new(:title, :dir, :css, :js)
6
+ Section = Struct.new(:number, :title, :dir, :slides, :show_chapter, :show_toc)
7
+
8
+ class Slide < Struct.new(:number, :classes, :html, :section_num)
9
+ def id
10
+ [section_num, number].join('-')
11
+ end
12
+
13
+ def extract_title
14
+ return @title if @title
15
+ html.sub!(/<h(\d)>(.*)<\/h\1>/) { @title = $2; "" }
16
+ @title
17
+ end
18
+
19
+ def extract_subtitle
20
+ return @subtitle if @subtitle
21
+ html.sub!(/<h(\d)>(.*)<\/h\1>/) { @subtitle = $2; "" }
22
+ @subtitle
23
+ end
24
+ end
25
+
26
+ attr_accessor :theme, :common, :parts
27
+
28
+ def initialize(dir)
29
+ @theme = build_theme(CONFIG.theme)
30
+ @common = build_theme("common")
31
+ @parts = CONFIG.sections || raise("check your presentation.json or showoff.json file")
32
+ @parts = Hash[@parts.zip @parts] if Array === @parts
33
+ end
34
+
35
+ def html
36
+ str = File.read("#{theme.dir}/index.erb")
37
+ Erubis::Eruby.new(str).result(:meta => CONFIG, :theme => theme, :sections => sections)
38
+ end
39
+
40
+ def path_for_asset(asset)
41
+ [CONFIG, theme, common].each { |dir| convert_styles(dir) }
42
+
43
+ Dir[ "#{CONFIG.dir}#{asset}"].first ||
44
+ Dir[ "#{theme.dir}#{asset}"].first ||
45
+ Dir[ "#{common.dir}#{asset}"].first ||
46
+ Dir["#{CONFIG.dir}/**#{asset}"].first
47
+ end
48
+
49
+ def convert_styles(dir)
50
+ if Dir.exist?("#{dir}/styles") && !Dir.exist?("#{dir}/css")
51
+ Dir.chdir(dir) { `sass --update styles:css` }
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ def build_theme(title)
58
+ Theme.new.tap do |t|
59
+ dir = File.expand_path("~/.slideoff/#{title}")
60
+ if File.exists?(dir)
61
+ t.dir = dir
62
+ else
63
+ t.dir = File.expand_path("../../../themes/#{title}", __FILE__)
64
+ end
65
+ t.title = title
66
+ Dir.chdir(t.dir) do
67
+ t.css = Dir["**/*.css"]
68
+ t.js = Dir["**/*.js"]
69
+ end
70
+ end
71
+ end
72
+
73
+ def sections
74
+ @parts.map.with_index do |(dir, options), section_num|
75
+ raw = Dir["#{CONFIG.dir}/#{dir}/**/*.md"].sort.map do |f|
76
+ File.read(f)
77
+ end.join("\n\n")
78
+ parts = raw.split(/!SLIDE */)
79
+ parts.delete('')
80
+ slide_num_diff = [options["show_chapter"], options["show_toc"]].count(true)
81
+ slides = parts.map.with_index do |slide, slide_num|
82
+ classes, md = slide.split("\n", 2)
83
+ html = Markdown.render(md)
84
+ Slide.new(slide_num+slide_num_diff, classes, html, section_num)
85
+ end
86
+ Section.new(section_num, options["title"], dir, slides, options["show_chapter"], options["show_toc"])
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,53 @@
1
+ require "goliath/api"
2
+
3
+
4
+ module Slideoff
5
+ class RemoteAPI < Goliath::API
6
+ def initialize(key)
7
+ # Secret token...
8
+ @key = key
9
+
10
+ # Share channel between connections
11
+ @chan = ::EM::Channel.new
12
+ end
13
+
14
+ def response(env)
15
+ path_info = Rack::Utils.unescape(env['PATH_INFO'])
16
+ slash, key, action = path_info.split('/', 3)
17
+
18
+ # Events are public
19
+ return stream_events(env) if 'events' == action
20
+
21
+ # Sending events is restricted
22
+ return forbidden unless key == @key
23
+
24
+ @chan.push(action)
25
+ [200, {
26
+ "Content-Type" => "text/plain; charset=utf-8",
27
+ "Content-Length" => Rack::Utils.bytesize(action).to_s
28
+ }, [action]]
29
+ end
30
+
31
+ def on_close(env)
32
+ return unless env['subscription']
33
+ env.logger.info "Stream connection closed."
34
+ @chan.unsubscribe(env['subscription'])
35
+ end
36
+
37
+ protected
38
+
39
+ def stream_events(env)
40
+ env['subscription'] = @chan.subscribe do |msg|
41
+ env.stream_send("data: #{msg}\n\n")
42
+ end
43
+ streaming_response(202, {"Content-Type" => "text/event-stream"})
44
+ end
45
+
46
+ def forbidden
47
+ [403, {
48
+ "Content-Type" => "text/plain",
49
+ "Content-Length" => "10"
50
+ }, ["Forbidden\n"]]
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,20 @@
1
+ require "rack/builder"
2
+
3
+
4
+ module Slideoff
5
+ class Routes
6
+
7
+ def self.run(presentation, opts = {})
8
+ Rack::Builder.new do
9
+ map '/remote' do
10
+ run Slideoff::RemoteAPI.new(opts[:remote_key])
11
+ end
12
+
13
+ map '/' do
14
+ run Slideoff::SlidesAPI.new(presentation)
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ require "goliath/server"
2
+ require "log4r"
3
+
4
+ module Slideoff
5
+ class Server
6
+ DEFAULT_PORT = Goliath::Server::DEFAULT_PORT
7
+ DEFAULT_ADDRESS = Goliath::Server::DEFAULT_ADDRESS
8
+
9
+ attr_reader :dir, :options, :remote_key, :host, :port
10
+
11
+ def initialize(options)
12
+ @dir = Dir.pwd
13
+ @options = options
14
+ @remote_key = @options.delete(:remote_key) || rand(1_000_000).to_s(16)
15
+ @host = @options.delete(:host) || DEFAULT_ADDRESS
16
+ @port = @options.delete(:port) || DEFAULT_PORT
17
+ end
18
+
19
+ def start
20
+ server = Goliath::Server.new(host, port)
21
+ server.options = options
22
+ server.app = Slideoff::Routes.run(presentation, :remote_key => remote_key)
23
+ server.logger = logger
24
+
25
+ server.start
26
+ end
27
+
28
+ private
29
+
30
+ def presentation
31
+ Slideoff::Presentation.new(dir)
32
+ end
33
+
34
+ def logger
35
+ logger = Log4r::Logger.new(self.class.name)
36
+ logger.add(stdout_outputter)
37
+ logger.level = Log4r::DEBUG
38
+ logger.info("Starting server on http://#{host}:#{port}. Rock your presentation!")
39
+ logger
40
+ end
41
+
42
+ def stdout_outputter
43
+ Log4r::StdoutOutputter.new('console', :formatter => log_formatter)
44
+ end
45
+
46
+ def log_formatter
47
+ Log4r::PatternFormatter.new(:pattern => "[%p:%l] %d %m")
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,59 @@
1
+ require "time"
2
+ require "rack/utils"
3
+ require "rack/mime"
4
+ require "goliath/api"
5
+
6
+
7
+ module Slideoff
8
+ class SlidesAPI < Goliath::API
9
+ def initialize(presentation)
10
+ @presentation = presentation
11
+ end
12
+
13
+ def response(env)
14
+ path_info = Rack::Utils.unescape(env["PATH_INFO"])
15
+ if path_info == "/"
16
+ serve_slides
17
+ elsif path_info.include? ".."
18
+ unauthorized_access
19
+ else
20
+ serve_asset(path_info)
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ def serve_slides
27
+ body = @presentation.html
28
+ [200, {
29
+ "Content-Type" => "text/html; charset=utf-8",
30
+ "Content-Length" => Rack::Utils.bytesize(body).to_s
31
+ }, [body] ]
32
+ end
33
+
34
+ def serve_asset(path_info)
35
+ path = @presentation.path_for_asset(path_info)
36
+ return page_not_found(path_info) unless path && File.readable?(path)
37
+ body = File.read(path)
38
+ [200, {
39
+ "Last-Modified" => File.mtime(path).httpdate,
40
+ "Content-Length" => Rack::Utils.bytesize(body).to_s,
41
+ "Content-Type" => Rack::Mime.mime_type(File.extname(path), 'text/plain'),
42
+ }, [body] ]
43
+ end
44
+
45
+ def page_not_found(path_info)
46
+ [404, {
47
+ "Content-Type" => "text/plain",
48
+ "Content-Length" => "0"
49
+ }, ["File not found: #{path_info}\n"] ]
50
+ end
51
+
52
+ def unauthorized_access
53
+ [403, {
54
+ "Content-Type" => "text/plain",
55
+ "Content-Length" => "0"
56
+ }, ["Forbidden\n"] ]
57
+ end
58
+ end
59
+ end