wee-pm 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +60 -0
- data/TODO +9 -0
- data/bin/wee-pm +114 -0
- data/bin/wee-pm-pdf +49 -0
- data/lib/wee-pm/colorize.rb +475 -0
- data/lib/wee-pm/colorize_filter.rb +22 -0
- data/lib/wee-pm/converter.rb +104 -0
- data/lib/wee-pm/presentation.rb +152 -0
- data/lib/wee-pm/presentation_maker.rb +395 -0
- data/lib/wee-pm/slideshtml.rb +204 -0
- data/lib/wee-pm/utils.rb +40 -0
- data/wee-pm.gemspec +25 -0
- metadata +71 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
class ColorizeFilter
|
2
|
+
def initialize(ruby_bin="ruby", colorize_bin=File.join(File.dirname(__FILE__), "colorize.rb"))
|
3
|
+
@ruby_bin, @colorize_bin = ruby_bin, colorize_bin
|
4
|
+
end
|
5
|
+
|
6
|
+
def run(lines, port, lang="__auto__", filename="")
|
7
|
+
lang ||= '__auto__'
|
8
|
+
|
9
|
+
cmd = "#{ @ruby_bin } #{ @colorize_bin } --cache --terminal=xterm-color --strip-ws --lang=#{ lang }"
|
10
|
+
if lines.nil?
|
11
|
+
cmd << " " + filename
|
12
|
+
end
|
13
|
+
|
14
|
+
IO.popen(cmd, "w+") { |f|
|
15
|
+
if lines
|
16
|
+
f << lines
|
17
|
+
f.close_write
|
18
|
+
end
|
19
|
+
port << f.read
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'wee-pm/colorize_filter'
|
2
|
+
|
3
|
+
class HtmlConverter
|
4
|
+
def count_overlays(content)
|
5
|
+
conv = converter()
|
6
|
+
conv.count = true
|
7
|
+
SlidesMarkup.new.convert(content, conv)
|
8
|
+
end
|
9
|
+
|
10
|
+
def render_on(r, overlay, content)
|
11
|
+
conv = converter()
|
12
|
+
conv.count = false
|
13
|
+
conv.show_number_of_overlays = overlay
|
14
|
+
if overlay > 0
|
15
|
+
@r = r
|
16
|
+
r << SlidesMarkup.new.convert(content, conv)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_annotations_on(r, annotations)
|
21
|
+
conv = converter()
|
22
|
+
conv.count = false
|
23
|
+
conv.show_number_of_overlays = nil
|
24
|
+
@r = r
|
25
|
+
r << SlidesMarkup.new.convert(annotations, conv)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def converter
|
32
|
+
conv = SlidesHtml.new
|
33
|
+
conv.block_processor = proc {|processor, option, lines|
|
34
|
+
port = ""
|
35
|
+
case processor
|
36
|
+
when 'colorize'
|
37
|
+
lang, *more = option.split(",")
|
38
|
+
h = {}
|
39
|
+
more.map {|i| k, v = i.split("="); h[k] = v}
|
40
|
+
|
41
|
+
lines = File.read(h['file']) if h['file']
|
42
|
+
|
43
|
+
if h['exec']
|
44
|
+
require 'tempfile'
|
45
|
+
f = Tempfile.new('pm')
|
46
|
+
f << "#!/bin/sh\n"
|
47
|
+
f << h['exec']
|
48
|
+
f << "\nread line\n"
|
49
|
+
f.close(false)
|
50
|
+
File.chmod(0755, f.path)
|
51
|
+
url = @r.url_for_callback(proc { `xterm #{ f.path }`; f.unlink })
|
52
|
+
port << %[<a class="codelink" href="#{ url }">execute</a>]
|
53
|
+
port << "<pre class='codefile'>"
|
54
|
+
else
|
55
|
+
port << "<pre>"
|
56
|
+
end
|
57
|
+
ColorizeFilter.new.run(lines, port, lang)
|
58
|
+
port << "</pre>\n"
|
59
|
+
when 'exec'
|
60
|
+
url = @r.url_for_callback(proc { `#{ lines }` })
|
61
|
+
port << %[<a class="codelink" href="#{ url }">execute</a>]
|
62
|
+
unless option == "hidden"
|
63
|
+
port << "<pre class='codefile'>"
|
64
|
+
port << lines
|
65
|
+
port << "</pre>\n"
|
66
|
+
end
|
67
|
+
#when 'file'
|
68
|
+
else
|
69
|
+
raise "unknown processor"
|
70
|
+
end
|
71
|
+
port
|
72
|
+
}
|
73
|
+
conv
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
class PsHtmlConverter < HtmlConverter
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def converter
|
83
|
+
conv = SlidesHtml.new
|
84
|
+
conv.block_processor = proc {|processor, option, lines|
|
85
|
+
port = ""
|
86
|
+
case processor
|
87
|
+
when 'colorize'
|
88
|
+
port << "<pre>"
|
89
|
+
ColorizeFilter.new.run(lines, port, option)
|
90
|
+
port << "</pre>\n"
|
91
|
+
when 'exec'
|
92
|
+
port << "<pre class='codefile'>"
|
93
|
+
port << "!!#{processor}#{ option ? ':' + option : ''}\n"
|
94
|
+
port << lines
|
95
|
+
port << "</pre>\n"
|
96
|
+
else
|
97
|
+
raise "unknown processor"
|
98
|
+
end
|
99
|
+
port
|
100
|
+
}
|
101
|
+
conv
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'wee-pm/slideshtml'
|
2
|
+
require 'wee-pm/utils'
|
3
|
+
|
4
|
+
# A presentation is mainly a collection of slides.
|
5
|
+
|
6
|
+
class Presentation
|
7
|
+
attr_accessor :title, :author, :email, :style
|
8
|
+
attr_reader :slides
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def from_string(str)
|
12
|
+
positions = [0]
|
13
|
+
str.scan(/^=([^=].*)$/) { positions << $~.offset(0).first }
|
14
|
+
slides = split_at(str, positions)
|
15
|
+
|
16
|
+
new {|pres|
|
17
|
+
pres.title = $1.strip if slides[0] =~ /^title::(.*)$/
|
18
|
+
pres.author = $1.strip if slides[0] =~ /^author::(.*)$/
|
19
|
+
pres.email = $1.strip if slides[0] =~ /^email::(.*)$/
|
20
|
+
pres.style = $1.strip if slides[0] =~ /^style::(.*)^endstyle::$/m
|
21
|
+
|
22
|
+
slides[1..-1].each do |s|
|
23
|
+
pres.slides << Slide.from_string(s)
|
24
|
+
end
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def split_at(s, pos)
|
31
|
+
(0..(pos.size-2)).collect {|i| s[pos[i]...pos[i+1]] } + [s[pos[-1]..-1]]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@title = 'unnamed presentation'
|
37
|
+
@author = ''
|
38
|
+
@email = ''
|
39
|
+
@style = ''
|
40
|
+
@slides = []
|
41
|
+
yield self if block_given?
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
str = "title:: #{ @title }\n" +
|
46
|
+
"author:: #{ @author }\n" +
|
47
|
+
"email:: #{ @email }\n"
|
48
|
+
unless @style.strip.empty?
|
49
|
+
str << "style::\n"
|
50
|
+
str << @style
|
51
|
+
str << "\n"
|
52
|
+
str << "endstyle::\n"
|
53
|
+
end
|
54
|
+
|
55
|
+
str << "\n"
|
56
|
+
|
57
|
+
@slides.each { |s| str << s.to_s }
|
58
|
+
str.gsub!("\r\n", "\n")
|
59
|
+
str
|
60
|
+
end
|
61
|
+
|
62
|
+
def save(filename)
|
63
|
+
File.open(filename, 'w+') {|f| f << self.to_s }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Each slide consists of one or more overlays.
|
68
|
+
class Slide
|
69
|
+
attr_accessor :title
|
70
|
+
attr_accessor :content # the rdoc content of the slide (excluding the title)
|
71
|
+
attr_accessor :annotations
|
72
|
+
attr_accessor :converter
|
73
|
+
attr_accessor :style
|
74
|
+
|
75
|
+
def content=(str)
|
76
|
+
@content = remove_leading_and_trailing_empty_lines(str)
|
77
|
+
end
|
78
|
+
|
79
|
+
def annotations=(str)
|
80
|
+
@annotations = remove_leading_and_trailing_empty_lines(str)
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
str = "= #{ @title }\n\n" + @content + "\n"
|
85
|
+
unless @annotations.strip.empty?
|
86
|
+
str << "annotations::\n"
|
87
|
+
str << @annotations
|
88
|
+
str << "\n"
|
89
|
+
str << "endannotations::\n"
|
90
|
+
end
|
91
|
+
unless @style.strip.empty?
|
92
|
+
str << "style::\n"
|
93
|
+
str << @style
|
94
|
+
str << "\n"
|
95
|
+
str << "endstyle::\n"
|
96
|
+
end
|
97
|
+
str << "\n"
|
98
|
+
str
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.from_string(str)
|
102
|
+
new {|s|
|
103
|
+
if str =~ /^=([^=].*)$/
|
104
|
+
str = $~.post_match
|
105
|
+
s.title = $1.strip
|
106
|
+
end
|
107
|
+
if str =~ /^\w+::/
|
108
|
+
s.content = $~.pre_match
|
109
|
+
if str =~ /^annotations::(.*)^endannotations::$/m
|
110
|
+
s.annotations = $1.strip
|
111
|
+
end
|
112
|
+
if str =~ /^style::(.*)^endstyle::$/m
|
113
|
+
s.style = $1.strip
|
114
|
+
end
|
115
|
+
else
|
116
|
+
s.content = str
|
117
|
+
end
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
def initialize
|
122
|
+
@number_of_overlays = []
|
123
|
+
@title = 'unnamed slide'
|
124
|
+
@content = ''
|
125
|
+
@annotations = ''
|
126
|
+
@style = ''
|
127
|
+
yield self if block_given?
|
128
|
+
end
|
129
|
+
|
130
|
+
def number_of_overlays
|
131
|
+
if @content != @number_of_overlays[1]
|
132
|
+
# content has changed -> re-calculate the number of overlays
|
133
|
+
num =
|
134
|
+
if @content.strip.empty?
|
135
|
+
0
|
136
|
+
else
|
137
|
+
converter().new.count_overlays(@content)
|
138
|
+
end
|
139
|
+
@number_of_overlays = [num, @content.dup]
|
140
|
+
end
|
141
|
+
@number_of_overlays[0]
|
142
|
+
end
|
143
|
+
|
144
|
+
def render_on(r, overlay)
|
145
|
+
converter().new.render_on(r, overlay, @content)
|
146
|
+
end
|
147
|
+
|
148
|
+
def render_annotations_on(r)
|
149
|
+
converter().new.render_annotations_on(r, @annotations)
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
@@ -0,0 +1,395 @@
|
|
1
|
+
require 'wee-pm/presentation'
|
2
|
+
require 'wee-pm/converter'
|
3
|
+
|
4
|
+
class PresentationMaker < Wee::Component
|
5
|
+
|
6
|
+
AREA_COLS = 90
|
7
|
+
DEFAULT_CONVERTER = HtmlConverter
|
8
|
+
|
9
|
+
def initialize(filename)
|
10
|
+
super()
|
11
|
+
@filename = filename
|
12
|
+
@presentation =
|
13
|
+
if File.exists? @filename
|
14
|
+
Presentation.from_string(File.read(@filename))
|
15
|
+
else
|
16
|
+
Presentation.new
|
17
|
+
end
|
18
|
+
|
19
|
+
if @presentation.slides.empty?
|
20
|
+
@presentation.slides << Slide.new
|
21
|
+
end
|
22
|
+
|
23
|
+
@presentation.slides.each do |sl|
|
24
|
+
sl.converter = DEFAULT_CONVERTER
|
25
|
+
end
|
26
|
+
|
27
|
+
@edit_mode = false
|
28
|
+
|
29
|
+
@show_title_page = true
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# --------------------------------------------------------------------------
|
34
|
+
# Actions: Show
|
35
|
+
# --------------------------------------------------------------------------
|
36
|
+
|
37
|
+
def show_title_page
|
38
|
+
@show_title_page = true
|
39
|
+
@current_slide_index = @current_slide = @current_overlay = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def select_slide(index)
|
43
|
+
@show_title_page = false
|
44
|
+
|
45
|
+
@current_slide_index = index
|
46
|
+
@current_slide = @presentation.slides[@current_slide_index]
|
47
|
+
@current_overlay = 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def show_all_overlays
|
51
|
+
@current_overlay = @current_slide.number_of_overlays
|
52
|
+
end
|
53
|
+
|
54
|
+
def next_slide_or_overlay
|
55
|
+
if @show_title_page
|
56
|
+
@show_title_page = false
|
57
|
+
select_slide(0)
|
58
|
+
elsif @current_overlay >= @current_slide.number_of_overlays
|
59
|
+
if @current_slide_index < @presentation.slides.size-1
|
60
|
+
select_slide(@current_slide_index + 1)
|
61
|
+
else
|
62
|
+
# TODO: show End-Box
|
63
|
+
end
|
64
|
+
else
|
65
|
+
@current_overlay += 1
|
66
|
+
end
|
67
|
+
|
68
|
+
unless @step_mode
|
69
|
+
@current_overlay = @current_slide.number_of_overlays
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def prev_slide_or_overlay
|
74
|
+
# we are already on the first page -> do nothing!
|
75
|
+
return if @show_title_page
|
76
|
+
|
77
|
+
if @step_mode
|
78
|
+
@current_overlay -= 1
|
79
|
+
return if @current_overlay >= 0
|
80
|
+
end
|
81
|
+
|
82
|
+
@current_slide_index -= 1
|
83
|
+
if @current_slide_index < 0
|
84
|
+
show_title_page()
|
85
|
+
else
|
86
|
+
select_slide(@current_slide_index)
|
87
|
+
@current_overlay = @current_slide.number_of_overlays
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# --------------------------------------------------------------------------
|
92
|
+
# Actions: Edit
|
93
|
+
# --------------------------------------------------------------------------
|
94
|
+
|
95
|
+
def add_new_slide_before
|
96
|
+
index = @current_slide_index
|
97
|
+
@presentation.slides[index,0] = Slide.new {|s|
|
98
|
+
s.title = @current_slide.title
|
99
|
+
s.converter = DEFAULT_CONVERTER
|
100
|
+
}
|
101
|
+
select_slide(index)
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_new_slide_after
|
105
|
+
@current_slide_index += 1
|
106
|
+
add_new_slide_before
|
107
|
+
end
|
108
|
+
|
109
|
+
def delete_current_slide
|
110
|
+
return if @presentation.slides.size <= 1
|
111
|
+
@presentation.slides.delete_at(@current_slide_index)
|
112
|
+
select_slide([@current_slide_index, @presentation.slides.size-1].min)
|
113
|
+
end
|
114
|
+
|
115
|
+
def move_current_slide_up
|
116
|
+
sl = @presentation.slides
|
117
|
+
ci = @current_slide_index
|
118
|
+
return if ci <= 0
|
119
|
+
sl[ci-1], sl[ci] = sl[ci], sl[ci-1]
|
120
|
+
select_slide(ci-1)
|
121
|
+
end
|
122
|
+
|
123
|
+
def move_current_slide_down
|
124
|
+
sl = @presentation.slides
|
125
|
+
ci = @current_slide_index
|
126
|
+
return if ci >= sl.size-1
|
127
|
+
sl[ci+1], sl[ci] = sl[ci], sl[ci+1]
|
128
|
+
select_slide(ci+1)
|
129
|
+
end
|
130
|
+
|
131
|
+
def save_presentation
|
132
|
+
@presentation.save(@filename)
|
133
|
+
end
|
134
|
+
|
135
|
+
def toggle_mode
|
136
|
+
@edit_mode = !@edit_mode
|
137
|
+
end
|
138
|
+
|
139
|
+
def toggle_step_mode
|
140
|
+
@step_mode = !@step_mode
|
141
|
+
end
|
142
|
+
|
143
|
+
# --------------------------------------------------------------------------
|
144
|
+
# Rendering
|
145
|
+
# --------------------------------------------------------------------------
|
146
|
+
|
147
|
+
def render
|
148
|
+
r.html do
|
149
|
+
r.head do
|
150
|
+
r.title("Presentation Maker #{ @filename }")
|
151
|
+
r.style(@presentation.style)
|
152
|
+
if @current_slide
|
153
|
+
r.style(@current_slide.style)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
body = r.body
|
158
|
+
|
159
|
+
#if not @edit_mode
|
160
|
+
# body.onclick(javascript_action {||next_slide_or_overlay})
|
161
|
+
#end
|
162
|
+
|
163
|
+
body.with do
|
164
|
+
|
165
|
+
if not @edit_mode
|
166
|
+
page_down = r.url_for_callback(proc {
|
167
|
+
next_slide_or_overlay()
|
168
|
+
})
|
169
|
+
page_up = r.url_for_callback(proc {
|
170
|
+
prev_slide_or_overlay()
|
171
|
+
})
|
172
|
+
toggle_mode_url = r.url_for_callback(proc {
|
173
|
+
toggle_mode()
|
174
|
+
})
|
175
|
+
toggle_step_mode_url = r.url_for_callback(proc {
|
176
|
+
toggle_step_mode()
|
177
|
+
})
|
178
|
+
|
179
|
+
r << %{
|
180
|
+
<script>
|
181
|
+
document.onkeypress = function(ev) {
|
182
|
+
switch (ev.keyCode) {
|
183
|
+
case 101: /* 'e' */
|
184
|
+
document.location.href='#{ toggle_mode_url }';
|
185
|
+
return false;
|
186
|
+
case 115: /* 's' */
|
187
|
+
document.location.href='#{ toggle_step_mode_url }';
|
188
|
+
return false;
|
189
|
+
case 34: /* page down */
|
190
|
+
document.location.href='#{ page_down }';
|
191
|
+
return false;
|
192
|
+
case 33: /* page up */
|
193
|
+
document.location.href='#{ page_up }';
|
194
|
+
return false;
|
195
|
+
};
|
196
|
+
return true;
|
197
|
+
};
|
198
|
+
</script>
|
199
|
+
}
|
200
|
+
end
|
201
|
+
|
202
|
+
r.span.id("navibutton").with do
|
203
|
+
r.anchor.callback { toggle_mode }.with do
|
204
|
+
r.space(2)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
render_body
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def render_slide
|
213
|
+
if @show_title_page
|
214
|
+
r.div.id('titlepage').css_class('slide').with do
|
215
|
+
r.h1.onclick(javascript_action {||next_slide_or_overlay}).
|
216
|
+
with(@presentation.title)
|
217
|
+
|
218
|
+
r.div.id('author_email').with do
|
219
|
+
r.text @presentation.author
|
220
|
+
r.text(" (")
|
221
|
+
r.anchor.href("mailto:#{ @presentation.email }").with(@presentation.email)
|
222
|
+
r.text(")")
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
elsif @current_slide
|
227
|
+
r.div.id('slides').css_class('slide'). with do
|
228
|
+
r.h1.onclick(javascript_action {||next_slide_or_overlay}).
|
229
|
+
with(@current_slide.title)
|
230
|
+
@current_slide.render_on(r, @current_overlay)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def render_navi
|
236
|
+
r.div.id('naviframe').with do
|
237
|
+
r.anchor.callback{toggle_mode}.with { r.text('Hide') }
|
238
|
+
|
239
|
+
r.text(" | ")
|
240
|
+
|
241
|
+
r.anchor.callback { toggle_step_mode }.with do
|
242
|
+
r.text('Change into Full-Slide mode') if @step_mode
|
243
|
+
r.text('Change into Step mode') if !@step_mode
|
244
|
+
end
|
245
|
+
|
246
|
+
r.text(" | ")
|
247
|
+
r.anchor.callback{save_presentation}.with { r.text('Save presentation') }
|
248
|
+
r.hr
|
249
|
+
|
250
|
+
r.text("Presentation: ")
|
251
|
+
if @show_title_page
|
252
|
+
r.text(@presentation.title)
|
253
|
+
else
|
254
|
+
r.anchor.callback{show_title_page}.with { r.text(@presentation.title) }
|
255
|
+
end
|
256
|
+
r.break
|
257
|
+
|
258
|
+
render_outline
|
259
|
+
|
260
|
+
r.hr
|
261
|
+
|
262
|
+
if @show_title_page
|
263
|
+
render_title_edit
|
264
|
+
else
|
265
|
+
render_slide_edit
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def render_title_edit
|
271
|
+
r.form do
|
272
|
+
r.submit_button.value('Update')
|
273
|
+
|
274
|
+
r.table.with do
|
275
|
+
%w(title author email).each do |f|
|
276
|
+
r.table_row.with do
|
277
|
+
r.table_data("#{ f.capitalize }: ")
|
278
|
+
r.table_data.with {
|
279
|
+
r.text_input.size(35).
|
280
|
+
callback {|c| @presentation.send("#{ f }=", c.to_s) }.
|
281
|
+
value(@presentation.send("#{ f }"))
|
282
|
+
}
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end # table
|
286
|
+
|
287
|
+
r.paragraph
|
288
|
+
r.text("Style:")
|
289
|
+
r.break
|
290
|
+
r.text_area.cols(AREA_COLS).rows(10).callback {|c| @presentation.style = c.to_s}.
|
291
|
+
with(@presentation.style)
|
292
|
+
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def render_slide_edit
|
297
|
+
r.ul do
|
298
|
+
r.li {
|
299
|
+
r.text('Add new slide ')
|
300
|
+
r.anchor.callback{add_new_slide_before}.with { r.text('before') }; r.text(' / ')
|
301
|
+
r.anchor.callback{add_new_slide_after}.with { r.text('after') }; r.space
|
302
|
+
r.text('current')
|
303
|
+
}
|
304
|
+
r.li {
|
305
|
+
r.text('Current slide: ')
|
306
|
+
r.anchor.callback{move_current_slide_up}.with { r.text('move up') }; r.text(' / ')
|
307
|
+
r.anchor.callback{move_current_slide_down}.with { r.text('move down') }; r.text(' / ')
|
308
|
+
r.anchor.callback{delete_current_slide}.with { r.text('delete') }
|
309
|
+
}
|
310
|
+
end
|
311
|
+
|
312
|
+
r.hr
|
313
|
+
|
314
|
+
r.paragraph
|
315
|
+
r.form do
|
316
|
+
r.submit_button.value('Update').callback { @current_overlay = @current_slide.number_of_overlays }
|
317
|
+
r.space
|
318
|
+
|
319
|
+
r.text "Title: "
|
320
|
+
r.text_input.size(35).callback {|c| @current_slide.title = c.to_s}.value(@current_slide.title)
|
321
|
+
|
322
|
+
r.paragraph
|
323
|
+
r.text_area.cols(AREA_COLS).rows(30).callback {|c| @current_slide.content = c.to_s}.with(@current_slide.content)
|
324
|
+
|
325
|
+
r.paragraph
|
326
|
+
r.text "Annotations:"
|
327
|
+
r.break
|
328
|
+
r.text_area.cols(AREA_COLS).rows(10).callback {|c| @current_slide.annotations = c.to_s}.
|
329
|
+
with(@current_slide.annotations)
|
330
|
+
|
331
|
+
r.paragraph
|
332
|
+
r.text "Style:"
|
333
|
+
r.break
|
334
|
+
r.text_area.cols(AREA_COLS).rows(10).callback {|c| @current_slide.style = c.to_s}.
|
335
|
+
with(@current_slide.style)
|
336
|
+
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def render_outline
|
341
|
+
r.ol {
|
342
|
+
@presentation.slides.each_with_index do |slide, i|
|
343
|
+
r.li do
|
344
|
+
if i == @current_slide_index
|
345
|
+
r.bold slide.title
|
346
|
+
r.break
|
347
|
+
|
348
|
+
(0 .. @current_slide.number_of_overlays).each do |j|
|
349
|
+
if j == @current_overlay
|
350
|
+
r.text "#{ j+1 }"
|
351
|
+
else
|
352
|
+
r.anchor.callback { @current_overlay = j }.with {
|
353
|
+
r.text "#{ j+1 }"
|
354
|
+
}
|
355
|
+
end
|
356
|
+
r.space
|
357
|
+
end
|
358
|
+
else
|
359
|
+
r.anchor.callback { select_slide(i); show_all_overlays() }.with {
|
360
|
+
r.text slide.title
|
361
|
+
}
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
}
|
366
|
+
end
|
367
|
+
|
368
|
+
def render_body
|
369
|
+
if not @edit_mode
|
370
|
+
render_slide
|
371
|
+
else
|
372
|
+
r.table do
|
373
|
+
r.table_row do
|
374
|
+
|
375
|
+
r.table_data.valign('top').with do
|
376
|
+
render_navi
|
377
|
+
end
|
378
|
+
|
379
|
+
r.table_data.valign('top').with do
|
380
|
+
render_slide
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
private
|
389
|
+
|
390
|
+
def javascript_action(&block)
|
391
|
+
url = r.url_for_callback(block)
|
392
|
+
"javascript: document.location.href='#{ url }';"
|
393
|
+
end
|
394
|
+
|
395
|
+
end
|