presently 0.23.0 → 0.24.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/animating-slides.md +14 -0
- data/lib/presently/application.rb +4 -4
- data/lib/presently/home_view.rb +1 -1
- data/lib/presently/page.rb +1 -0
- data/lib/presently/presentation_controller.rb +10 -1
- data/lib/presently/{recording_view.rb → recorder_view.rb} +4 -27
- data/lib/presently/recording_controls_view.rb +44 -0
- data/lib/presently/slide_renderer.rb +3 -1
- data/lib/presently/version.rb +1 -1
- data/lib/presently.rb +1 -1
- data/public/_components/@socketry/presently/Presently/Scripts.js +3 -0
- data/public/_components/@socketry/presently/Presently/SlideRendering.js +8 -6
- data/public/_static/recorder.css +41 -32
- data/public/_static/slides.css +12 -0
- data/public/application.js +4 -4
- data/public/recording_controls.js +522 -0
- data/readme.md +10 -12
- data/releases.md +9 -0
- data.tar.gz.sig +0 -0
- metadata +4 -3
- metadata.gz.sig +0 -0
- data/public/recorder.js +0 -366
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d697262a722f7882d1d7b21809cdd51e1bb6475dd137f085e3e6fcd6a6f19f1
|
|
4
|
+
data.tar.gz: f6f9cb865b7ba25161aafb1ec85cc665f88890b0845f8cd2e08f82ef89984cb6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c1823bec93a21b7d361805c6e0f6f35512d5a91a524ef05a8837c9fbdac36091afacd689c311f2f664efb9f7f5c5dc02a3f27caa2cdfbcce6d5937d9ea0f2206
|
|
7
|
+
data.tar.gz: efe1d9bc5bd918a498f7e395e466abdbcae60cc5896c8ac3c75b76fd952ca4b459b0479cb1c3df5618e3f4fc072eaa0c7db5e0376f82675a6951bcf8e2e94fe9
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/context/animating-slides.md
CHANGED
|
@@ -29,6 +29,20 @@ The script receives a `slide` object — an instance of the `Slide` class export
|
|
|
29
29
|
|
|
30
30
|
If the script contains a syntax error or throws an exception, the error is logged to the browser console and the presentation continues unaffected.
|
|
31
31
|
|
|
32
|
+
## Flicker-Free Initial State
|
|
33
|
+
|
|
34
|
+
Presently keeps a scripted slide hidden until its scripts have run. Any visibility or styling that defines the slide's initial state must therefore be applied synchronously at the top level of the script:
|
|
35
|
+
|
|
36
|
+
``` javascript
|
|
37
|
+
const bullets = slide.find("li").builder({effect: "fade"})
|
|
38
|
+
bullets.show(0)
|
|
39
|
+
bullets.play(500)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Use `slide.after()`, `setTimeout()`, promises, and `requestAnimationFrame()` only for changes that occur after the initial state is established. Deferring the initial `show()` call allows the slide to become visible before the callback runs and can expose its uninitialized content for a frame.
|
|
43
|
+
|
|
44
|
+
Integrations using `SlideRendering#render(update)` have the same requirement: `update` must mutate the supplied view synchronously. Presently runs the new slide's scripts immediately after that update, before yielding to asynchronous syntax highlighting or code-focus preparation.
|
|
45
|
+
|
|
32
46
|
## The Slide API
|
|
33
47
|
|
|
34
48
|
### `slide.find(selector)`
|
|
@@ -11,7 +11,7 @@ require_relative "presentation_controller"
|
|
|
11
11
|
require_relative "home_view"
|
|
12
12
|
require_relative "display_view"
|
|
13
13
|
require_relative "presenter_view"
|
|
14
|
-
require_relative "
|
|
14
|
+
require_relative "recorder_view"
|
|
15
15
|
require_relative "recordings"
|
|
16
16
|
require_relative "slide_assets"
|
|
17
17
|
require_relative "playback"
|
|
@@ -44,7 +44,7 @@ module Presently
|
|
|
44
44
|
# The view classes that this application allows.
|
|
45
45
|
# @returns [Array(Class)] The allowed view classes.
|
|
46
46
|
def allowed_views
|
|
47
|
-
[HomeView, DisplayView, PresenterView,
|
|
47
|
+
[HomeView, DisplayView, PresenterView, RecorderView, RecordingControlsView]
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# The shared state passed to all views via the resolver.
|
|
@@ -60,7 +60,7 @@ module Presently
|
|
|
60
60
|
templates = Templates.for(@templates_roots)
|
|
61
61
|
presentation = Presentation.load(@slides_root, templates)
|
|
62
62
|
|
|
63
|
-
PresentationController.new(presentation, state: State.new)
|
|
63
|
+
PresentationController.new(presentation, state: State.new, recordings: @recordings)
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
@@ -89,7 +89,7 @@ module Presently
|
|
|
89
89
|
|
|
90
90
|
router.get("/display"){make_page(resolver.root(DisplayView), interface: :display).call}
|
|
91
91
|
router.get("/presenter"){make_page(resolver.root(PresenterView), interface: :presenter).call}
|
|
92
|
-
router.get("/
|
|
92
|
+
router.get("/recorder"){make_page(resolver.root(RecorderView), interface: :recorder).call}
|
|
93
93
|
|
|
94
94
|
router.route("/recordings", methods: ["GET", "HEAD", "PUT", "PATCH"]) do |request|
|
|
95
95
|
handle_recording(request, request_parameters(request))
|
data/lib/presently/home_view.rb
CHANGED
|
@@ -20,7 +20,7 @@ module Presently
|
|
|
20
20
|
description: "Control the presentation with speaker notes, timing, and slide previews.",
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
|
-
href: "/
|
|
23
|
+
href: "/recorder",
|
|
24
24
|
title: "Narration Recorder",
|
|
25
25
|
description: "Record, review, and save narration for each slide.",
|
|
26
26
|
},
|
data/lib/presently/page.rb
CHANGED
|
@@ -29,6 +29,7 @@ module Presently
|
|
|
29
29
|
].freeze
|
|
30
30
|
IMPORTS = {
|
|
31
31
|
"live" => "/_components/@socketry/live/Live.js",
|
|
32
|
+
"@socketry/live" => "/_components/@socketry/live/Live.js",
|
|
32
33
|
"live-audio" => "/_components/@socketry/live-audio/Live/Audio.js",
|
|
33
34
|
"morphdom" => "/_components/morphdom/morphdom-esm.js",
|
|
34
35
|
"@socketry/presently" => "/_components/@socketry/presently/Presently.js",
|
|
@@ -18,8 +18,10 @@ module Presently
|
|
|
18
18
|
# Initialize a new controller for the given presentation.
|
|
19
19
|
# @parameter presentation [Presentation] The presentation to control.
|
|
20
20
|
# @parameter state [State | Nil] The state persistence object. If provided, state is saved on changes and restored on initialization.
|
|
21
|
-
|
|
21
|
+
# @parameter recordings [Recordings | Nil] The narration recording store, when recording is enabled.
|
|
22
|
+
def initialize(presentation, state: nil, recordings: nil)
|
|
22
23
|
@presentation = presentation
|
|
24
|
+
@recordings = recordings
|
|
23
25
|
@current_index = 0
|
|
24
26
|
@clock = Clock.new
|
|
25
27
|
@listeners = []
|
|
@@ -55,6 +57,13 @@ module Presently
|
|
|
55
57
|
@presentation.slides[@current_index]
|
|
56
58
|
end
|
|
57
59
|
|
|
60
|
+
# Whether the given slide has a narration recording.
|
|
61
|
+
# @parameter slide [Slide] The slide to check.
|
|
62
|
+
# @returns [Boolean]
|
|
63
|
+
def recording_available?(slide)
|
|
64
|
+
@recordings&.exist?(slide) || false
|
|
65
|
+
end
|
|
66
|
+
|
|
58
67
|
# The slide following the current one.
|
|
59
68
|
# @returns [Slide | Nil] The next slide, or `nil` if on the last slide.
|
|
60
69
|
def next_slide
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# Copyright, 2026, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
require_relative "editor"
|
|
7
|
+
require_relative "recording_controls_view"
|
|
7
8
|
require_relative "slide_view"
|
|
8
9
|
require_relative "slide_renderer"
|
|
9
10
|
|
|
@@ -13,7 +14,7 @@ module Presently
|
|
|
13
14
|
# Recording is intentionally separate from {PresenterView}: presenting is a
|
|
14
15
|
# live performance interface, while recording is an authoring workflow with
|
|
15
16
|
# retakes, playback, and explicit saving.
|
|
16
|
-
class
|
|
17
|
+
class RecorderView < SlideView
|
|
17
18
|
# Initialize a recording view.
|
|
18
19
|
# @parameter id [String] The unique element identifier.
|
|
19
20
|
# @parameter data [Hash] The element data attributes.
|
|
@@ -22,6 +23,7 @@ module Presently
|
|
|
22
23
|
super(id, data)
|
|
23
24
|
@controller = controller
|
|
24
25
|
@slide_renderer = SlideRenderer.new(css_class: "slide recording-slide", templates: controller.templates)
|
|
26
|
+
@recording_controls = RecordingControlsView.mount(self, "recording-controls", controller: controller)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
# Bind this view to a page and register for slide changes.
|
|
@@ -73,9 +75,6 @@ module Presently
|
|
|
73
75
|
slide = @controller.current_slide
|
|
74
76
|
return unless slide
|
|
75
77
|
|
|
76
|
-
index = @controller.current_index
|
|
77
|
-
recording_url = "/recordings?index=#{index}"
|
|
78
|
-
|
|
79
78
|
builder.tag(:div, class: "recorder") do
|
|
80
79
|
render_navigation(builder, slide)
|
|
81
80
|
|
|
@@ -88,29 +87,7 @@ module Presently
|
|
|
88
87
|
builder.tag(:section, class: "recording-panel") do
|
|
89
88
|
builder.tag(:h2){builder.text("Narration")}
|
|
90
89
|
builder.tag(:p){builder.text("Record, review, and save one audio track for this slide.")}
|
|
91
|
-
|
|
92
|
-
builder.tag("presently-recorder",
|
|
93
|
-
id: "presently-recorder-#{index}",
|
|
94
|
-
"data-recording-url": recording_url,
|
|
95
|
-
"data-slide-duration": slide.duration
|
|
96
|
-
) do
|
|
97
|
-
builder.tag(:div, class: "recording-actions") do
|
|
98
|
-
builder.tag(:button, class: "recording-toggle", type: "button"){builder.text("● Record")}
|
|
99
|
-
builder.tag(:button, class: "recording-save", type: "button", disabled: true){builder.text("Save")}
|
|
100
|
-
builder.tag(:span, class: "recording-indicator", "aria-hidden": "true"){}
|
|
101
|
-
builder.tag(:span, class: "recording-time"){builder.text("0:00")}
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
builder.tag(:audio, class: "recording-playback", controls: true, preload: "metadata", hidden: true){}
|
|
105
|
-
builder.tag(:button, class: "recording-apply-duration", type: "button", hidden: true) do
|
|
106
|
-
builder.text("Update Slide Duration")
|
|
107
|
-
end
|
|
108
|
-
builder.tag(:label, class: "recording-duration-option") do
|
|
109
|
-
builder.tag(:input, class: "recording-update-duration", type: "checkbox")
|
|
110
|
-
builder.tag(:span){builder.text("Update slide duration to match recording")}
|
|
111
|
-
end
|
|
112
|
-
builder.tag(:p, class: "recording-status", role: "status"){builder.text("Checking for an existing recording…")}
|
|
113
|
-
end
|
|
90
|
+
builder << @recording_controls.to_html
|
|
114
91
|
end
|
|
115
92
|
|
|
116
93
|
builder.tag(:section, class: "recording-notes") do
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "live"
|
|
7
|
+
|
|
8
|
+
module Presently
|
|
9
|
+
# The persistent narration controls nested within {RecorderView}.
|
|
10
|
+
class RecordingControlsView < Live::View
|
|
11
|
+
# Initialize the recording controls view.
|
|
12
|
+
# @parameter id [String] The stable element identifier.
|
|
13
|
+
# @parameter data [Hash] The element data attributes.
|
|
14
|
+
# @parameter controller [PresentationController] The shared presentation controller.
|
|
15
|
+
def initialize(id, data, controller:)
|
|
16
|
+
super(id, data)
|
|
17
|
+
@controller = controller
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Use the client-side recorder as this live view's custom element.
|
|
21
|
+
def tag_name
|
|
22
|
+
"presently-recording-controls"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Render the recorder host. Its shadow DOM owns the controls themselves.
|
|
26
|
+
# @parameter builder [XRB::Builder] The HTML builder.
|
|
27
|
+
def build_markup(builder)
|
|
28
|
+
slide = @controller.current_slide
|
|
29
|
+
return unless slide
|
|
30
|
+
|
|
31
|
+
index = @controller.current_index
|
|
32
|
+
recording_present = @controller.recording_available?(slide)
|
|
33
|
+
|
|
34
|
+
builder.inline_tag(tag_name,
|
|
35
|
+
id: @id,
|
|
36
|
+
data: @data,
|
|
37
|
+
"data-slide-index": index,
|
|
38
|
+
"data-recording-url": "/recordings?index=#{index}",
|
|
39
|
+
"data-slide-duration": slide.duration,
|
|
40
|
+
"data-recording-state": recording_present ? "present" : "missing"
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -45,7 +45,9 @@ module Presently
|
|
|
45
45
|
scope = TemplateScope.new(slide)
|
|
46
46
|
html = template.to_string(scope)
|
|
47
47
|
|
|
48
|
-
classes = [@css_class, extra_class]
|
|
48
|
+
classes = [@css_class, extra_class]
|
|
49
|
+
classes << "slide-scripted" unless slide.scripts.empty?
|
|
50
|
+
classes = classes.compact.join(" ")
|
|
49
51
|
path = Stylesheet.encode_path(slide.path)
|
|
50
52
|
|
|
51
53
|
builder.tag(:div, class: "slide-surface", data: {template: slide.template}) do
|
data/lib/presently/version.rb
CHANGED
data/lib/presently.rb
CHANGED
|
@@ -8,7 +8,7 @@ require_relative "presently/version"
|
|
|
8
8
|
require_relative "presently/recordings"
|
|
9
9
|
require_relative "presently/stylesheet"
|
|
10
10
|
require_relative "presently/recordings/normalizer"
|
|
11
|
-
require_relative "presently/
|
|
11
|
+
require_relative "presently/recorder_view"
|
|
12
12
|
require_relative "presently/playback"
|
|
13
13
|
require_relative "presently/application"
|
|
14
14
|
require_relative "presently/environment/application"
|
|
@@ -25,6 +25,9 @@ export function runScript(slideEl, {animated = true} = {}) {
|
|
|
25
25
|
}
|
|
26
26
|
} catch (error) {
|
|
27
27
|
console.error('Slide script error:', error);
|
|
28
|
+
} finally {
|
|
29
|
+
// Scripted slides remain hidden until their initial state has been applied.
|
|
30
|
+
slideEl.dataset.slideReady = '';
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
return slide;
|
|
@@ -27,19 +27,21 @@ export class SlideRendering {
|
|
|
27
27
|
async initialize() {
|
|
28
28
|
if (this.#disposed) return false;
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
// Establish script-controlled visibility before yielding to asynchronous
|
|
31
|
+
// highlighting, so partially initialized slide content is never painted.
|
|
33
32
|
this.#view.querySelectorAll('.slide').forEach(slideElement => {
|
|
34
33
|
const slide = runScript(slideElement);
|
|
35
34
|
if (slide) this.#slides.push(slide);
|
|
36
35
|
});
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
await prepareSlides();
|
|
38
|
+
return !this.#disposed;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// Update the view, initialize its slides, and emit the change event.
|
|
42
|
-
//
|
|
42
|
+
// The update must mutate the view synchronously so slide scripts can establish
|
|
43
|
+
// their initial state before the browser can paint the updated DOM.
|
|
44
|
+
// @parameter update [Function] Updates the view's DOM synchronously.
|
|
43
45
|
// @returns [Promise(Boolean)] Whether the rendering was completed.
|
|
44
46
|
async render(update) {
|
|
45
47
|
if (this.#disposed) return false;
|
|
@@ -48,7 +50,7 @@ export class SlideRendering {
|
|
|
48
50
|
const render = async () => {
|
|
49
51
|
if (this.#disposed) return;
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
update(this.#view);
|
|
52
54
|
initialized = await this.initialize();
|
|
53
55
|
};
|
|
54
56
|
|
data/public/_static/recorder.css
CHANGED
|
@@ -55,69 +55,81 @@
|
|
|
55
55
|
opacity: 0.65;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
presently-
|
|
58
|
+
presently-recording-controls {
|
|
59
59
|
display: grid;
|
|
60
60
|
gap: 1rem;
|
|
61
|
+
/* Preserve the completed control layout while the client component upgrades. */
|
|
62
|
+
min-block-size: 12.5rem;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
presently-recording-controls::part(actions) {
|
|
64
66
|
display: flex;
|
|
65
67
|
align-items: center;
|
|
66
68
|
gap: 0.6rem;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
presently-recording-controls::part(toggle),
|
|
72
|
+
presently-recording-controls::part(save),
|
|
73
|
+
presently-recording-controls::part(apply-duration) {
|
|
74
|
+
box-sizing: border-box;
|
|
71
75
|
padding: 0.55rem 0.9rem;
|
|
72
76
|
background: var(--surface-light);
|
|
73
|
-
border: 1px solid rgba(255,255,255,0.12);
|
|
77
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
74
78
|
border-radius: 6px;
|
|
75
79
|
color: var(--slide-text);
|
|
76
80
|
cursor: pointer;
|
|
77
81
|
font: inherit;
|
|
82
|
+
transition:
|
|
83
|
+
background-color 120ms ease-out,
|
|
84
|
+
border-color 120ms ease-out,
|
|
85
|
+
color 120ms ease-out,
|
|
86
|
+
opacity 120ms ease-out;
|
|
78
87
|
}
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
presently-recording-controls::part(apply-duration) {
|
|
81
90
|
justify-self: start;
|
|
82
91
|
}
|
|
83
92
|
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
presently-recording-controls::part(toggle):hover:not(:disabled),
|
|
94
|
+
presently-recording-controls::part(save):hover:not(:disabled),
|
|
95
|
+
presently-recording-controls::part(apply-duration):hover:not(:disabled) {
|
|
86
96
|
background: var(--accent);
|
|
87
97
|
}
|
|
88
98
|
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
presently-recording-controls::part(toggle):disabled,
|
|
100
|
+
presently-recording-controls::part(save):disabled,
|
|
101
|
+
presently-recording-controls::part(apply-duration):disabled {
|
|
91
102
|
cursor: default;
|
|
92
103
|
opacity: 0.35;
|
|
93
104
|
}
|
|
94
105
|
|
|
95
|
-
|
|
106
|
+
presently-recording-controls::part(toggle):not(:disabled) {
|
|
96
107
|
color: var(--accent-light);
|
|
97
108
|
}
|
|
98
109
|
|
|
99
|
-
presently-
|
|
110
|
+
presently-recording-controls[data-recording-state="recording"]::part(toggle) {
|
|
100
111
|
color: #ef5350;
|
|
101
112
|
}
|
|
102
113
|
|
|
103
|
-
|
|
114
|
+
presently-recording-controls::part(indicator) {
|
|
115
|
+
box-sizing: border-box;
|
|
104
116
|
width: 0.65rem;
|
|
105
117
|
height: 0.65rem;
|
|
106
118
|
margin-left: auto;
|
|
107
|
-
background: rgba(255,255,255,0.2);
|
|
119
|
+
background: rgba(255, 255, 255, 0.2);
|
|
108
120
|
border-radius: 50%;
|
|
109
121
|
}
|
|
110
122
|
|
|
111
|
-
presently-
|
|
112
|
-
presently-
|
|
123
|
+
presently-recording-controls[data-recording-state="preparing"]::part(indicator),
|
|
124
|
+
presently-recording-controls[data-recording-state="starting"]::part(indicator) {
|
|
113
125
|
background: #f5b942;
|
|
114
|
-
box-shadow: 0 0 0 0.25rem rgba(245,185,66,0.15);
|
|
126
|
+
box-shadow: 0 0 0 0.25rem rgba(245, 185, 66, 0.15);
|
|
115
127
|
animation: recording-indicator-pulse 600ms ease-in-out infinite alternate;
|
|
116
128
|
}
|
|
117
129
|
|
|
118
|
-
presently-
|
|
130
|
+
presently-recording-controls[data-recording-state="recording"]::part(indicator) {
|
|
119
131
|
background: #ef5350;
|
|
120
|
-
box-shadow: 0 0 0 0.25rem rgba(239,83,80,0.15);
|
|
132
|
+
box-shadow: 0 0 0 0.25rem rgba(239, 83, 80, 0.15);
|
|
121
133
|
}
|
|
122
134
|
|
|
123
135
|
@keyframes recording-indicator-pulse {
|
|
@@ -127,32 +139,29 @@ presently-recorder[data-state="recording"] .recording-indicator {
|
|
|
127
139
|
}
|
|
128
140
|
}
|
|
129
141
|
|
|
130
|
-
|
|
142
|
+
presently-recording-controls::part(time) {
|
|
131
143
|
font-family: var(--font-mono);
|
|
132
144
|
font-variant-numeric: tabular-nums;
|
|
133
145
|
}
|
|
134
146
|
|
|
135
|
-
|
|
147
|
+
presently-recording-controls::part(playback) {
|
|
148
|
+
display: block;
|
|
136
149
|
width: 100%;
|
|
137
150
|
}
|
|
138
151
|
|
|
139
|
-
|
|
152
|
+
presently-recording-controls::part(duration-option) {
|
|
140
153
|
display: flex;
|
|
141
154
|
align-items: center;
|
|
142
155
|
gap: 0.5rem;
|
|
143
156
|
cursor: pointer;
|
|
144
157
|
}
|
|
145
158
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.recording-status.error {
|
|
154
|
-
color: #ff4757;
|
|
155
|
-
opacity: 1;
|
|
159
|
+
@media (prefers-reduced-motion: reduce) {
|
|
160
|
+
presently-recording-controls::part(toggle),
|
|
161
|
+
presently-recording-controls::part(save),
|
|
162
|
+
presently-recording-controls::part(apply-duration) {
|
|
163
|
+
transition: none;
|
|
164
|
+
}
|
|
156
165
|
}
|
|
157
166
|
|
|
158
167
|
.recording-notes {
|
data/public/_static/slides.css
CHANGED
|
@@ -43,6 +43,9 @@
|
|
|
43
43
|
|
|
44
44
|
/* Content size used by the diagram template. */
|
|
45
45
|
--slide-font-diagram: 0.75em;
|
|
46
|
+
|
|
47
|
+
/* Link color chosen for legibility against the default dark slide background. */
|
|
48
|
+
--slide-link: #7dd3fc;
|
|
46
49
|
|
|
47
50
|
/* Space between the slide edge and its content. */
|
|
48
51
|
--slide-gutter: 3cqi;
|
|
@@ -60,6 +63,11 @@
|
|
|
60
63
|
container-type: size;
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
/* Never expose a scripted slide before its script establishes the initial state. */
|
|
67
|
+
.slide-scripted:not([data-slide-ready]) {
|
|
68
|
+
visibility: hidden;
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
@container (min-aspect-ratio: 16 / 9) {
|
|
64
72
|
.slide {
|
|
65
73
|
width: auto;
|
|
@@ -80,6 +88,10 @@
|
|
|
80
88
|
line-height: 1.5;
|
|
81
89
|
}
|
|
82
90
|
|
|
91
|
+
.slide a {
|
|
92
|
+
color: var(--slide-link);
|
|
93
|
+
}
|
|
94
|
+
|
|
83
95
|
.slide-header {
|
|
84
96
|
flex: none;
|
|
85
97
|
width: 100%;
|
data/public/application.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {SlideRendering} from '@socketry/presently';
|
|
1
|
+
import './recording_controls.js';
|
|
3
2
|
|
|
4
|
-
import '
|
|
3
|
+
import {Live} from '@socketry/live';
|
|
4
|
+
import {SlideRendering} from '@socketry/presently';
|
|
5
5
|
|
|
6
6
|
const SLIDE_RENDER_EVENT = 'presently:slide:render';
|
|
7
7
|
|
|
@@ -52,7 +52,7 @@ document.addEventListener('change', (event) => {
|
|
|
52
52
|
|
|
53
53
|
// Keyboard navigation
|
|
54
54
|
document.addEventListener('keydown', (event) => {
|
|
55
|
-
if (event.target.closest('button, input, textarea, select, audio, presently-
|
|
55
|
+
if (event.target.closest('button, input, textarea, select, audio, presently-recording-controls')) return;
|
|
56
56
|
|
|
57
57
|
const liveView = document.querySelector('live-view');
|
|
58
58
|
if (!liveView) return;
|