highcarb 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.swp
2
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@highcarb
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,47 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ highcarb (0.1)
5
+ coffee-script
6
+ em-websocket
7
+ haml
8
+ kramdown
9
+ mime-types
10
+ nokogiri
11
+ sass
12
+ thin
13
+ trollop
14
+
15
+ GEM
16
+ remote: http://rubygems.org/
17
+ specs:
18
+ addressable (2.2.7)
19
+ coffee-script (2.2.0)
20
+ coffee-script-source
21
+ execjs
22
+ coffee-script-source (1.2.0)
23
+ daemons (1.1.8)
24
+ em-websocket (0.3.6)
25
+ addressable (>= 2.1.1)
26
+ eventmachine (>= 0.12.9)
27
+ eventmachine (0.12.10)
28
+ execjs (1.3.0)
29
+ multi_json (~> 1.0)
30
+ haml (3.1.4)
31
+ kramdown (0.13.5)
32
+ mime-types (1.17.2)
33
+ multi_json (1.1.0)
34
+ nokogiri (1.5.0)
35
+ rack (1.4.1)
36
+ sass (3.1.15)
37
+ thin (1.3.1)
38
+ daemons (>= 1.0.9)
39
+ eventmachine (>= 0.12.6)
40
+ rack (>= 1.0.0)
41
+ trollop (1.16.2)
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ highcarb!
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # HighCarb
2
+
3
+ HighCarb is a framework to create presentations, and to control them remotely.
4
+
5
+ The presentation is based on Deck.js
6
+
7
+ ## Installation
8
+
9
+ I have to create a gem (or event a .deb package). Right now, the easiest way to install it
10
+ is clone the repository and add an alias.
11
+
12
+ ```
13
+ $ cd /somewhere/
14
+ $ git clone git://github.com/ayosec/highcarb.git
15
+ $ cd highcarb
16
+ $ bundle install
17
+ $ alias highcarb="ruby /somewhere/highcarb/bin/highcarb"
18
+ ```
19
+
20
+ ### Dependencies
21
+
22
+ * You have to install Pygmentize if you want to highlight the code snippets.
23
+ * A JavaScript interpreter is needed to compile the CoffeeScript source. Rhino or JavaScript can be used with no problems.
24
+
25
+ In Debian (and derived) everything can be installed with
26
+
27
+ ```
28
+ $ sudo apt-get install nodejs python-pygments
29
+ ```
30
+
31
+ ## Generate a presentation project
32
+
33
+ The `-g` flag generate a new tree with the base for the presentation
34
+
35
+ ```
36
+ $ highcarb -g /my/slides/foobar
37
+ ```
38
+
39
+ ## Adding content
40
+
41
+ The generated tree is something like
42
+
43
+ ```
44
+ /slide
45
+ ├── assets
46
+ │   ├── README
47
+ │   ├── base.scss
48
+ │   ├── remote.scss
49
+ │   ├── custom.coffee
50
+ │   ├── custom-remote.coffee
51
+ │   └── vendor
52
+ │   └── deck.js
53
+ │   ├── ...
54
+ │   └── ...
55
+ ├── slides
56
+ │   └── 0001.haml
57
+ └── snippets
58
+ └── README
59
+ ```
60
+
61
+ ### Slides
62
+
63
+ The content can be wrote in HAML, MarkDown or in raw HTML.
64
+
65
+ The generator will concatenate all the files when the presentation is shown.
66
+
67
+ #### Special tags
68
+
69
+ `%snippet` is used to load a file from the `snippets` directory. If Pygmentize is found, the code will be highlighted. If not, the content will be shown in a monospace font.
70
+
71
+ `%asset` load a file from the `assets` directory. If the file is an image, an `img` will be created. If it is a CSS file (or SCSS), a `link` tag will be used. And, for JavaScript (or CoffeeScript) files, a `script` tag is used.
72
+
73
+ If type asset type can not be determined by the MIME type, a CSS class can be added to the `asset` tag to force the type. The class can be `image`, `style` or `javascript`
74
+
75
+ If the asset is something else, a link will be added with an anchor.
76
+
77
+ `%external` can be used to create link to external pages. The shown text is shorted to be less noisy.
78
+
79
+ #### Notes
80
+
81
+ Everything with the `note` CSS class will be removed from the slide. This content is accessible in the `remote` view.
82
+
83
+ ## Assets
84
+
85
+ Every file from the `asset` directory is accessible from the `http://domain/asset/` URL.
86
+
87
+ If the file is a CoffeeScript source, it will be compiled as JavaScript before be sent. Same for SCSS.
88
+
89
+ ## Example
90
+
91
+ With this files
92
+
93
+ ```
94
+ /slide
95
+ ├── assets
96
+ │   ├── hacks.coffee
97
+ └── first.png
98
+ └── snippets
99
+ └── README
100
+ ```
101
+
102
+ We could write
103
+
104
+ ```haml
105
+
106
+ %asset hacks.coffee
107
+
108
+ .slide
109
+ %h1 First slide
110
+
111
+ %asset first.png
112
+
113
+ .slide
114
+ %h1 Second one
115
+
116
+ %ul
117
+ %li.slide this
118
+ %li.slide and
119
+ %li.slide that
120
+ %li.slide
121
+ See this:
122
+ %external http://somewhere.tld/sometime
123
+ ```
124
+
125
+ ## View the presentation
126
+
127
+
128
+ ```
129
+ $ highcarb /my/slides/foobar
130
+ ```
131
+
132
+ Some options are available with the `--help` flag.
133
+
134
+ With the defaults options the web server will listen on 9090, so the presentation can
135
+ be see at http://localhost:9090/
136
+
137
+ To control it from another browser go to http://localhost:9090/remote. The remote view
138
+ show the full slides, so you can see everything. Left and right keys can be used to move
139
+ the slide of the remote browser.
140
+
141
+ There is no need to restart the server if the content is changed. Everything will be regenerated
142
+ when reload the page in the browser. The HTML generated for the snippets is cached. The cached key
143
+ is the MD5 sum of the content.
data/bin/highcarb ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ require "highcarb/command"
6
+
7
+ HighCarb::Command.new.parse!(ARGV).run!
8
+
@@ -0,0 +1,47 @@
1
+
2
+ require "sass"
3
+
4
+ module HighCarb
5
+ module AssetsController
6
+ def assets(asset)
7
+ if asset.include?("/../")
8
+ plain_response! 403, "URL can not contain /../"
9
+ end
10
+
11
+ asset_path = assets_root.join("./" + asset)
12
+ if not asset_path.exist?
13
+ not_found! asset
14
+ end
15
+
16
+ if not asset_path.file?
17
+ plain_response! 403, "#{asset} is not a file"
18
+ end
19
+
20
+ output = nil
21
+ mime_type = nil
22
+
23
+ # Process SASS
24
+ if asset_path.extname == ".scss"
25
+ output = Sass::Engine.for_file(asset_path.to_s, {}).render
26
+ mime_type = "text/css"
27
+ end
28
+
29
+ # Process CoffeeScript
30
+ if asset_path.extname == ".coffee"
31
+ output = CoffeeScript.compile(asset_path.read)
32
+ mime_type = "application/javascript"
33
+ end
34
+
35
+ if output == nil
36
+ mime_type = MIME::Types.type_for(asset_path.to_s).first || "application/octet-stream"
37
+ output = asset_path.read
38
+ end
39
+
40
+ [
41
+ 200,
42
+ { "Content-Type" => mime_type.to_s },
43
+ output
44
+ ]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,61 @@
1
+
2
+ require "trollop"
3
+ require "logger"
4
+
5
+ require "highcarb"
6
+ require "highcarb/generator"
7
+ require "highcarb/services"
8
+
9
+ module HighCarb
10
+ class Command
11
+ attr_reader :options
12
+ attr_reader :command_line
13
+ attr_reader :args
14
+
15
+ def initialize
16
+ @command_line = @args = []
17
+ @options = {}
18
+ @logger = Logger.new(STDERR).tap {|logger| logger.level = Logger::WARN }
19
+ end
20
+
21
+ def parse!(args)
22
+ @command_line = args.dup
23
+ @args = args
24
+ @options = Trollop.options(@args) do
25
+ opt "generate", "Generate a new highcarb project"
26
+ opt "server", "Start the servers (default action). See --http-port and --ws-port"
27
+
28
+ opt "http-port", "HTTP server port", default: 9090
29
+ opt "ws-port", "WebSockets port", default: 9091
30
+
31
+ opt "skip-libs", "Don't download vendor libraries, like Deck.js and jQuery"
32
+
33
+ opt "verbose", "Be verbose"
34
+ end
35
+
36
+ if @options["verbose"]
37
+ @logger.level = Logger::DEBUG
38
+ end
39
+
40
+ self
41
+ end
42
+
43
+ def run!
44
+ if args.size != 1
45
+ STDERR.puts "Please indicate the project path as an extra argument of the command. For example:"
46
+ STDERR.puts "$ \033[1m#$0 #{command_line * " "} project-path/\033[m"
47
+ exit 1
48
+ end
49
+
50
+ if options["generate"]
51
+ # Generate a new project
52
+ HighCarb::Generator.new(self, args.first).run!
53
+ else
54
+ HighCarb::Services.start!(self, @logger)
55
+ end
56
+
57
+ rescue HighCarb::Error => error
58
+ STDERR.puts "ERROR: " + error.message
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,71 @@
1
+
2
+ require "pathname"
3
+
4
+ module HighCarb
5
+ class Generator
6
+
7
+ class PathAlreadyExist < HighCarb::Error
8
+ def message
9
+ "The path exist and can not overridden"
10
+ end
11
+ end
12
+
13
+ attr_reader :path
14
+ attr_reader :command
15
+
16
+ def initialize(command, path)
17
+ @command = command
18
+ @path = path
19
+ end
20
+
21
+ def run!
22
+ path = Pathname.new(self.path)
23
+ raise PathAlreadyExist if path.exist?
24
+
25
+ create_file path.join("slides/0001.haml"),
26
+ ".slide\n" +
27
+ " %h1 Title\n" +
28
+ " Content\n"
29
+
30
+ create_file path.join("assets/README"),
31
+ "Put in this directory any file that you want to use in your presentation (images, et al)\n\n" +
32
+ "Files ending with .coffee will be compiled with CoffeeScript.\n" +
33
+ "Files ending with .scss will be compiled with SASS. Compass is available."
34
+
35
+ create_file path.join("assets/base.scss"),
36
+ "/*\n * Write here your own styles.\n" +
37
+ " * Compass modules are available\n */\n\n\n" +
38
+ "@import url('/assets/vendor/deck.js/themes/style/swiss.css');\n" +
39
+ "@import url('/assets/vendor/deck.js/themes/transition/horizontal-slide.css');\n"
40
+
41
+ create_file path.join("assets/remote.scss"), "/* Add here your styles for the /remote view */"
42
+ create_file path.join("assets/custom-remote.coffee"), "# Add here your own code for the /remote view"
43
+ create_file path.join("assets/custom.coffee"), "# Add here your own code for the views"
44
+
45
+ create_file path.join("snippets/README"),
46
+ "Put in this directory any snippet of code that you want to include in your presentation.\n" +
47
+ "You need to install Pygmentize if you want to format the code.\n" +
48
+ "The snippets are loaded with a <snippet>name.rb</snippet> tag.\n" +
49
+ "With Haml, you can use %snippet name.rb\n"
50
+
51
+ # Download deck.js, which will include jQuery
52
+ if not command.options["skip-libs"]
53
+ vendor_path = path.join("assets/vendor").expand_path
54
+ vendor_path.mkpath
55
+ Dir.chdir vendor_path do
56
+ puts "Downloading Deck.js into \033[1m#{vendor_path}\033[m..."
57
+ system "curl -L https://github.com/imakewebthings/deck.js/tarball/master | tar xzf -"
58
+ vendor_path.children.first.rename vendor_path.join("deck.js")
59
+ end
60
+ end
61
+ end
62
+
63
+ # Helpers
64
+
65
+ def create_file(path, content)
66
+ puts "Create \033[1m#{path}\033[m"
67
+ path.dirname.mkpath
68
+ path.open "w" do |f| f.write content end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,59 @@
1
+
2
+ require "mime/types"
3
+ require "pathname"
4
+ require "haml"
5
+ require "kramdown"
6
+
7
+ require "highcarb/assets_controller"
8
+ require "highcarb/slides_controller"
9
+ require "highcarb/views_controller"
10
+
11
+ module HighCarb
12
+
13
+ class RackApp
14
+
15
+ include SlidesController
16
+ include AssetsController
17
+ include ViewsController
18
+
19
+ attr_reader :command
20
+ attr_reader :root
21
+ attr_reader :assets_root
22
+
23
+ def initialize(command)
24
+ @command = command
25
+ @root = Pathname.new(command.args.first)
26
+ @assets_root = @root.join("./assets")
27
+ end
28
+
29
+ def plain_response!(status, content)
30
+ throw :response, [status, {'Content-Type' => 'text/plain'}, content]
31
+ end
32
+
33
+ def not_found!(path)
34
+ plain_response! 404, "Object #{path} not found"
35
+ end
36
+
37
+ def call(env)
38
+ catch(:response) do
39
+ case env["PATH_INFO"]
40
+ when "/slides"
41
+ slides
42
+
43
+ when /\A\/assets\/(.*)\Z/
44
+ assets $1
45
+
46
+ when "/remote"
47
+ render_view "remote"
48
+
49
+ when "/"
50
+ render_view "index"
51
+
52
+ else
53
+ not_found! env["PATH_INFO"]
54
+ end
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,27 @@
1
+
2
+ require "thin"
3
+ require "em-websocket"
4
+
5
+ require "highcarb/rack_app"
6
+ require "highcarb/sockets"
7
+
8
+ module HighCarb
9
+ module Services
10
+ extend self
11
+
12
+ def start!(command, logger)
13
+ EM.run do
14
+ EM::WebSocket.start(host: '0.0.0.0', port: command.options["ws-port"] ) do |websocket|
15
+ WSConnection.new websocket, logger
16
+ end
17
+
18
+ Thin::Server.start(
19
+ '0.0.0.0',
20
+ command.options["http-port"],
21
+ Rack::Builder.new { run RackApp.new(command) }
22
+ )
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,136 @@
1
+
2
+ require "digest/md5"
3
+ require "nokogiri"
4
+
5
+ module HighCarb
6
+ module SlidesController
7
+ def slides
8
+ output = []
9
+
10
+ # Load the content from the sources
11
+ root.join("slides").children.sort.each do |slide_file|
12
+ # Only use non-hidden files
13
+ if slide_file.file? and slide_file.basename.to_s !~ /^\./
14
+ case slide_file.extname.downcase
15
+ when ".haml"
16
+ output << Haml::Engine.new(slide_file.read).render
17
+
18
+ when ".html"
19
+ output << slide_file.read
20
+
21
+ when ".md"
22
+ output << Kramdown::Document.new(slide_file.read).to_html
23
+
24
+ else
25
+ STDERR.puts "\033[31mCan not parse #{slide_file}\033[m"
26
+ end
27
+ end
28
+ end
29
+
30
+ page = Nokogiri::HTML.parse(output.join)
31
+
32
+ # Find the <snippet> tags and replace with the content from a
33
+ # file located under the snippet/ directory
34
+ page.search("snippet").each do |snippet_tag|
35
+ snippet_tag.replace load_snippet(snippet_tag.inner_text.strip)
36
+ end
37
+
38
+ # Find the <asset> tags and replace with link, script or img,
39
+ # depending on the MIME type
40
+ page.search("asset").each do |asset_tag|
41
+ asset_tag.replace load_asset(asset_tag.inner_text.strip, asset_tag.attributes["class"].to_s.split)
42
+ end
43
+
44
+ # Find the <external> tags and replace with <a>
45
+ # The text shown will be reduced
46
+ page.search("external").each do |external_tag|
47
+ href = ERB::Util.h(external_tag.inner_text.strip)
48
+
49
+ text = href.gsub(/\w+:\/+/, "")
50
+ text = text[0,45] + "&hellip;" if text.length > 45
51
+
52
+ external_tag.replace %[<a class="external" href="#{href}" target="_blank" title="Open #{href} in a new window">#{text}</a>]
53
+ end
54
+
55
+ # Append a server side generated identifier. This helps to identify them
56
+ # both in presenter- and remote-control-mode
57
+ last_slide_id = 0
58
+ page.search(".slide").each do |slide_node|
59
+ last_slide_id += 1
60
+ slide_node["data-slide-id"] = last_slide_id.to_s
61
+ end
62
+
63
+ # Response with everything
64
+ output = page.at("body").inner_html
65
+ throw :response, [200, {'Content-Type' => 'text/html'}, output]
66
+ end
67
+
68
+ def load_snippet(snippet_name)
69
+ snippet_path = root.join("snippets", snippet_name)
70
+ snippet_html_cached = root.join("tmp", "snippets",
71
+ Digest::MD5.new.tap {|digest| digest << snippet_path.read }.hexdigest + ".html")
72
+
73
+ if snippet_html_cached.exist? and snippet_html_cached.mtime > snippet_path.mtime
74
+
75
+ snippet_html_cached.read
76
+
77
+ else
78
+
79
+ content = begin
80
+ IO.popen(["pygmentize", "-f", "html", "-O", "noclasses=true", snippet_path.to_s]).read
81
+ rescue Errno::ENOENT
82
+ if not @pygmentize_error_shown
83
+ STDERR.puts "\033[31mpygmentize could not be used. You have to install it if you want to highlight the snippets."
84
+ STDERR.puts "The snippets will be included with no format\033[m"
85
+ @pygmentize_error_shown = true
86
+ end
87
+
88
+ %[<pre class="raw-snippet">#{ERB::Util.h snippet_path.read}</pre>]
89
+ end
90
+
91
+ snippet_html_cached.dirname.mkpath
92
+ snippet_html_cached.open("w") {|f| f.write content }
93
+ content
94
+ end
95
+ end
96
+
97
+ def load_asset(asset_name, css_class = [])
98
+ asset_path = assets_root.join(asset_name)
99
+ asset_url = "/assets/#{ERB::Util.h asset_name}"
100
+ asset_type = nil
101
+
102
+ if not css_class.empty?
103
+ # Check if the css_class list contains any of the valid classes
104
+ asset_type = (%w(image style javascript) & css_class).first
105
+ end
106
+
107
+ if asset_type.nil?
108
+ # If the class attribute has no known class, infer it with the MIME type
109
+ mime_type = MIME::Types.type_for(asset_name).first
110
+ asset_type =
111
+ if (mime_type and mime_type.media_type == "image")
112
+ "image"
113
+ elsif mime_type.to_s == "text/css"
114
+ "style"
115
+ elsif mime_type.to_s == "application/javascript" or asset_path.extname == "coffee"
116
+ "javascript"
117
+ end
118
+ end
119
+
120
+ case asset_type
121
+ when "image"
122
+ %[<img class="asset" src="#{asset_url}">]
123
+
124
+ when "style"
125
+ %[<link href="#{asset_url}" rel="stylesheet">]
126
+
127
+ when "javascript"
128
+ %[<script src="#{asset_url}"></script>]
129
+
130
+ else
131
+ %[<a href="#{asset_url}" target="_blank">#{ERB::Util.h asset_name}</script>]
132
+ end
133
+
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,47 @@
1
+
2
+ module HighCarb
3
+ class WSConnection
4
+
5
+ class <<self
6
+ attr_accessor :connected_clients
7
+ attr_accessor :last_client_id
8
+ end
9
+
10
+ self.connected_clients = []
11
+ self.last_client_id = 0
12
+
13
+ attr_reader :client_id
14
+ attr_reader :websocket
15
+ attr_reader :logger
16
+
17
+ def initialize(websocket, logger)
18
+ @logger = logger
19
+ @client_id = (self.class.last_client_id += 1)
20
+
21
+ @websocket = websocket
22
+ websocket.onopen &method(:on_open)
23
+ websocket.onclose &method(:on_close)
24
+ websocket.onmessage &method(:on_msg)
25
+ end
26
+
27
+ def on_open
28
+ logger.info { "[WS] Open client: #{client_id}" }
29
+ self.class.connected_clients << self
30
+ end
31
+
32
+ def on_close
33
+ logger.info { "[WS] Closed client: #{client_id}" }
34
+ self.class.connected_clients.delete self
35
+ end
36
+
37
+ def on_msg(msg)
38
+ logger.info { "[WS] Message from #{client_id}: #{msg}" }
39
+
40
+ self.class.connected_clients.each do |client|
41
+ if client != self
42
+ client.websocket.send msg
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,38 @@
1
+
2
+ require "json"
3
+ require "coffee-script"
4
+
5
+ module HighCarb
6
+ module ViewsController
7
+
8
+ DefaultViewsPath = Pathname.new(File.expand_path("../../../resources/views/", __FILE__))
9
+
10
+ class ViewContext
11
+ attr_reader :options, :root
12
+ def initialize(options, root)
13
+ @options = options
14
+ @root = root
15
+ end
16
+
17
+ def load_coffe(source)
18
+ "<script>//<![CDATA[\n" + CoffeeScript.compile(root.join(source + ".coffee").read) + "\n//]]></script>"
19
+ end
20
+ end
21
+
22
+ def render_view(view_name)
23
+ view_path = root.join("views", view_name + ".haml")
24
+ if not view_path.exist?
25
+ view_path = DefaultViewsPath.join(view_name + ".haml")
26
+ end
27
+
28
+ if not view_path.exist?
29
+ not_found! view_name + " view"
30
+ end
31
+
32
+ output = Haml::Engine.new(view_path.read).render(ViewContext.new(command.options, view_path.dirname))
33
+
34
+ throw :response, [200, {'Content-Type' => 'text/html'}, output]
35
+ end
36
+
37
+ end
38
+ end
data/lib/highcarb.rb ADDED
@@ -0,0 +1,5 @@
1
+
2
+ module HighCarb
3
+ class Error < StandardError
4
+ end
5
+ end
@@ -0,0 +1,42 @@
1
+
2
+ window.WebSocket = MozWebSocket if MozWebSocket?
3
+
4
+ $ ->
5
+
6
+ # Load slides and initialize Deck.js
7
+ $(".deck-container").load "/slides",
8
+ ->
9
+ $(".deck-container").find(".note").remove()
10
+ $.deck ".slide"
11
+
12
+ # Open a permanent connection to the server. With this channel
13
+ # we can receive commands to change the current slide
14
+ toJson = JSON.stringify
15
+
16
+ channel = new WebSocket WebSocketsURL
17
+ channel.onmessage = (msgEvent) ->
18
+ msg = JSON.parse(msgEvent.data)
19
+
20
+ switch msg.action
21
+ when "next-slide" then $.deck('next')
22
+ when "prev-slide" then $.deck('prev')
23
+ when "go-to"
24
+ for slide, index in $.deck('getSlides')
25
+ if slide.data("slide-id") == msg.slideId
26
+ $.deck("go", index)
27
+ break
28
+
29
+ channel.onopen = ->
30
+ channel.send toJson(ack: true)
31
+
32
+ # Send a notification to every other client when the current slide has changed
33
+ lastSlideSelectedEvent = -1
34
+ $(document).bind 'deck.change', (event, from, to) ->
35
+ if lastSlideSelectedEvent != -1
36
+ clearTimeout lastSlideSelectedEvent
37
+
38
+ lastSlideSelectedEvent = \
39
+ setTimeout ->
40
+ lastSlideSelectedEvent = -1
41
+ channel.send toJson(action: "slide-selected", slideId: $.deck('getSlide').data("slide-id"))
42
+ 100
@@ -0,0 +1,21 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %link{rel: "stylesheet", href: "/assets/base.scss"}
5
+
6
+ %body.presenter
7
+ .deck-container Loading...
8
+
9
+ %script{ src: "/assets/vendor/deck.js/jquery-1.7.min.js" }
10
+ %script{ src: "/assets/vendor/deck.js/modernizr.custom.js" }
11
+ %script{ src: "/assets/vendor/deck.js/core/deck.core.js" }
12
+ %script{ src: "/assets/vendor/deck.js/extensions/menu/deck.menu.js" }
13
+ %script{ src: "/assets/vendor/deck.js/extensions/goto/deck.goto.js" }
14
+ %script{ src: "/assets/vendor/deck.js/extensions/status/deck.status.js" }
15
+ %script{ src: "/assets/vendor/deck.js/extensions/hash/deck.hash.js" }
16
+
17
+ :javascript
18
+ window.WebSocketsURL = "ws://" + location.hostname + ":#{options["ws-port"].to_json}/";
19
+
20
+ = load_coffe "index"
21
+ %script{ src: "/assets/custom.coffee" }
@@ -0,0 +1,56 @@
1
+
2
+ window.WebSocket = MozWebSocket if MozWebSocket?
3
+
4
+ $ ->
5
+ $(".slides").
6
+ load("/slides").
7
+ delegate(".slide", "click", (event) ->
8
+ node = event.target
9
+ while node
10
+ slideId = $(node).data("slide-id")
11
+ if slideId
12
+ event.preventDefault()
13
+ channel.send toJson(action: "go-to", slideId: slideId)
14
+ return false
15
+
16
+ node = node.parentNode
17
+
18
+ true
19
+ )
20
+
21
+ toJson = JSON.stringify
22
+
23
+ channel = new WebSocket WebSocketsURL
24
+ channel.onmessage = (msgEvent) ->
25
+ msg = JSON.parse(msgEvent.data)
26
+
27
+ # Set the remote-selected class to the new slide
28
+ $(".remote-selected").removeClass "remote-selected"
29
+ item = $(".slide[data-slide-id=#{msg.slideId}]").
30
+ addClass("remote-selected")
31
+
32
+ # Keep the selected item always in the center
33
+ offset = item.offset()
34
+ if offset
35
+ newTop = offset.top - ($(window).height() - item.height()) / 2
36
+ #$("html").scrollTop newTop
37
+ $("html").animate(scrollTop: newTop, 300)
38
+
39
+ channel.onopen = ->
40
+ channel.send toJson(ack: true)
41
+
42
+ # Send commands to the presenter
43
+ nextSlide = -> channel.send toJson(action: "next-slide")
44
+ prevSlide = -> channel.send toJson(action: "prev-slide")
45
+ $(".actions .next").click nextSlide
46
+ $(".actions .prev").click prevSlide
47
+
48
+ $(document).keypress (event) ->
49
+ switch(event.keyCode)
50
+ when 37 then prevSlide()
51
+ when 39 then nextSlide()
52
+ else return
53
+
54
+ event.preventDefault()
55
+
56
+
@@ -0,0 +1,61 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %title Remote control
5
+ %script{ src: "/assets/vendor/deck.js/jquery-1.7.min.js" }
6
+ %script{ src: "/assets/custom.coffee" }
7
+ %script{ src: "/assets/custom-remote.coffee" }
8
+
9
+ %style
10
+ :erb
11
+ body {
12
+ background: black;
13
+ color: white;
14
+ }
15
+
16
+ .slides > .slide {
17
+ background: white;
18
+ color: black;
19
+ border: 1px solid black;
20
+ padding: 1em;
21
+ margin: 1em;
22
+ }
23
+
24
+ .slide:hover {
25
+ cursor: pointer;
26
+ border: 1px solid black;
27
+ }
28
+
29
+ .remote-selected { background: #ffc !important; }
30
+
31
+ .actions {
32
+ position: fixed;
33
+ right: 1em;
34
+ top: 1em;
35
+ }
36
+
37
+ .actions span {
38
+ color: black;
39
+ background: gray;
40
+ display: inline-block;
41
+ font-size: 120%;
42
+ cursor: pointer;
43
+ margin: 0.1ex;
44
+ padding: 0.5ex;
45
+ }
46
+
47
+ %link{rel: "stylesheet", href: "/assets/base.scss"}
48
+ %link{rel: "stylesheet", href: "/assets/remote.scss"}
49
+
50
+ %body.remote
51
+ .actions
52
+ %span.prev Prev
53
+ %span.next Next
54
+
55
+ .slides Loading ...
56
+
57
+ :javascript
58
+ window.WebSocketsURL = "ws://" + location.hostname + ":#{options["ws-port"].to_json}/";
59
+
60
+ = load_coffe "remote"
61
+
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: highcarb
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ayose Cazorla
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thin
16
+ requirement: &4114200 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *4114200
25
+ - !ruby/object:Gem::Dependency
26
+ name: mime-types
27
+ requirement: &4109560 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *4109560
36
+ - !ruby/object:Gem::Dependency
37
+ name: em-websocket
38
+ requirement: &4104920 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *4104920
47
+ - !ruby/object:Gem::Dependency
48
+ name: trollop
49
+ requirement: &4103380 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *4103380
58
+ - !ruby/object:Gem::Dependency
59
+ name: nokogiri
60
+ requirement: &4102700 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *4102700
69
+ - !ruby/object:Gem::Dependency
70
+ name: haml
71
+ requirement: &4100660 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *4100660
80
+ - !ruby/object:Gem::Dependency
81
+ name: sass
82
+ requirement: &4100060 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *4100060
91
+ - !ruby/object:Gem::Dependency
92
+ name: kramdown
93
+ requirement: &4099460 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :runtime
100
+ prerelease: false
101
+ version_requirements: *4099460
102
+ - !ruby/object:Gem::Dependency
103
+ name: coffee-script
104
+ requirement: &4098560 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: *4098560
113
+ description: HighCarb can build a presentation based on HAML, Markdown or raw HTML,
114
+ and let to control them from another browser, via WebSockets.
115
+ email:
116
+ - ayosec@gmail.com
117
+ executables:
118
+ - highcarb
119
+ extensions: []
120
+ extra_rdoc_files: []
121
+ files:
122
+ - .gitignore
123
+ - .rvmrc
124
+ - Gemfile
125
+ - Gemfile.lock
126
+ - README.md
127
+ - bin/highcarb
128
+ - lib/highcarb.rb
129
+ - lib/highcarb/assets_controller.rb
130
+ - lib/highcarb/command.rb
131
+ - lib/highcarb/generator.rb
132
+ - lib/highcarb/rack_app.rb
133
+ - lib/highcarb/services.rb
134
+ - lib/highcarb/slides_controller.rb
135
+ - lib/highcarb/sockets.rb
136
+ - lib/highcarb/views_controller.rb
137
+ - resources/views/index.coffee
138
+ - resources/views/index.haml
139
+ - resources/views/remote.coffee
140
+ - resources/views/remote.haml
141
+ homepage: https://github.com/ayosec/highcarb
142
+ licenses: []
143
+ post_install_message:
144
+ rdoc_options: []
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ none: false
149
+ requirements:
150
+ - - ! '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ! '>='
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 1.8.10
162
+ signing_key:
163
+ specification_version: 3
164
+ summary: Slides manager based on Deck.js
165
+ test_files: []