presently 0.3.0 → 0.4.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/slides.rb +24 -0
- data/lib/presently/presenter_view.rb +2 -2
- data/lib/presently/slide.rb +93 -64
- data/lib/presently/slide_renderer.rb +3 -2
- data/lib/presently/version.rb +1 -1
- data/readme.md +9 -2
- data/releases.md +5 -0
- data.tar.gz.sig +0 -0
- metadata +1 -1
- 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: 3991c95331fdcabd96b64dd78833690a52468f2a1e570eddf3234fd5e9bfa7ac
|
|
4
|
+
data.tar.gz: 816e1de43c33929e12424659455e116891294d79dcc3d2ea41cc8df9b13317dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bc5afae0aaeb288b51b4ccd6c8e66a562acd44898a49a3777299865ccff1a400258e88dd1fa3950071db8949b107fa076eac2d5042ccdd847e11d8d395801892
|
|
7
|
+
data.tar.gz: e0b1a9f6e5743beecdb4745469335c5ed8ad154e74bd5fdf2c745f54ce83532c1d81af3014c67ff8c9480e878b09a21e0df50cf73805e2a5676c0516a8ef807a
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/bake/presently/slides.rb
CHANGED
|
@@ -9,6 +9,30 @@ def initialize(context)
|
|
|
9
9
|
require "fileutils"
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
# Extract all presenter notes and print them to stdout.
|
|
13
|
+
#
|
|
14
|
+
# Loads every slide in the slides directory using the Presently API and
|
|
15
|
+
# prints each slide's presenter notes to stdout. Each slide's notes are
|
|
16
|
+
# preceded by a `##` heading with the slide file path.
|
|
17
|
+
#
|
|
18
|
+
# @parameter slides_root [String] The slides directory. Default: `slides`.
|
|
19
|
+
def notes(slides_root: "slides")
|
|
20
|
+
require "presently"
|
|
21
|
+
|
|
22
|
+
presentation = Presently::Presentation.load(slides_root)
|
|
23
|
+
|
|
24
|
+
presentation.slides.each do |slide|
|
|
25
|
+
next unless slide.notes
|
|
26
|
+
|
|
27
|
+
puts "## #{slide.path}"
|
|
28
|
+
puts
|
|
29
|
+
puts slide.notes.to_commonmark
|
|
30
|
+
puts
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
return nil
|
|
34
|
+
end
|
|
35
|
+
|
|
12
36
|
# Renumber slide files sequentially with a consistent step size.
|
|
13
37
|
#
|
|
14
38
|
# Renames all `.md` files in the slides directory to have sequential
|
|
@@ -270,8 +270,8 @@ module Presently
|
|
|
270
270
|
builder.tag(:div, class: "notes") do
|
|
271
271
|
builder.tag(:h3){builder.text("Notes")}
|
|
272
272
|
builder.tag(:div, class: "notes-content") do
|
|
273
|
-
if slide&.notes
|
|
274
|
-
builder.raw(
|
|
273
|
+
if notes = slide&.notes
|
|
274
|
+
builder.raw(notes.to_html)
|
|
275
275
|
else
|
|
276
276
|
builder.tag(:p, class: "no-notes"){builder.text("No presenter notes for this slide.")}
|
|
277
277
|
end
|
data/lib/presently/slide.rb
CHANGED
|
@@ -14,116 +14,145 @@ module Presently
|
|
|
14
14
|
# Each slide has YAML front_matter for metadata (template, duration, focus), content sections
|
|
15
15
|
# split by Markdown headings, and optional presenter notes separated by `---`.
|
|
16
16
|
class Slide
|
|
17
|
+
# A fragment of a Markly AST document.
|
|
18
|
+
#
|
|
19
|
+
# Wraps a `Markly::Node` of type `:document` and provides rendering helpers.
|
|
20
|
+
# Used for both content sections and presenter notes so callers can choose
|
|
21
|
+
# their output format without the parser pre-committing to one.
|
|
22
|
+
class Fragment
|
|
23
|
+
# Markly extensions enabled for all slide Markdown rendering.
|
|
24
|
+
EXTENSIONS = [:table, :tasklist, :strikethrough, :autolink]
|
|
25
|
+
|
|
26
|
+
# Initialize a fragment from a Markly document node.
|
|
27
|
+
# @parameter node [Markly::Node] A document node containing the fragment content.
|
|
28
|
+
def initialize(node)
|
|
29
|
+
@node = node
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @attribute [Markly::Node] The underlying AST document node.
|
|
33
|
+
attr :node
|
|
34
|
+
|
|
35
|
+
# Whether the fragment has no content.
|
|
36
|
+
# @returns [Boolean]
|
|
37
|
+
def empty?
|
|
38
|
+
@node.first_child.nil?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Render the fragment to HTML using the Presently renderer.
|
|
42
|
+
#
|
|
43
|
+
# Mermaid fenced code blocks are rendered as `<mermaid-diagram>` elements.
|
|
44
|
+
# @returns [String] The rendered HTML.
|
|
45
|
+
def to_html
|
|
46
|
+
Renderer.new(flags: Markly::UNSAFE, extensions: EXTENSIONS).render(@node)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Render the fragment back to CommonMark Markdown.
|
|
50
|
+
# @returns [String] The CommonMark source.
|
|
51
|
+
def to_commonmark
|
|
52
|
+
@node.to_commonmark
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
alias to_s to_commonmark
|
|
56
|
+
end
|
|
57
|
+
|
|
17
58
|
# Parses a Markdown slide file into structured data for {Slide}.
|
|
18
59
|
#
|
|
19
60
|
# Handles YAML front_matter extraction, presenter note separation, and
|
|
20
|
-
# Markdown
|
|
61
|
+
# Markdown AST construction via Markly.
|
|
21
62
|
module Parser
|
|
22
|
-
# Markly extensions enabled for all slide Markdown rendering.
|
|
23
|
-
EXTENSIONS = [:table, :tasklist, :strikethrough, :autolink]
|
|
24
|
-
|
|
25
63
|
module_function
|
|
26
|
-
|
|
64
|
+
|
|
27
65
|
# Parse the file and return a {Slide}.
|
|
28
66
|
# @parameter path [String] The file path to parse.
|
|
29
67
|
# @returns [Slide]
|
|
30
68
|
def load(path)
|
|
31
69
|
raw = File.read(path)
|
|
32
|
-
|
|
70
|
+
|
|
33
71
|
# Parse once, with native front matter support.
|
|
34
|
-
document = Markly.parse(raw, flags: Markly::UNSAFE | Markly::FRONT_MATTER, extensions: EXTENSIONS)
|
|
35
|
-
|
|
72
|
+
document = Markly.parse(raw, flags: Markly::UNSAFE | Markly::FRONT_MATTER, extensions: Fragment::EXTENSIONS)
|
|
73
|
+
|
|
36
74
|
# Extract front matter from the first AST node if present.
|
|
37
75
|
front_matter = nil
|
|
38
76
|
if (front_matter_node = document.first_child) && front_matter_node.type == :front_matter
|
|
39
77
|
front_matter = YAML.safe_load(front_matter_node.string_content)
|
|
40
78
|
front_matter_node.delete
|
|
41
79
|
end
|
|
42
|
-
|
|
80
|
+
|
|
43
81
|
# Find the last hrule, which acts as the separator between slide content and presenter notes.
|
|
44
82
|
last_hrule = nil
|
|
45
83
|
document.each{|node| last_hrule = node if node.type == :hrule}
|
|
46
|
-
|
|
84
|
+
|
|
47
85
|
if last_hrule
|
|
48
|
-
|
|
86
|
+
notes_node = Markly::Node.new(:document)
|
|
49
87
|
while child = last_hrule.next
|
|
50
|
-
|
|
88
|
+
notes_node.append_child(child)
|
|
51
89
|
end
|
|
52
90
|
last_hrule.delete
|
|
53
|
-
|
|
91
|
+
|
|
54
92
|
# Extract the last javascript code block from the notes as the slide script.
|
|
55
93
|
script_node = nil
|
|
56
|
-
|
|
94
|
+
notes_node.each do |node|
|
|
57
95
|
if node.type == :code_block && node.fence_info.to_s.strip == "javascript"
|
|
58
96
|
script_node = node
|
|
59
97
|
end
|
|
60
98
|
end
|
|
61
|
-
|
|
99
|
+
|
|
62
100
|
script = nil
|
|
63
101
|
if script_node
|
|
64
102
|
script = script_node.string_content
|
|
65
103
|
script_node.delete
|
|
66
104
|
end
|
|
67
|
-
|
|
68
|
-
content = parse_sections(document
|
|
69
|
-
notes =
|
|
105
|
+
|
|
106
|
+
content = parse_sections(document)
|
|
107
|
+
notes = Fragment.new(notes_node)
|
|
70
108
|
else
|
|
71
|
-
content = parse_sections(document
|
|
109
|
+
content = parse_sections(document)
|
|
72
110
|
notes = nil
|
|
73
111
|
script = nil
|
|
74
112
|
end
|
|
75
|
-
|
|
113
|
+
|
|
76
114
|
Slide.new(path, front_matter: front_matter, content: content, notes: notes, script: script)
|
|
77
115
|
end
|
|
78
|
-
|
|
79
|
-
# Parse a
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# @parameter
|
|
84
|
-
# @returns [Hash(String,
|
|
85
|
-
def parse_sections(
|
|
116
|
+
|
|
117
|
+
# Parse a Markly document into content sections based on top-level headings.
|
|
118
|
+
#
|
|
119
|
+
# Each heading becomes a named key; content before the first heading is
|
|
120
|
+
# collected under `"body"`. Each value is a {Fragment} wrapping a document node.
|
|
121
|
+
# @parameter document [Markly::Node] The document to parse.
|
|
122
|
+
# @returns [Hash(String, Fragment)] Sections keyed by heading name.
|
|
123
|
+
def parse_sections(document)
|
|
86
124
|
sections = {}
|
|
87
125
|
current_key = "body"
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
126
|
+
current_node = Markly::Node.new(:document)
|
|
127
|
+
|
|
128
|
+
document.each do |node|
|
|
91
129
|
if node.type == :header
|
|
92
|
-
sections[current_key] =
|
|
130
|
+
sections[current_key] = Fragment.new(current_node) unless current_node.first_child.nil?
|
|
93
131
|
current_key = node.to_plaintext.strip.downcase.gsub(/\s+/, "_")
|
|
94
|
-
|
|
132
|
+
current_node = Markly::Node.new(:document)
|
|
95
133
|
else
|
|
96
|
-
|
|
134
|
+
current_node.append_child(node.dup)
|
|
97
135
|
end
|
|
98
136
|
end
|
|
99
|
-
|
|
100
|
-
sections[current_key] =
|
|
101
|
-
|
|
137
|
+
|
|
138
|
+
sections[current_key] = Fragment.new(current_node) unless current_node.first_child.nil?
|
|
139
|
+
|
|
102
140
|
sections
|
|
103
141
|
end
|
|
104
|
-
|
|
105
|
-
# Render a list of AST nodes to HTML via a temporary document.
|
|
106
|
-
# @parameter nodes [Array(Markly::Node)] The nodes to render.
|
|
107
|
-
# @returns [String] The rendered HTML.
|
|
108
|
-
def render_nodes(nodes)
|
|
109
|
-
doc = Markly::Node.new(:document)
|
|
110
|
-
nodes.each{|node| doc.append_child(node.dup)}
|
|
111
|
-
Renderer.new(flags: Markly::UNSAFE, extensions: EXTENSIONS).render(doc)
|
|
112
|
-
end
|
|
113
142
|
end
|
|
114
|
-
|
|
143
|
+
|
|
115
144
|
# Load and parse a slide from a Markdown file.
|
|
116
145
|
# @parameter path [String] The file path to the Markdown slide.
|
|
117
146
|
# @returns [Slide]
|
|
118
147
|
def self.load(path)
|
|
119
148
|
Parser.load(path)
|
|
120
149
|
end
|
|
121
|
-
|
|
150
|
+
|
|
122
151
|
# Initialize a slide with pre-parsed data.
|
|
123
152
|
# @parameter path [String] The file path of the slide.
|
|
124
153
|
# @parameter front_matter [Hash | Nil] The parsed YAML front_matter.
|
|
125
|
-
# @parameter content [Hash(String,
|
|
126
|
-
# @parameter notes [
|
|
154
|
+
# @parameter content [Hash(String, Fragment)] Content sections keyed by heading name.
|
|
155
|
+
# @parameter notes [Fragment | Nil] The presenter notes as a Markly AST fragment.
|
|
127
156
|
# @parameter script [String | Nil] JavaScript to execute after the slide renders.
|
|
128
157
|
def initialize(path, front_matter: nil, content: {}, notes: nil, script: nil)
|
|
129
158
|
@path = path
|
|
@@ -132,58 +161,58 @@ module Presently
|
|
|
132
161
|
@notes = notes
|
|
133
162
|
@script = script
|
|
134
163
|
end
|
|
135
|
-
|
|
164
|
+
|
|
136
165
|
# @attribute [String] The file path of the slide.
|
|
137
166
|
attr :path
|
|
138
|
-
|
|
167
|
+
|
|
139
168
|
# @attribute [Hash | Nil] The parsed YAML front_matter.
|
|
140
169
|
attr :front_matter
|
|
141
|
-
|
|
142
|
-
# @attribute [Hash(String,
|
|
170
|
+
|
|
171
|
+
# @attribute [Hash(String, Fragment)] The content sections keyed by heading name.
|
|
143
172
|
attr :content
|
|
144
|
-
|
|
145
|
-
# @attribute [
|
|
173
|
+
|
|
174
|
+
# @attribute [Fragment | Nil] The presenter notes as a Markly AST fragment.
|
|
146
175
|
attr :notes
|
|
147
|
-
|
|
176
|
+
|
|
148
177
|
# @attribute [String | Nil] JavaScript to execute after the slide renders on the display.
|
|
149
178
|
attr :script
|
|
150
|
-
|
|
179
|
+
|
|
151
180
|
# The template to use for rendering this slide.
|
|
152
181
|
# @returns [String] The template name from front_matter, or `"default"`.
|
|
153
182
|
def template
|
|
154
183
|
@front_matter&.fetch("template", "default") || "default"
|
|
155
184
|
end
|
|
156
|
-
|
|
185
|
+
|
|
157
186
|
# The expected duration of this slide in seconds.
|
|
158
187
|
# @returns [Integer] The duration from front_matter, or `60`.
|
|
159
188
|
def duration
|
|
160
189
|
@front_matter&.fetch("duration", 60) || 60
|
|
161
190
|
end
|
|
162
|
-
|
|
191
|
+
|
|
163
192
|
# The title of this slide.
|
|
164
193
|
# @returns [String] The title from front_matter, or the filename without extension.
|
|
165
194
|
def title
|
|
166
195
|
@front_matter&.fetch("title", File.basename(@path, ".md")) || File.basename(@path, ".md")
|
|
167
196
|
end
|
|
168
|
-
|
|
197
|
+
|
|
169
198
|
# Whether this slide should be skipped in the presentation.
|
|
170
199
|
# @returns [Boolean]
|
|
171
200
|
def skip?
|
|
172
201
|
@front_matter&.fetch("skip", false) || false
|
|
173
202
|
end
|
|
174
|
-
|
|
203
|
+
|
|
175
204
|
# The navigation marker for this slide, used in the presenter's jump-to dropdown.
|
|
176
205
|
# @returns [String | Nil] The marker label, or `nil` if not marked.
|
|
177
206
|
def marker
|
|
178
207
|
@front_matter&.fetch("marker", nil)
|
|
179
208
|
end
|
|
180
|
-
|
|
209
|
+
|
|
181
210
|
# The transition type for animating into this slide.
|
|
182
211
|
# @returns [String | Nil] The transition name (e.g. `"fade"`, `"slide-left"`, `"morph"`), or `nil` for instant swap.
|
|
183
212
|
def transition
|
|
184
213
|
@front_matter&.fetch("transition", nil)
|
|
185
214
|
end
|
|
186
|
-
|
|
215
|
+
|
|
187
216
|
# The line range to focus on for code slides.
|
|
188
217
|
# @returns [Array(Integer, Integer) | Nil] The `[start, end]` line numbers (1-based), or `nil`.
|
|
189
218
|
def focus
|
|
@@ -68,14 +68,15 @@ module Presently
|
|
|
68
68
|
# @parameter name [String] The section name (derived from the Markdown heading).
|
|
69
69
|
# @returns [Boolean]
|
|
70
70
|
def section?(name)
|
|
71
|
-
|
|
71
|
+
fragment = @slide.content[name]
|
|
72
|
+
fragment && !fragment.empty?
|
|
72
73
|
end
|
|
73
74
|
|
|
74
75
|
# Get a named content section as raw HTML markup.
|
|
75
76
|
# @parameter name [String] The section name (derived from the Markdown heading).
|
|
76
77
|
# @returns [XRB::MarkupString] The rendered HTML content, safe for embedding.
|
|
77
78
|
def section(name)
|
|
78
|
-
XRB::MarkupString.raw(@slide.content[name] || "")
|
|
79
|
+
XRB::MarkupString.raw(@slide.content[name]&.to_html || "")
|
|
79
80
|
end
|
|
80
81
|
end
|
|
81
82
|
end
|
data/lib/presently/version.rb
CHANGED
data/readme.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A web-based presentation tool built with [Lively](https://github.com/socketry/lively). Write your slides in Markdown, present them in the browser, and control everything from a separate presenter display.
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
[](https://github.com/socketry/presently/actions?workflow=Test)
|
|
6
8
|
|
|
7
9
|
## Features
|
|
@@ -19,14 +21,19 @@ A web-based presentation tool built with [Lively](https://github.com/socketry/li
|
|
|
19
21
|
|
|
20
22
|
Please see the [project documentation](https://socketry.github.io/presently/) for more details.
|
|
21
23
|
|
|
22
|
-
- [Animating Slides](https://socketry.github.io/presently/guides/animating-slides/index) - This guide explains how to animate content within slides using the `morph` transition and the slide scripting system.
|
|
23
|
-
|
|
24
24
|
- [Getting Started](https://socketry.github.io/presently/guides/getting-started/index) - This guide explains how to use `presently` to create and deliver web-based presentations using Markdown slides.
|
|
25
25
|
|
|
26
|
+
- [Animating Slides](https://socketry.github.io/presently/guides/animating-slides/index) - This guide explains how to animate content within slides using the `morph` transition and the slide scripting system.
|
|
27
|
+
|
|
26
28
|
## Releases
|
|
27
29
|
|
|
28
30
|
Please see the [project releases](https://socketry.github.io/presently/releases/index) for all releases.
|
|
29
31
|
|
|
32
|
+
### v0.4.0
|
|
33
|
+
|
|
34
|
+
- Add `bake presently:slides:notes` task to extract all presenter notes into a single Markdown document, with each slide's file path as a heading. Useful for reviewing or sharing speaker notes outside of the presentation.
|
|
35
|
+
- Presenter notes are now kept as a Markdown AST internally and rendered to HTML on demand, so the notes you write are faithfully round-tripped rather than converted to HTML at parse time.
|
|
36
|
+
|
|
30
37
|
### v0.3.0
|
|
31
38
|
|
|
32
39
|
- Add `diagram` template with a `position: relative` container — direct `<div>` children are `position: absolute` by default for free-form layouts.
|
data/releases.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.4.0
|
|
4
|
+
|
|
5
|
+
- Add `bake presently:slides:notes` task to extract all presenter notes into a single Markdown document, with each slide's file path as a heading. Useful for reviewing or sharing speaker notes outside of the presentation.
|
|
6
|
+
- Presenter notes are now kept as a Markdown AST internally and rendered to HTML on demand, so the notes you write are faithfully round-tripped rather than converted to HTML at parse time.
|
|
7
|
+
|
|
3
8
|
## v0.3.0
|
|
4
9
|
|
|
5
10
|
- Add `diagram` template with a `position: relative` container — direct `<div>` children are `position: absolute` by default for free-form layouts.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
|
Binary file
|