wee-pm 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/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
|