presently 0.20.0 → 0.21.0
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/bake/presently/rehearse.rb +1 -9
- data/context/getting-started.md +37 -29
- data/lib/presently/slide.rb +101 -50
- data/lib/presently/slide_renderer.rb +55 -16
- data/lib/presently/templates.rb +1 -1
- data/lib/presently/version.rb +1 -1
- data/public/_static/index.css +93 -42
- data/readme.md +9 -8
- data/releases.md +16 -0
- data/templates/code.xrb +5 -6
- data/templates/default.xrb +5 -3
- data/templates/diagram.xrb +8 -6
- data/templates/fill.xrb +9 -2
- data/templates/image.xrb +8 -5
- data/templates/section.xrb +11 -5
- data/templates/statement.xrb +5 -3
- data/templates/title.xrb +6 -9
- data/templates/two_column.xrb +23 -5
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d60b3fb59bbf07f0fdd4f3e1756998485049e2b55dc56225160e160af02346e
|
|
4
|
+
data.tar.gz: 4837aa9c8574f3b759a3199db53ad263d323df5cd6239df7292e8535ad83702c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b597972eabe2fec25758be28d3630db39756e93e7e2c24c43cfcdbaca3385741c1306748ada054cc7ea9d8bdbe9ba91f05217453cd5bfca3a1be5c2cc885b8d9
|
|
7
|
+
data.tar.gz: 122236fe265570f0be41163f662376b55df718d28ee52eada7e796c415ea52edd0f8f225649e1b9438d457cbb9281c2d06674274a80a33d6ec7b12ef304bed80
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/bake/presently/rehearse.rb
CHANGED
|
@@ -82,7 +82,7 @@ def rehearse(slides_root: "slides", speaker: nil, from: nil, to: nil, resume: fa
|
|
|
82
82
|
offset = prior_records.length
|
|
83
83
|
slides.each_with_index do |slide, index|
|
|
84
84
|
relative_path = slide.path
|
|
85
|
-
headline =
|
|
85
|
+
headline = slide.title
|
|
86
86
|
absolute = offset + index + 1
|
|
87
87
|
total = offset + slides.length
|
|
88
88
|
|
|
@@ -199,14 +199,6 @@ private
|
|
|
199
199
|
|
|
200
200
|
UPDATE_THRESHOLD = 1.0
|
|
201
201
|
|
|
202
|
-
def extract_headline(slide)
|
|
203
|
-
body = slide.content["body"]
|
|
204
|
-
return slide.title unless body && !body.empty?
|
|
205
|
-
|
|
206
|
-
first_line = body.to_commonmark.strip.lines.first.to_s.strip
|
|
207
|
-
first_line.empty? ? slide.title : first_line
|
|
208
|
-
end
|
|
209
|
-
|
|
210
202
|
def print_report(records, log_path)
|
|
211
203
|
planned_total = records.sum{|record| record[:planned]}
|
|
212
204
|
actual_total = records.sum{|record| record[:actual]}
|
data/context/getting-started.md
CHANGED
|
@@ -60,11 +60,7 @@ template: title
|
|
|
60
60
|
duration: 30
|
|
61
61
|
---
|
|
62
62
|
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
Welcome to My Talk
|
|
66
|
-
|
|
67
|
-
# Subtitle
|
|
63
|
+
# Welcome to My Talk
|
|
68
64
|
|
|
69
65
|
A presentation built with Presently
|
|
70
66
|
|
|
@@ -76,7 +72,7 @@ These are presenter notes — only visible in the presenter view.
|
|
|
76
72
|
Each slide has three parts:
|
|
77
73
|
|
|
78
74
|
1. **YAML frontmatter** between `---` markers at the top, specifying the template, duration, and other metadata.
|
|
79
|
-
2. **Content** with
|
|
75
|
+
2. **Content** with an optional H1 title and any template placeholders written as H2 sections.
|
|
80
76
|
3. **Presenter notes** after a `---` separator in the body (optional).
|
|
81
77
|
|
|
82
78
|
### Styling Slides
|
|
@@ -196,7 +192,7 @@ Templates define the visual layout of each slide. Select a template using the `t
|
|
|
196
192
|
|
|
197
193
|
### Default
|
|
198
194
|
|
|
199
|
-
A general-purpose content slide.
|
|
195
|
+
A general-purpose content slide. An H1 becomes the slide title and the remaining document becomes its body.
|
|
200
196
|
|
|
201
197
|
``` markdown
|
|
202
198
|
---
|
|
@@ -204,6 +200,8 @@ template: default
|
|
|
204
200
|
duration: 60
|
|
205
201
|
---
|
|
206
202
|
|
|
203
|
+
# Key points
|
|
204
|
+
|
|
207
205
|
- First point
|
|
208
206
|
- Second point
|
|
209
207
|
- Third point
|
|
@@ -211,7 +209,7 @@ duration: 60
|
|
|
211
209
|
|
|
212
210
|
### Title
|
|
213
211
|
|
|
214
|
-
A large title with a
|
|
212
|
+
A large title with a short body, centered on the slide.
|
|
215
213
|
|
|
216
214
|
``` markdown
|
|
217
215
|
---
|
|
@@ -219,18 +217,14 @@ template: title
|
|
|
219
217
|
duration: 30
|
|
220
218
|
---
|
|
221
219
|
|
|
222
|
-
# Title
|
|
223
|
-
|
|
224
|
-
My Presentation Title
|
|
225
|
-
|
|
226
|
-
# Subtitle
|
|
220
|
+
# My Presentation Title
|
|
227
221
|
|
|
228
222
|
A subtitle or tagline
|
|
229
223
|
```
|
|
230
224
|
|
|
231
225
|
### Section
|
|
232
226
|
|
|
233
|
-
A section divider slide with a large heading and accent background.
|
|
227
|
+
A section divider slide with a large heading, optional supporting body, and accent background.
|
|
234
228
|
|
|
235
229
|
``` markdown
|
|
236
230
|
---
|
|
@@ -238,9 +232,9 @@ template: section
|
|
|
238
232
|
duration: 15
|
|
239
233
|
---
|
|
240
234
|
|
|
241
|
-
#
|
|
235
|
+
# Part Two
|
|
242
236
|
|
|
243
|
-
|
|
237
|
+
Architecture and design
|
|
244
238
|
```
|
|
245
239
|
|
|
246
240
|
### Two Column
|
|
@@ -253,14 +247,18 @@ template: two_column
|
|
|
253
247
|
duration: 90
|
|
254
248
|
---
|
|
255
249
|
|
|
256
|
-
#
|
|
250
|
+
# Client and server responsibilities
|
|
251
|
+
|
|
252
|
+
The application is split across two cooperating environments.
|
|
253
|
+
|
|
254
|
+
## Left
|
|
257
255
|
|
|
258
256
|
**Server Side**
|
|
259
257
|
|
|
260
258
|
- Ruby + Lively
|
|
261
259
|
- WebSocket connections
|
|
262
260
|
|
|
263
|
-
|
|
261
|
+
## Right
|
|
264
262
|
|
|
265
263
|
**Client Side**
|
|
266
264
|
|
|
@@ -277,9 +275,10 @@ A syntax-highlighted code slide with optional focus regions for code walkthrough
|
|
|
277
275
|
template: code
|
|
278
276
|
duration: 60
|
|
279
277
|
focus: 2-8
|
|
280
|
-
title: Constructor
|
|
281
278
|
---
|
|
282
279
|
|
|
280
|
+
# Constructor
|
|
281
|
+
|
|
283
282
|
```ruby
|
|
284
283
|
class Presentation
|
|
285
284
|
def initialize
|
|
@@ -298,7 +297,7 @@ Create animated walkthroughs by using multiple slides with the same code but dif
|
|
|
298
297
|
|
|
299
298
|
### Statement
|
|
300
299
|
|
|
301
|
-
A prominent statement or quote, centered on the slide. Supports an optional
|
|
300
|
+
A prominent statement or quote, centered on the slide. Supports an optional `## Translation` placeholder.
|
|
302
301
|
|
|
303
302
|
``` markdown
|
|
304
303
|
---
|
|
@@ -308,14 +307,14 @@ duration: 30
|
|
|
308
307
|
|
|
309
308
|
The best way to predict the future is to create it.
|
|
310
309
|
|
|
311
|
-
|
|
310
|
+
## Translation
|
|
312
311
|
|
|
313
312
|
未来を予測する最善の方法は、それを創ることである。
|
|
314
313
|
```
|
|
315
314
|
|
|
316
315
|
### Translations
|
|
317
316
|
|
|
318
|
-
|
|
317
|
+
Templates can extract an optional `## Translation` section and position it independently from the main document. Every standard template displays it separately in a lighter style.
|
|
319
318
|
|
|
320
319
|
### Image
|
|
321
320
|
|
|
@@ -329,7 +328,7 @@ duration: 30
|
|
|
329
328
|
|
|
330
329
|

|
|
331
330
|
|
|
332
|
-
|
|
331
|
+
## Caption
|
|
333
332
|
|
|
334
333
|
System architecture overview
|
|
335
334
|
```
|
|
@@ -344,11 +343,7 @@ template: diagram
|
|
|
344
343
|
duration: 60
|
|
345
344
|
---
|
|
346
345
|
|
|
347
|
-
#
|
|
348
|
-
|
|
349
|
-
Request lifecycle
|
|
350
|
-
|
|
351
|
-
# Body
|
|
346
|
+
# Request lifecycle
|
|
352
347
|
|
|
353
348
|
<div style="display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 2em; width: 80%;">
|
|
354
349
|
<div>Browser</div>
|
|
@@ -420,7 +415,20 @@ service "presently" do
|
|
|
420
415
|
end
|
|
421
416
|
```
|
|
422
417
|
|
|
423
|
-
Templates receive a {ruby Presently::TemplateScope}
|
|
418
|
+
Templates receive a {ruby Presently::TemplateScope}. `self.slide_header` renders the semantic H1 title and optional section metadata, while `self.document` renders the remaining slide body. `self.extract(name)` removes an H2 placeholder with that exact heading text from the body and returns its rendered content. Extract placeholders before rendering the remaining document:
|
|
419
|
+
|
|
420
|
+
``` xrb
|
|
421
|
+
<?r translation = self.extract("Translation") ?>
|
|
422
|
+
#{self.slide_header}
|
|
423
|
+
<div class="slide-body">
|
|
424
|
+
#{self.document}
|
|
425
|
+
</div>
|
|
426
|
+
<?r if translation ?>
|
|
427
|
+
<div class="slide-translation">#{translation}</div>
|
|
428
|
+
<?r end ?>
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Extraction stops at the next heading of the same or a higher level, so lower-level headings remain inside the extracted fragment. Placeholder names are case-sensitive and must match the heading text exactly. Only placeholders requested by the template are removed; other headings remain ordinary document content.
|
|
424
432
|
|
|
425
433
|
## Customizing the Application
|
|
426
434
|
|
data/lib/presently/slide.rb
CHANGED
|
@@ -14,13 +14,13 @@ require_relative "stylesheet"
|
|
|
14
14
|
module Presently
|
|
15
15
|
# A single slide parsed from a Markdown file.
|
|
16
16
|
#
|
|
17
|
-
# Each slide has YAML front_matter for metadata (template, duration, focus),
|
|
18
|
-
#
|
|
17
|
+
# Each slide has YAML front_matter for metadata (template, duration, focus), a Markdown
|
|
18
|
+
# document, and optional presenter notes separated by `---`.
|
|
19
19
|
class Slide
|
|
20
20
|
# A fragment of a Markly AST document.
|
|
21
21
|
#
|
|
22
22
|
# Wraps a `Markly::Node` of type `:document` and provides rendering helpers.
|
|
23
|
-
# Used for both
|
|
23
|
+
# Used for both slide documents and presenter notes so callers can choose
|
|
24
24
|
# their output format without the parser pre-committing to one.
|
|
25
25
|
class Fragment
|
|
26
26
|
# Markly extensions enabled for all slide Markdown rendering.
|
|
@@ -35,6 +35,13 @@ module Presently
|
|
|
35
35
|
# @attribute [Markly::Node] The underlying AST document node.
|
|
36
36
|
attr :node
|
|
37
37
|
|
|
38
|
+
# Duplicate this fragment and its underlying AST.
|
|
39
|
+
# @returns [Fragment] An independently mutable copy.
|
|
40
|
+
def initialize_copy(other)
|
|
41
|
+
super
|
|
42
|
+
@node = other.node.dup
|
|
43
|
+
end
|
|
44
|
+
|
|
38
45
|
# Whether the fragment has no content.
|
|
39
46
|
# @returns [Boolean]
|
|
40
47
|
def empty?
|
|
@@ -47,6 +54,51 @@ module Presently
|
|
|
47
54
|
Markly::Renderer::HTML.new(flags: Markly::UNSAFE, extensions: EXTENSIONS).render(@node)
|
|
48
55
|
end
|
|
49
56
|
|
|
57
|
+
# Extract the content belonging to a named H2 placeholder.
|
|
58
|
+
#
|
|
59
|
+
# The matching heading itself is removed. Following nodes are extracted up
|
|
60
|
+
# to the next heading of the same or a higher level. Lower-level headings
|
|
61
|
+
# remain part of the extracted content.
|
|
62
|
+
# @parameter name [String] The heading name to extract.
|
|
63
|
+
# @returns [Fragment | Nil] The extracted content, or `nil` when not found.
|
|
64
|
+
def extract(name)
|
|
65
|
+
heading = find_heading(name)
|
|
66
|
+
return unless heading
|
|
67
|
+
|
|
68
|
+
level = heading.header_level
|
|
69
|
+
fragment = Markly::Node.new(:document)
|
|
70
|
+
node = heading.next
|
|
71
|
+
heading.delete
|
|
72
|
+
|
|
73
|
+
while node
|
|
74
|
+
next_node = node.next
|
|
75
|
+
break if node.type == :header && node.header_level <= level
|
|
76
|
+
|
|
77
|
+
fragment.append_child(node)
|
|
78
|
+
node = next_node
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
Fragment.new(fragment)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Extract the first heading at the given level, retaining the heading node.
|
|
85
|
+
# @parameter level [Integer] The Markdown heading level.
|
|
86
|
+
# @returns [Fragment | Nil] A fragment containing the extracted heading.
|
|
87
|
+
def extract_heading(level = 1)
|
|
88
|
+
heading = nil
|
|
89
|
+
@node.each do |node|
|
|
90
|
+
if node.type == :header && node.header_level == level
|
|
91
|
+
heading = node
|
|
92
|
+
break
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
return unless heading
|
|
96
|
+
|
|
97
|
+
fragment = Markly::Node.new(:document)
|
|
98
|
+
fragment.append_child(heading)
|
|
99
|
+
Fragment.new(fragment)
|
|
100
|
+
end
|
|
101
|
+
|
|
50
102
|
# Render the fragment back to CommonMark Markdown.
|
|
51
103
|
# @returns [String] The CommonMark source.
|
|
52
104
|
def to_commonmark
|
|
@@ -54,6 +106,27 @@ module Presently
|
|
|
54
106
|
end
|
|
55
107
|
|
|
56
108
|
alias to_s to_commonmark
|
|
109
|
+
|
|
110
|
+
# Return the plain text of the first heading at the given level.
|
|
111
|
+
# @parameter level [Integer] The Markdown heading level.
|
|
112
|
+
# @returns [String | Nil] The heading text, or `nil` when not found.
|
|
113
|
+
def heading_text(level = 1)
|
|
114
|
+
@node.each do |node|
|
|
115
|
+
if node.type == :header && node.header_level == level
|
|
116
|
+
return node.dup.extract_children.to_plaintext
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def find_heading(name)
|
|
124
|
+
@node.each do |node|
|
|
125
|
+
return node if node.type == :header && node.header_level == 2 && node.dup.extract_children.to_plaintext == name
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
nil
|
|
129
|
+
end
|
|
57
130
|
end
|
|
58
131
|
|
|
59
132
|
# Parses a Markdown slide file into structured data for {Slide}.
|
|
@@ -61,8 +134,17 @@ module Presently
|
|
|
61
134
|
# Handles YAML front_matter extraction, presenter note separation, and
|
|
62
135
|
# Markdown AST construction via Markly.
|
|
63
136
|
module Parser
|
|
137
|
+
FLAGS = Markly::UNSAFE | Markly::FRONT_MATTER | Markly::INLINE_CODE_INFO | Markly::HTML_BLOCK_BLANK_LINES
|
|
138
|
+
|
|
64
139
|
module_function
|
|
65
140
|
|
|
141
|
+
# Parse slide Markdown with the standard flags and extensions.
|
|
142
|
+
# @parameter source [String] The Markdown source.
|
|
143
|
+
# @returns [Markly::Node] The parsed document.
|
|
144
|
+
def parse(source)
|
|
145
|
+
Markly.parse(source, flags: FLAGS, extensions: Fragment::EXTENSIONS)
|
|
146
|
+
end
|
|
147
|
+
|
|
66
148
|
# Parse the file and return a {Slide}.
|
|
67
149
|
# @parameter presentation [Presentation] The presentation which owns the slide.
|
|
68
150
|
# @parameter path [String] The slide path relative to the presentation root.
|
|
@@ -72,7 +154,7 @@ module Presently
|
|
|
72
154
|
raw = File.read(source_path)
|
|
73
155
|
|
|
74
156
|
# Parse once, with native front matter support.
|
|
75
|
-
document =
|
|
157
|
+
document = parse(raw)
|
|
76
158
|
|
|
77
159
|
expand_includes!(document, File.dirname(source_path), presentation.root)
|
|
78
160
|
rewrite_image_urls!(document, source_path, presentation.root)
|
|
@@ -109,14 +191,12 @@ module Presently
|
|
|
109
191
|
script_node.delete
|
|
110
192
|
end
|
|
111
193
|
|
|
112
|
-
content = parse_sections(document)
|
|
113
194
|
notes = Fragment.new(notes_node)
|
|
114
195
|
else
|
|
115
|
-
content = parse_sections(document)
|
|
116
196
|
notes = nil
|
|
117
197
|
end
|
|
118
198
|
|
|
119
|
-
Slide.new(presentation, path, front_matter: front_matter,
|
|
199
|
+
Slide.new(presentation, path, front_matter: front_matter, document: Fragment.new(document), notes: notes, scripts: scripts)
|
|
120
200
|
end
|
|
121
201
|
|
|
122
202
|
# Extract reusable setup scripts from the expanded slide document.
|
|
@@ -169,7 +249,7 @@ module Presently
|
|
|
169
249
|
to_replace.each do |paragraph, relative_path|
|
|
170
250
|
included_path = File.expand_path(relative_path, base_dir)
|
|
171
251
|
included_raw = File.read(included_path)
|
|
172
|
-
included_document =
|
|
252
|
+
included_document = parse(included_raw)
|
|
173
253
|
|
|
174
254
|
# Strip front matter from included file if present.
|
|
175
255
|
front_matter_node = included_document.first_child
|
|
@@ -211,31 +291,6 @@ module Presently
|
|
|
211
291
|
end
|
|
212
292
|
end
|
|
213
293
|
|
|
214
|
-
# Parse a Markly document into content sections based on top-level headings.
|
|
215
|
-
#
|
|
216
|
-
# Each heading becomes a named key; content before the first heading is
|
|
217
|
-
# collected under `"body"`. Each value is a {Fragment} wrapping a document node.
|
|
218
|
-
# @parameter document [Markly::Node] The document to parse.
|
|
219
|
-
# @returns [Hash(String, Fragment)] Sections keyed by heading name.
|
|
220
|
-
def parse_sections(document)
|
|
221
|
-
sections = {}
|
|
222
|
-
current_key = "body"
|
|
223
|
-
current_node = Markly::Node.new(:document)
|
|
224
|
-
|
|
225
|
-
document.each do |node|
|
|
226
|
-
if node.type == :header
|
|
227
|
-
sections[current_key] = Fragment.new(current_node) unless current_node.first_child.nil?
|
|
228
|
-
current_key = node.to_plaintext.strip.downcase.gsub(/\s+/, "_")
|
|
229
|
-
current_node = Markly::Node.new(:document)
|
|
230
|
-
else
|
|
231
|
-
current_node.append_child(node.dup)
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
sections[current_key] = Fragment.new(current_node) unless current_node.first_child.nil?
|
|
236
|
-
|
|
237
|
-
sections
|
|
238
|
-
end
|
|
239
294
|
end
|
|
240
295
|
|
|
241
296
|
# Load and parse a slide from a Markdown file.
|
|
@@ -250,18 +305,16 @@ module Presently
|
|
|
250
305
|
# @parameter presentation [Presentation] The presentation which owns the slide.
|
|
251
306
|
# @parameter path [String] The slide path relative to the presentation root.
|
|
252
307
|
# @parameter front_matter [Hash | Nil] The parsed YAML front_matter.
|
|
253
|
-
# @parameter
|
|
308
|
+
# @parameter document [Fragment | Nil] The complete slide document.
|
|
254
309
|
# @parameter notes [Fragment | Nil] The presenter notes as a Markly AST fragment.
|
|
255
310
|
# @parameter scripts [Array(String)] JavaScript sources to execute after the slide renders.
|
|
256
|
-
|
|
257
|
-
def initialize(presentation, path, front_matter: nil, content: {}, notes: nil, scripts: [], script: nil)
|
|
311
|
+
def initialize(presentation, path, front_matter: nil, document: nil, notes: nil, scripts: [])
|
|
258
312
|
@presentation = presentation
|
|
259
313
|
@path = path
|
|
260
314
|
@front_matter = front_matter
|
|
261
|
-
@
|
|
315
|
+
@document = document || Fragment.new(Markly::Node.new(:document))
|
|
262
316
|
@notes = notes
|
|
263
317
|
@scripts = scripts.dup
|
|
264
|
-
@scripts << script if script
|
|
265
318
|
end
|
|
266
319
|
|
|
267
320
|
# @attribute [Presentation] The presentation which owns the slide.
|
|
@@ -279,8 +332,8 @@ module Presently
|
|
|
279
332
|
# @attribute [Hash | Nil] The parsed YAML front_matter.
|
|
280
333
|
attr :front_matter
|
|
281
334
|
|
|
282
|
-
# @attribute [
|
|
283
|
-
attr :
|
|
335
|
+
# @attribute [Fragment] The complete slide document.
|
|
336
|
+
attr :document
|
|
284
337
|
|
|
285
338
|
# @attribute [Fragment | Nil] The presenter notes as a Markly AST fragment.
|
|
286
339
|
attr :notes
|
|
@@ -288,14 +341,6 @@ module Presently
|
|
|
288
341
|
# @attribute [Array(String)] JavaScript sources to execute after the slide renders on the display.
|
|
289
342
|
attr :scripts
|
|
290
343
|
|
|
291
|
-
# All JavaScript sources combined into a single string.
|
|
292
|
-
# @returns [String | Nil] The combined JavaScript, or `nil` when the slide has no scripts.
|
|
293
|
-
def script
|
|
294
|
-
return if @scripts.empty?
|
|
295
|
-
|
|
296
|
-
@scripts.join("\n\n")
|
|
297
|
-
end
|
|
298
|
-
|
|
299
344
|
# The template to use for rendering this slide.
|
|
300
345
|
# @returns [String] The template name from front_matter, or `"default"`.
|
|
301
346
|
def template
|
|
@@ -309,9 +354,15 @@ module Presently
|
|
|
309
354
|
end
|
|
310
355
|
|
|
311
356
|
# The title of this slide.
|
|
312
|
-
# @returns [String] The
|
|
357
|
+
# @returns [String] The H1 text, front matter title, or filename without extension.
|
|
313
358
|
def title
|
|
314
|
-
@front_matter&.fetch("title",
|
|
359
|
+
@document.heading_text(1) || @front_matter&.fetch("title", nil) || File.basename(@path, ".md")
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# The section name for this slide.
|
|
363
|
+
# @returns [String | Nil] The section from front matter, or `nil` when it was not specified.
|
|
364
|
+
def section
|
|
365
|
+
@front_matter&.fetch("section", nil)
|
|
315
366
|
end
|
|
316
367
|
|
|
317
368
|
# Whether this slide should be skipped in the presentation.
|
|
@@ -64,36 +64,75 @@ module Presently
|
|
|
64
64
|
|
|
65
65
|
# Provides the scope for XRB template rendering.
|
|
66
66
|
#
|
|
67
|
-
# Templates
|
|
67
|
+
# Templates selectively extract placeholders with `self.extract(name)` and render the remainder
|
|
68
|
+
# with `self.document`.
|
|
68
69
|
class TemplateScope
|
|
69
70
|
# Initialize a new template scope for the given slide.
|
|
70
71
|
# @parameter slide [Slide] The slide being rendered.
|
|
71
72
|
def initialize(slide)
|
|
72
73
|
@slide = slide
|
|
74
|
+
@document = slide.document.dup
|
|
75
|
+
@heading = @document.extract_heading(1)
|
|
76
|
+
@extracted = {}
|
|
73
77
|
end
|
|
74
78
|
|
|
75
79
|
# @attribute [Slide] The slide being rendered.
|
|
76
80
|
attr :slide
|
|
77
81
|
|
|
78
|
-
#
|
|
79
|
-
# @returns [
|
|
80
|
-
def
|
|
81
|
-
@
|
|
82
|
+
# Render the slide body after title and placeholder extraction.
|
|
83
|
+
# @returns [XRB::MarkupString] The remaining document as HTML.
|
|
84
|
+
def document
|
|
85
|
+
markup(@document)
|
|
82
86
|
end
|
|
83
87
|
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
# Extract and render a named H2 placeholder from this render's document.
|
|
89
|
+
#
|
|
90
|
+
# Extraction is cached so a template can reference a placeholder more than
|
|
91
|
+
# once without mutating the document repeatedly.
|
|
92
|
+
# @parameter name [String] The heading name to extract.
|
|
93
|
+
# @returns [XRB::MarkupString | Nil] The extracted HTML, or `nil` when absent.
|
|
94
|
+
def extract(name)
|
|
95
|
+
fragment = extract_fragment(name)
|
|
96
|
+
return unless fragment && !fragment.empty?
|
|
97
|
+
|
|
98
|
+
markup(fragment)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Render the slide header using semantic markup.
|
|
102
|
+
# @returns [XRB::MarkupString] The rendered header, or an empty string when no metadata is present.
|
|
103
|
+
def slide_header
|
|
104
|
+
section = @slide.section
|
|
105
|
+
|
|
106
|
+
return XRB::MarkupString.raw("") unless present?(section) || @heading
|
|
107
|
+
|
|
108
|
+
builder = XRB::Builder.new
|
|
109
|
+
builder.tag(:header, class: "slide-header") do
|
|
110
|
+
if present?(section)
|
|
111
|
+
builder.tag(:div, class: "slide-section-heading") do
|
|
112
|
+
builder.text(section.to_s)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
builder.raw(@heading.to_html) if @heading
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
XRB::MarkupString.raw(builder.to_s)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def extract_fragment(name)
|
|
125
|
+
return @extracted[name] if @extracted.key?(name)
|
|
126
|
+
|
|
127
|
+
@extracted[name] = @document.extract(name)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def markup(fragment)
|
|
131
|
+
XRB::MarkupString.raw(fragment&.to_html || "")
|
|
90
132
|
end
|
|
91
133
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# @returns [XRB::MarkupString] The rendered HTML content, safe for embedding.
|
|
95
|
-
def section(name)
|
|
96
|
-
XRB::MarkupString.raw(@slide.content[name]&.to_html || "")
|
|
134
|
+
def present?(value)
|
|
135
|
+
value && !value.to_s.empty?
|
|
97
136
|
end
|
|
98
137
|
end
|
|
99
138
|
end
|
data/lib/presently/templates.rb
CHANGED
|
@@ -24,7 +24,7 @@ module Presently
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# Initialize with a fully-resolved root list.
|
|
27
|
-
# Prefer {.
|
|
27
|
+
# Prefer {.for} for normal use; use this when you already have the complete list.
|
|
28
28
|
# @parameter roots [Array(String)] The complete ordered list of directories to search.
|
|
29
29
|
def initialize(roots = [DEFAULT_ROOT])
|
|
30
30
|
@roots = roots
|
data/lib/presently/version.rb
CHANGED
data/public/_static/index.css
CHANGED
|
@@ -51,7 +51,6 @@ html, body {
|
|
|
51
51
|
|
|
52
52
|
.slide {
|
|
53
53
|
--slide-font-size: 2.2cqi;
|
|
54
|
-
--slide-font-body: 1em;
|
|
55
54
|
--slide-font-title: 1.8em;
|
|
56
55
|
--slide-font-heading: 1.6em;
|
|
57
56
|
--slide-font-statement: 1.5em;
|
|
@@ -89,10 +88,33 @@ html, body {
|
|
|
89
88
|
line-height: 1.5;
|
|
90
89
|
}
|
|
91
90
|
|
|
91
|
+
.slide-header {
|
|
92
|
+
flex: none;
|
|
93
|
+
width: 100%;
|
|
94
|
+
padding: 0;
|
|
95
|
+
background: none;
|
|
96
|
+
border: 0;
|
|
97
|
+
margin-bottom: var(--slide-gap);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.slide-section-heading {
|
|
101
|
+
margin-bottom: calc(var(--slide-gap) * 0.25);
|
|
102
|
+
color: var(--accent-light);
|
|
103
|
+
font-family: var(--font-mono);
|
|
104
|
+
font-size: 0.45em;
|
|
105
|
+
font-weight: 700;
|
|
106
|
+
letter-spacing: 0.08em;
|
|
107
|
+
text-transform: uppercase;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.slide-header > h1 {
|
|
111
|
+
margin: 0;
|
|
112
|
+
font-size: var(--slide-font-heading);
|
|
113
|
+
line-height: 1.1;
|
|
114
|
+
}
|
|
115
|
+
|
|
92
116
|
/* Default template */
|
|
93
117
|
.default-template .slide-body {
|
|
94
|
-
font-size: var(--slide-font-body);
|
|
95
|
-
line-height: 1.5;
|
|
96
118
|
width: 100%;
|
|
97
119
|
}
|
|
98
120
|
|
|
@@ -118,22 +140,19 @@ html, body {
|
|
|
118
140
|
text-align: center;
|
|
119
141
|
}
|
|
120
142
|
|
|
121
|
-
.title-template .slide-
|
|
122
|
-
font-size: var(--slide-font-title);
|
|
123
|
-
font-weight: 700;
|
|
143
|
+
.title-template .slide-header {
|
|
124
144
|
margin-bottom: calc(var(--slide-gap) * 0.75);
|
|
125
145
|
}
|
|
126
146
|
|
|
127
|
-
.title-template .slide-
|
|
128
|
-
|
|
147
|
+
.title-template .slide-header > h1 {
|
|
148
|
+
font-size: var(--slide-font-title);
|
|
129
149
|
}
|
|
130
150
|
|
|
131
|
-
.title-template .slide-
|
|
132
|
-
font-size: var(--slide-font-body);
|
|
151
|
+
.title-template .slide-body {
|
|
133
152
|
opacity: 0.7;
|
|
134
153
|
}
|
|
135
154
|
|
|
136
|
-
.title-template .slide-
|
|
155
|
+
.title-template .slide-body p {
|
|
137
156
|
margin: 0;
|
|
138
157
|
}
|
|
139
158
|
|
|
@@ -146,22 +165,49 @@ html, body {
|
|
|
146
165
|
text-align: center;
|
|
147
166
|
}
|
|
148
167
|
|
|
149
|
-
.section-template .slide-
|
|
150
|
-
|
|
168
|
+
.section-template .slide-header {
|
|
169
|
+
margin-bottom: 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.section-template .slide-header > h1 {
|
|
151
173
|
font-weight: 700;
|
|
152
174
|
}
|
|
153
175
|
|
|
154
|
-
.section-template .slide-
|
|
176
|
+
.section-template .slide-body {
|
|
177
|
+
margin-top: calc(var(--slide-gap) * 0.75);
|
|
178
|
+
opacity: 0.7;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.section-template .slide-body p {
|
|
155
182
|
margin: 0;
|
|
156
183
|
}
|
|
157
184
|
|
|
158
185
|
/* Two column template */
|
|
159
186
|
.two-column-template {
|
|
160
|
-
flex-direction: row;
|
|
161
|
-
gap: calc(var(--slide-gap) * 1.5);
|
|
162
187
|
padding: calc(var(--slide-gutter) * 0.75) var(--slide-gutter);
|
|
163
188
|
}
|
|
164
189
|
|
|
190
|
+
.two-column-template .slide-columns {
|
|
191
|
+
display: flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
flex: 1;
|
|
194
|
+
width: 100%;
|
|
195
|
+
gap: calc(var(--slide-gap) * 1.5);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.two-column-template > .slide-body {
|
|
199
|
+
width: 100%;
|
|
200
|
+
margin-bottom: var(--slide-gap);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.two-column-template > .slide-body > :first-child {
|
|
204
|
+
margin-top: 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.two-column-template > .slide-body > :last-child {
|
|
208
|
+
margin-bottom: 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
165
211
|
.two-column-template .column {
|
|
166
212
|
flex: 1;
|
|
167
213
|
min-width: 0;
|
|
@@ -178,23 +224,10 @@ html, body {
|
|
|
178
224
|
}
|
|
179
225
|
|
|
180
226
|
/* Diagram template */
|
|
181
|
-
.diagram-template {
|
|
182
|
-
padding: var(--slide-gutter);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.diagram-template .slide-title {
|
|
186
|
-
flex: none;
|
|
187
|
-
width: 100%;
|
|
188
|
-
margin-bottom: var(--slide-gap);
|
|
189
|
-
font-size: var(--slide-font-body);
|
|
190
|
-
font-weight: 700;
|
|
227
|
+
.diagram-template .slide-header {
|
|
191
228
|
text-align: center;
|
|
192
229
|
}
|
|
193
230
|
|
|
194
|
-
.diagram-template .slide-title p {
|
|
195
|
-
margin: 0;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
231
|
.diagram-template .slide-body {
|
|
199
232
|
display: grid;
|
|
200
233
|
flex: 1;
|
|
@@ -356,6 +389,10 @@ pre > syntax-code {
|
|
|
356
389
|
width: 100%;
|
|
357
390
|
}
|
|
358
391
|
|
|
392
|
+
.statement-template .slide-header {
|
|
393
|
+
margin-bottom: 0;
|
|
394
|
+
}
|
|
395
|
+
|
|
359
396
|
.statement-template .slide-statement mermaid-diagram {
|
|
360
397
|
font-weight: normal;
|
|
361
398
|
}
|
|
@@ -386,11 +423,17 @@ mermaid-diagram {
|
|
|
386
423
|
/* Fullscreen template */
|
|
387
424
|
.fill-template {
|
|
388
425
|
padding: 0;
|
|
389
|
-
width: 100%;
|
|
390
|
-
height: 100%;
|
|
391
426
|
}
|
|
392
427
|
|
|
393
|
-
.fill-template .slide-
|
|
428
|
+
.fill-template .slide-header {
|
|
429
|
+
position: absolute;
|
|
430
|
+
top: var(--slide-gutter);
|
|
431
|
+
left: var(--slide-gutter);
|
|
432
|
+
z-index: 1;
|
|
433
|
+
width: calc(100% - var(--slide-gutter) * 2);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.fill-template .slide-body {
|
|
394
437
|
width: 100%;
|
|
395
438
|
height: 100%;
|
|
396
439
|
display: flex;
|
|
@@ -398,10 +441,19 @@ mermaid-diagram {
|
|
|
398
441
|
justify-content: center;
|
|
399
442
|
}
|
|
400
443
|
|
|
401
|
-
.fill-template .slide-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
444
|
+
.fill-template .slide-translation {
|
|
445
|
+
position: absolute;
|
|
446
|
+
bottom: var(--slide-gutter);
|
|
447
|
+
left: var(--slide-gutter);
|
|
448
|
+
z-index: 1;
|
|
449
|
+
width: calc(100% - var(--slide-gutter) * 2);
|
|
450
|
+
text-align: center;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.fill-template .slide-body img,
|
|
454
|
+
.fill-template .slide-body iframe,
|
|
455
|
+
.fill-template .slide-body svg,
|
|
456
|
+
.fill-template .slide-body video {
|
|
405
457
|
width: 100%;
|
|
406
458
|
height: 100%;
|
|
407
459
|
object-fit: contain;
|
|
@@ -414,14 +466,12 @@ mermaid-diagram {
|
|
|
414
466
|
align-items: stretch;
|
|
415
467
|
}
|
|
416
468
|
|
|
417
|
-
.code-template .
|
|
418
|
-
font-size: var(--slide-font-secondary);
|
|
419
|
-
font-weight: 600;
|
|
469
|
+
.code-template .slide-header {
|
|
420
470
|
margin-bottom: calc(var(--slide-gap) * 0.5);
|
|
421
471
|
}
|
|
422
472
|
|
|
423
|
-
.code-template .
|
|
424
|
-
|
|
473
|
+
.code-template .slide-header > h1 {
|
|
474
|
+
font-size: var(--slide-font-secondary);
|
|
425
475
|
}
|
|
426
476
|
|
|
427
477
|
.code-viewport {
|
|
@@ -760,6 +810,7 @@ body.playback[data-controls="false"] .playback-start-screen {
|
|
|
760
810
|
display: flex;
|
|
761
811
|
align-items: center;
|
|
762
812
|
gap: 1rem;
|
|
813
|
+
min-width: 0;
|
|
763
814
|
}
|
|
764
815
|
|
|
765
816
|
.toolbar > button,
|
data/readme.md
CHANGED
|
@@ -67,6 +67,15 @@ The task records the presentation at 1920×1080 and 30 frames per second by defa
|
|
|
67
67
|
|
|
68
68
|
Please see the [project releases](https://socketry.github.io/presently/releases/index) for all releases.
|
|
69
69
|
|
|
70
|
+
### v0.21.0
|
|
71
|
+
|
|
72
|
+
- [Semantic Slides](https://socketry.github.io/presently/releases/index#semantic-slides)
|
|
73
|
+
- [Slide Scripts](https://socketry.github.io/presently/releases/index#slide-scripts)
|
|
74
|
+
|
|
75
|
+
### v0.20.1
|
|
76
|
+
|
|
77
|
+
- Keep presenter toolbars within the viewport when slide paths are long.
|
|
78
|
+
|
|
70
79
|
### v0.19.0
|
|
71
80
|
|
|
72
81
|
- Keep Anime.js resources created by delayed slide callbacks bound to the slide lifecycle.
|
|
@@ -112,14 +121,6 @@ q
|
|
|
112
121
|
|
|
113
122
|
- Change zoom to 50% on slide preview (presenter display).
|
|
114
123
|
|
|
115
|
-
### v0.12.0
|
|
116
|
-
|
|
117
|
-
- Add support for translation to code slide.
|
|
118
|
-
|
|
119
|
-
### v0.11.0
|
|
120
|
-
|
|
121
|
-
- Add `Slide#element` and `SlideContext#element` getters — expose the slide body DOM element directly for cases where `find()` is not sufficient, such as measuring dimensions, attaching event listeners, or integrating third-party libraries.
|
|
122
|
-
|
|
123
124
|
## See Also
|
|
124
125
|
|
|
125
126
|
- [lively](https://github.com/socketry/lively) — The real-time application framework that powers Presently.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.21.0
|
|
4
|
+
|
|
5
|
+
### Semantic Slides
|
|
6
|
+
|
|
7
|
+
Slides now use semantic Markdown instead of treating every heading as a named section. Use a single H1 for the slide title, write the body directly after it, and replace structural sections such as `# Title`, `# Body`, `# Subtitle`, and `# Heading` with ordinary title and body content. For example, replace `# Title` followed by the title text with `# The actual title`, and move any display title from front matter into the H1.
|
|
8
|
+
|
|
9
|
+
Template-specific placeholders are exact, case-sensitive H2 headings: `## Translation`, `## Caption`, `## Left`, and `## Right`. Custom templates should replace `self.section(name)` with `self.extract(name)` before rendering `self.document`; extraction removes that placeholder so it can be positioned independently.
|
|
10
|
+
|
|
11
|
+
### Slide Scripts
|
|
12
|
+
|
|
13
|
+
Slide scripts are now exposed only through `Slide#scripts`. Replace the compatibility `Slide#script` accessor and `script:` initializer argument with the ordered `scripts` array.
|
|
14
|
+
|
|
15
|
+
## v0.20.1
|
|
16
|
+
|
|
17
|
+
- Keep presenter toolbars within the viewport when slide paths are long.
|
|
18
|
+
|
|
3
19
|
## v0.19.0
|
|
4
20
|
|
|
5
21
|
- Keep Anime.js resources created by delayed slide callbacks bound to the slide lifecycle.
|
data/templates/code.xrb
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
+
<?r translation = self.extract("Translation") ?>
|
|
1
2
|
<div class="slide-inner code-template">
|
|
2
|
-
|
|
3
|
-
<div class="code-title"><p>#{title}</p></div>
|
|
4
|
-
<?r end ?>
|
|
3
|
+
#{self.slide_header}
|
|
5
4
|
<div class="code-viewport" data-focus-start="#{self.slide.focus&.first}" data-focus-end="#{self.slide.focus&.last}">
|
|
6
5
|
<div class="code-scroll">
|
|
7
|
-
#{self.
|
|
6
|
+
#{self.document}
|
|
8
7
|
</div>
|
|
9
8
|
<div class="code-dim code-dim-top"></div>
|
|
10
9
|
<div class="code-dim code-dim-bottom"></div>
|
|
11
10
|
</div>
|
|
12
|
-
<?r if
|
|
11
|
+
<?r if translation ?>
|
|
13
12
|
<div class="slide-translation">
|
|
14
|
-
#{
|
|
13
|
+
#{translation}
|
|
15
14
|
</div>
|
|
16
15
|
<?r end ?>
|
|
17
16
|
</div>
|
data/templates/default.xrb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
<?r translation = self.extract("Translation") ?>
|
|
1
2
|
<div class="slide-inner default-template">
|
|
3
|
+
#{self.slide_header}
|
|
2
4
|
<div class="slide-body">
|
|
3
|
-
#{self.
|
|
5
|
+
#{self.document}
|
|
4
6
|
</div>
|
|
5
|
-
<?r if
|
|
7
|
+
<?r if translation ?>
|
|
6
8
|
<div class="slide-translation">
|
|
7
|
-
#{
|
|
9
|
+
#{translation}
|
|
8
10
|
</div>
|
|
9
11
|
<?r end ?>
|
|
10
12
|
</div>
|
data/templates/diagram.xrb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
<?r translation = self.extract("Translation") ?>
|
|
1
2
|
<div class="slide-inner diagram-template">
|
|
2
|
-
|
|
3
|
-
<div class="slide-title">
|
|
4
|
-
#{self.section("title")}
|
|
5
|
-
</div>
|
|
6
|
-
<?r end ?>
|
|
3
|
+
#{self.slide_header}
|
|
7
4
|
<div class="slide-body">
|
|
8
|
-
#{self.
|
|
5
|
+
#{self.document}
|
|
9
6
|
</div>
|
|
7
|
+
<?r if translation ?>
|
|
8
|
+
<div class="slide-translation">
|
|
9
|
+
#{translation}
|
|
10
|
+
</div>
|
|
11
|
+
<?r end ?>
|
|
10
12
|
</div>
|
data/templates/fill.xrb
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
<?r translation = self.extract("Translation") ?>
|
|
1
2
|
<div class="slide-inner fill-template">
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
#{self.slide_header}
|
|
4
|
+
<div class="slide-body">
|
|
5
|
+
#{self.document}
|
|
4
6
|
</div>
|
|
7
|
+
<?r if translation ?>
|
|
8
|
+
<div class="slide-translation">
|
|
9
|
+
#{translation}
|
|
10
|
+
</div>
|
|
11
|
+
<?r end ?>
|
|
5
12
|
</div>
|
data/templates/image.xrb
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
<?r caption = self.extract("Caption") ?>
|
|
2
|
+
<?r translation = self.extract("Translation") ?>
|
|
1
3
|
<div class="slide-inner image-template">
|
|
4
|
+
#{self.slide_header}
|
|
2
5
|
<div class="slide-image">
|
|
3
|
-
#{self.
|
|
6
|
+
#{self.document}
|
|
4
7
|
</div>
|
|
5
|
-
<?r if
|
|
6
|
-
<div class="slide-caption">#{
|
|
8
|
+
<?r if caption ?>
|
|
9
|
+
<div class="slide-caption">#{caption}</div>
|
|
7
10
|
<?r end ?>
|
|
8
|
-
<?r if
|
|
11
|
+
<?r if translation ?>
|
|
9
12
|
<div class="slide-translation">
|
|
10
|
-
#{
|
|
13
|
+
#{translation}
|
|
11
14
|
</div>
|
|
12
15
|
<?r end ?>
|
|
13
16
|
</div>
|
data/templates/section.xrb
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
<?r translation = self.extract("Translation") ?>
|
|
2
|
+
<?r header = self.slide_header ?>
|
|
3
|
+
<?r body = self.document ?>
|
|
1
4
|
<div class="slide-inner section-template">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
#{header}
|
|
6
|
+
<?r unless body.empty? ?>
|
|
7
|
+
<div class="slide-body">
|
|
8
|
+
#{body}
|
|
9
|
+
</div>
|
|
10
|
+
<?r end ?>
|
|
11
|
+
<?r if translation ?>
|
|
6
12
|
<div class="slide-translation">
|
|
7
|
-
#{
|
|
13
|
+
#{translation}
|
|
8
14
|
</div>
|
|
9
15
|
<?r end ?>
|
|
10
16
|
</div>
|
data/templates/statement.xrb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
<?r translation = self.extract("Translation") ?>
|
|
1
2
|
<div class="slide-inner statement-template">
|
|
3
|
+
#{self.slide_header}
|
|
2
4
|
<div class="slide-statement">
|
|
3
|
-
#{self.
|
|
5
|
+
#{self.document}
|
|
4
6
|
</div>
|
|
5
|
-
<?r if
|
|
7
|
+
<?r if translation ?>
|
|
6
8
|
<div class="slide-translation">
|
|
7
|
-
#{
|
|
9
|
+
#{translation}
|
|
8
10
|
</div>
|
|
9
11
|
<?r end ?>
|
|
10
12
|
</div>
|
data/templates/title.xrb
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
+
<?r translation = self.extract("Translation") ?>
|
|
1
2
|
<div class="slide-inner title-template">
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
#{self.slide_header}
|
|
4
|
+
<div class="slide-body">
|
|
5
|
+
#{self.document}
|
|
4
6
|
</div>
|
|
5
|
-
<?r if
|
|
6
|
-
<div class="slide-subtitle">
|
|
7
|
-
#{self.section("subtitle")}
|
|
8
|
-
</div>
|
|
9
|
-
<?r end ?>
|
|
10
|
-
<?r if self.section?("translation") ?>
|
|
7
|
+
<?r if translation ?>
|
|
11
8
|
<div class="slide-translation">
|
|
12
|
-
#{
|
|
9
|
+
#{translation}
|
|
13
10
|
</div>
|
|
14
11
|
<?r end ?>
|
|
15
12
|
</div>
|
data/templates/two_column.xrb
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
|
+
<?r left = self.extract("Left") ?>
|
|
2
|
+
<?r right = self.extract("Right") ?>
|
|
3
|
+
<?r translation = self.extract("Translation") ?>
|
|
4
|
+
<?r header = self.slide_header ?>
|
|
5
|
+
<?r body = self.document ?>
|
|
1
6
|
<div class="slide-inner two-column-template">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
#{header}
|
|
8
|
+
<?r unless body.empty? ?>
|
|
9
|
+
<div class="slide-body">
|
|
10
|
+
#{body}
|
|
11
|
+
</div>
|
|
12
|
+
<?r end ?>
|
|
13
|
+
<div class="slide-columns">
|
|
14
|
+
<div class="column left-column">
|
|
15
|
+
#{left}
|
|
16
|
+
</div>
|
|
17
|
+
<div class="column right-column">
|
|
18
|
+
#{right}
|
|
19
|
+
</div>
|
|
7
20
|
</div>
|
|
21
|
+
<?r if translation ?>
|
|
22
|
+
<div class="slide-translation">
|
|
23
|
+
#{translation}
|
|
24
|
+
</div>
|
|
25
|
+
<?r end ?>
|
|
8
26
|
</div>
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: presently
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.21.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -286,7 +286,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
286
286
|
- !ruby/object:Gem::Version
|
|
287
287
|
version: '0'
|
|
288
288
|
requirements: []
|
|
289
|
-
rubygems_version: 4.0.
|
|
289
|
+
rubygems_version: 4.0.16
|
|
290
290
|
specification_version: 4
|
|
291
291
|
summary: A web-based presentation tool built with Lively.
|
|
292
292
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|