highcarb 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.
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: []