oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1{{/* editor — GET /d/{sqid}/edit, the editor (editor.go, spec §3.1).
2
3 The three panes, server-rendered: the strip (every slide as a
4 server-drawn SVG thumbnail, each with its menu), the
5 stage (the switcher, the current slide's section, then its notes),
6 and the inspector (three sections of real form controls: the deck,
7 the slide, the selected block). The bundle (fixtures/editor-entry.js)
8 binds ProseMirror to the text blocks on the stage, fills the
9 inspector for the selection and turns every change into one drive
10 operation, keeps the strip current, and saves — it adds editing to
11 a page that already reads correctly, and it never renders a slide
12 of its own.
13
14 Everything the bundle needs is a data attribute on [slides-editor]:
15 the deck, the log's seq, the current slide, the person's name and
16 colour, and every string the bundle will ever show. The bundle
17 carries no copy: a state changes which of these texts is on screen,
18 and nothing else. Everything the bundle builds more than once (a
19 strip item) is cloned from a <template> here, so its markup and
20 its words are this file's too.
21
22 The inspector's controls are hidden and shown by the bundle
23 (hidden on a field whose block type or slide layout does not have
24 it); every one of them is a labelled control whether or not it is
25 on screen, and TestEditorInspectorMarkupIsFormsWithLabels holds it
26 to that.
27
28 The <noscript> is spec §8's floor said out loud — the editor is
29 the one surface that needs scripts — and it points at the viewer,
30 which does not. */}}
31{{define "page-head"}}<link rel="stylesheet" href="{{asset "static/slides.css"}}">
32<link rel="stylesheet" href="{{asset "static/editor.css"}}">
33<link rel="stylesheet" href="{{asset "static/estilo/save-status.css"}}">
34<script type="module" src="{{.BundleSrc}}"></script>{{end}}
35{{define "content"}}
36<div slides-editor
37 data-deck="{{.DeckSqid}}"
38 data-seq="{{.Seq}}"
39 data-slide="{{.SlideID}}"
40 data-client-name="{{.ClientName}}"
41 data-client-colour="{{.ClientColour}}"
42 data-save-status-module="{{asset "static/estilo/save-status.mjs"}}"
43 data-text-error="{{.L "status.error"}}"
44 data-text-current="{{.CurrentLabel}}"
45 data-text-slide="{{.SlideLabel}}"
46 data-text-phone-override="{{.L "block.phone.override"}}"
47 data-text-canvas-selected="{{.L "canvas.selected"}}"
48 data-text-presence-slide="{{.L "presence.slide"}}"
49 data-text-presence-block="{{.L "presence.block"}}"
50 data-text-presence-more="{{.L "presence.more"}}"
51 data-text-presence-joined="{{.L "presence.joined"}}"
52 data-text-presence-left="{{.L "presence.left"}}"
53 data-store="{{if .HasStore}}1{{else}}0{{end}}"
54 data-text-upload-uploading="{{.L "block.image.uploading"}}"
55 data-text-upload-checking="{{.L "block.image.checking"}}"
56 data-text-upload-done="{{.L "block.image.done"}}"
57 data-text-upload-failed="{{.L "block.image.failed"}}"
58 data-text-upload-toolarge="{{.L "block.image.toolarge"}}"
59 data-text-upload-type="{{.L "block.image.type"}}"
60 data-text-upload-altfirst="{{.L "block.image.altfirst"}}"
61 data-text-asset-remove="{{.L "assets.remove"}}"
62 {{- range .BlockTypes}}
63 data-type-{{.}}="{{$.L (printf "type.%s" .)}}"
64 {{- end}}>
65<noscript>
66 <div rst-callout rst-tone="warning">
67 <div rst-callout-body>
68 <p>{{.NoScriptBody}}</p>
69 <p><a rst-btn href="{{.BackHref}}">{{.NoScriptLink}}</a></p>
70 </div>
71 </div>
72</noscript>
73
74<header slides-editor-bar>
75 {{template "back-nav" dict "Href" .BackHref "Label" .BackLabel}}
76 <h1 slides-editor-title>{{.Title}}</h1>
77 {{/* Every keyboard shortcut has a menu item: Ctrl+Enter, Ctrl+D,
78 Ctrl+Z and Shift+Ctrl+Z are all here as buttons, so somebody who
79 does not know the keys — or cannot press them — finds the same
80 commands by name. Present is a form, as it is on the viewer,
81 because pressing it writes (presentation.go). */}}
82 <details rst-dropdown slides-menu name="rst-menus">
83 <summary rst-btn>{{.MenuLabel}} <span rst-caret aria-hidden="true">{{icon "chevron-down"}}</span></summary>
84 <div rst-dropdown-menu>
85 <button type="button" data-command="new-slide">{{.NewSlideLabel}}</button>
86 <button type="button" data-command="duplicate-slide"{{if not .HasSlides}} disabled{{end}}>{{.DuplicateLabel}}</button>
87 <button type="button" data-command="undo">{{.UndoLabel}}</button>
88 <button type="button" data-command="redo">{{.RedoLabel}}</button>
89 </div>
90 </details>
91 <form method="post" action="{{.PresentAction}}" slides-present-form>
92 <button rst-btn="primary" type="submit">{{.PresentLabel}}</button>
93 </form>
94 {{/* The save line is estilo's savestatus (save_status.go): text,
95 always, "Saved" at rest, and the bundle moves it through
96 "Saving…", "Offline, retrying" and the dropped-batch line with
97 estilo's own module. A slide that would not load is not a save
98 state, so it is said in a line of its own beside it, from
99 data-text-error above. Both are role="status": a change is read
100 out without stealing focus. */}}
101 {{template "estilo-save-status" .SaveStatus}}
102 <span slides-load-error role="status" aria-live="polite" aria-atomic="true"></span>
103 {{/* Presence's one announcement (spec §3.3): somebody arriving in
104 the deck or leaving it, said once and politely. Where each of
105 them IS shows on the strip and on the stage and is never spoken
106 — a live region that followed a colleague's cursor would make
107 the editor unusable with a screen reader. */}}
108 <p slides-presence-live class="rst-sr-only" aria-live="polite" aria-atomic="true"></p>
109</header>
110
111{{/* One strip item, the shape the bundle clones for a slide it adds:
112 the thumbnail (an <img> of the server's SVG — inert, because it is
113 a picture of the slide and never a second copy of it in the tab
114 order; the src is left empty here, because strip.mjs fills it once
115 the slide exists on the server), the link that is the one control
116 for opening it, and the item's own menu. The menu's five commands are
117 the Slide section's, so a slide can be reordered, copied or
118 removed from where it is listed; the drag handle is the item
119 itself (draggable), with Alt+arrows and the menu as the keyboard's
120 equivalents. */}}
121<template slides-strip-item-template>
122 <li slides-strip-item draggable="true">
123 <div slides-thumb inert><img slides-thumb-img alt="" width="1920" height="1080"></div>
124 <a slides-strip-link href="#">
125 <span slides-strip-label></span>
126 <span slides-strip-current>{{.CurrentLabel}}</span>
127 </a>
128 <details rst-dropdown slides-strip-menu name="rst-menus">
129 <summary rst-btn="sm" aria-label="{{.L "strip.actions"}}"><span aria-hidden="true">{{icon "kebab"}}</span></summary>
130 <div rst-dropdown-menu>
131 <button type="button" data-slide-command="add-after">{{.L "slide.add"}}</button>
132 <button type="button" data-slide-command="duplicate">{{.DuplicateLabel}}</button>
133 <button type="button" data-slide-command="move-up">{{.L "slide.move.up"}}</button>
134 <button type="button" data-slide-command="move-down">{{.L "slide.move.down"}}</button>
135 <button type="button" data-slide-command="delete" class="rst-danger">{{.L "slide.delete"}}</button>
136 </div>
137 </details>
138 </li>
139</template>
140
141<nav slides-strip-nav aria-label="{{.StripLabel}}">
142 <p id="slides-strip-help" class="rst-sr-only">{{.L "strip.reorder"}}</p>
143 <ol slides-strip aria-describedby="slides-strip-help">
144 {{range .Strip}}
145 <li slides-strip-item draggable="true" data-slide-id="{{.ID}}"{{if .Current}} data-current="true"{{end}}>
146 <div slides-thumb inert><img slides-thumb-img src="{{.Thumb}}" alt="{{.Label}}" width="1920" height="1080" loading="lazy"></div>
147 <a slides-strip-link href="#{{.ID}}"{{if .Current}} aria-current="true"{{end}}>
148 <span slides-strip-label>{{.Label}}</span>
149 <span slides-strip-current>{{$.CurrentLabel}}</span>
150 </a>
151 <details rst-dropdown slides-strip-menu name="rst-menus">
152 <summary rst-btn="sm" aria-label="{{$.L "strip.actions"}}"><span aria-hidden="true">{{icon "kebab"}}</span></summary>
153 <div rst-dropdown-menu>
154 <button type="button" data-slide-command="add-after">{{$.L "slide.add"}}</button>
155 <button type="button" data-slide-command="duplicate">{{$.DuplicateLabel}}</button>
156 <button type="button" data-slide-command="move-up">{{$.L "slide.move.up"}}</button>
157 <button type="button" data-slide-command="move-down">{{$.L "slide.move.down"}}</button>
158 <button type="button" data-slide-command="delete" class="rst-danger">{{$.L "slide.delete"}}</button>
159 </div>
160 </details>
161 </li>
162 {{end}}
163 </ol>
164</nav>
165
166<section slides-stage-pane aria-label="{{.StageLabel}}" data-view="desktop">
167 {{/* The switcher (spec §3.2): three real radios with visible words.
168 Which one is on decides where a layout edit lands — the desktop
169 block, or its phone override map — so it is never an icon. */}}
170 <div slides-stage-bar>
171 <fieldset slides-switcher>
172 <legend class="rst-sr-only">{{.L "view"}}</legend>
173 <label><input type="radio" name="slides-view" value="desktop" checked> {{.L "view.desktop"}}</label>
174 <label><input type="radio" name="slides-view" value="phone"> {{.L "view.phone"}}</label>
175 <label><input type="radio" name="slides-view" value="both"> {{.L "view.both"}}</label>
176 </fieldset>
177 <label rst-switch slides-notes-toggle><input type="checkbox" name="slides-notes" checked><span rst-switch-track aria-hidden="true"></span> {{.L "slide.notes.show"}}</label>
178 </div>
179 {{/* The stage and the notes are always here, an empty deck's
180 included: the bundle's New slide loads the first slide INTO
181 [slides-stage], so the element has to exist before there is a
182 slide to put in it. The empty state sits inside the stage and is
183 replaced by the first slide's section. The phone stage beside it
184 is the "both" view's read-only preview, empty until then. */}}
185 <div slides-stages>
186 <div slides-stage data-vp="desktop">{{if .HasSlides}}{{.Stage}}{{else}}<div rst-box slides-stage-empty>
187 {{template "empty-state" dict "Title" .EmptyTitle "Body" .EmptyBody}}
188 </div>{{end}}</div>
189 <div slides-stage-phone role="region" aria-label="{{.L "view.phone.readonly"}}" hidden></div>
190 </div>
191 <section slides-notes-pane aria-labelledby="slides-notes-heading"{{if not .HasSlides}} hidden{{end}}>
192 <h2 id="slides-notes-heading" slides-notes-heading>{{.NotesLabel}}</h2>
193 <div slides-notes>{{.Notes}}</div>
194 </section>
195</section>
196
197<details slides-inspector open>
198 <summary>{{.InspectorLabel}}</summary>
199
200 {{/* Deck: title, theme, accent, fonts — each control one set_meta. */}}
201 <div rst-box-head><h2>{{.L "inspector.deck"}}</h2></div>
202 <form rst-form rst-box slides-inspector-form data-section="deck">
203 {{template "field-text" dict "Name" "rst-deck-title" "Label" (.L "deck.title") "Value" .Title}}
204 {{template "field-select" dict "ID" "rst-deck-theme" "Name" "rst-deck-theme" "Label" (.L "deck.theme") "Options" .Themes "Plain" true}}
205 <div rst-field>
206 <label rst-field-label for="rst-deck-accent">{{.L "deck.accent"}}</label>
207 <input rst-input="short" id="rst-deck-accent" name="rst-deck-accent" type="color">
208 </div>
209 <p><button rst-btn="sm" type="button" data-command="accent-reset">{{.L "deck.accent.reset"}}</button></p>
210 <div rst-field-row>
211 <div rst-field>
212 <label rst-field-label for="rst-deck-font-heading">{{.L "deck.font.heading"}}</label>
213 <select rst-input id="rst-deck-font-heading" name="rst-deck-font-heading">
214 <option value="">{{.L "font.theme"}}</option>
215 <option value="system-ui, sans-serif">{{.L "font.system"}}</option>
216 <option value="Georgia, serif">{{.L "font.serif"}}</option>
217 <option value="ui-monospace, monospace">{{.L "font.mono"}}</option>
218 </select>
219 </div>
220 <div rst-field>
221 <label rst-field-label for="rst-deck-font-body">{{.L "deck.font.body"}}</label>
222 <select rst-input id="rst-deck-font-body" name="rst-deck-font-body">
223 <option value="">{{.L "font.theme"}}</option>
224 <option value="system-ui, sans-serif">{{.L "font.system"}}</option>
225 <option value="Georgia, serif">{{.L "font.serif"}}</option>
226 <option value="ui-monospace, monospace">{{.L "font.mono"}}</option>
227 </select>
228 </div>
229 </div>
230 {{/* Unused images (spec §2.7): the assets no image block on any
231 slide refers to any more. Deleting the last block that used one
232 leaves the registration behind on purpose — an undo has to be
233 able to put the block back — so this is where a person clears
234 them out. The bundle fills the list from the document on every
235 change; each Remove is one remove_asset. */}}
236 <fieldset slides-unused-assets>
237 <legend>{{.L "assets.unused"}}</legend>
238 <p rst-text-muted slides-unused-none>{{.L "assets.unused.none"}}</p>
239 <ul slides-unused-list></ul>
240 </fieldset>
241 </form>
242
243 {{/* Slide: layout, template, background, transition, phone
244 behaviour, the structure commands, and the Blocks list a
245 keyboard selects from. Hidden until there is a slide. */}}
246 <div rst-box-head slides-when-slide{{if not .HasSlides}} hidden{{end}}><h2>{{.L "inspector.slide"}}</h2></div>
247 <form rst-form rst-box slides-inspector-form data-section="slide" slides-when-slide{{if not .HasSlides}} hidden{{end}}>
248 {{template "choice-field" dict "Legend" (.L "slide.layout") "Name" "rst-slide-layout" "Options" (list
249 (dict "Value" "flow" "Title" (.L "layout.flow") "Desc" (.L "layout.flow.desc") "Checked" true)
250 (dict "Value" "canvas" "Title" (.L "layout.canvas") "Desc" (.L "layout.canvas.desc")))}}
251 <div rst-field data-layout="flow">
252 <label rst-field-label for="rst-slide-template">{{.L "slide.template"}}</label>
253 <select rst-input id="rst-slide-template" name="rst-slide-template">
254 <option value="title">{{.L "template.title"}}</option>
255 <option value="title-body">{{.L "template.title-body"}}</option>
256 <option value="two-column">{{.L "template.two-column"}}</option>
257 <option value="section">{{.L "template.section"}}</option>
258 <option value="image-full">{{.L "template.image-full"}}</option>
259 <option value="quote">{{.L "template.quote"}}</option>
260 <option value="blank">{{.L "template.blank"}}</option>
261 </select>
262 </div>
263 <div rst-field-row>
264 <div rst-field>
265 <label rst-field-label for="rst-slide-background">{{.L "slide.background"}}</label>
266 <select rst-input id="rst-slide-background" name="rst-slide-background">
267 <option value="theme">{{.L "background.theme"}}</option>
268 <option value="color">{{.L "background.color"}}</option>
269 </select>
270 </div>
271 <div rst-field data-background="color" hidden>
272 <label rst-field-label for="rst-slide-background-color">{{.L "slide.background.color"}}</label>
273 <input rst-input="short" id="rst-slide-background-color" name="rst-slide-background-color" type="color">
274 </div>
275 </div>
276 <div rst-field>
277 <label rst-field-label for="rst-slide-transition">{{.L "slide.transition"}}</label>
278 <select rst-input id="rst-slide-transition" name="rst-slide-transition">
279 <option value="none">{{.L "transition.none"}}</option>
280 <option value="fade">{{.L "transition.fade"}}</option>
281 </select>
282 </div>
283 <div rst-field data-layout="canvas" hidden>
284 <label rst-field-label for="rst-slide-phone">{{.L "slide.phone"}}</label>
285 <select rst-input id="rst-slide-phone" name="rst-slide-phone">
286 <option value="inherit">{{.L "slide.phone.inherit"}}</option>
287 <option value="reflow">{{.L "slide.phone.reflow"}}</option>
288 </select>
289 </div>
290 <div slides-inspector-actions>
291 <button rst-btn="sm" type="button" data-command="slide-move-up">{{.L "slide.move.up"}}</button>
292 <button rst-btn="sm" type="button" data-command="slide-move-down">{{.L "slide.move.down"}}</button>
293 <button rst-btn="sm" type="button" data-command="slide-add-after">{{.L "slide.add"}}</button>
294 <button rst-btn="sm" type="button" data-command="duplicate-slide">{{.DuplicateLabel}}</button>
295 <button rst-btn="sm" type="button" data-command="slide-delete-ask">{{.L "slide.delete"}}</button>
296 </div>
297 {{/* The delete confirm lives here, in the section, rather than in a
298 window.confirm the catalog cannot word and a keyboard cannot
299 always reach. */}}
300 <div rst-callout rst-tone="warning" slides-confirm hidden>
301 <div rst-callout-body>
302 <p>{{.L "slide.delete.confirm"}}</p>
303 <p slides-inspector-actions>
304 <button rst-btn="danger sm" type="button" data-command="slide-delete">{{.L "slide.delete"}}</button>
305 <button rst-btn="sm" type="button" data-command="slide-delete-cancel">{{.L "slide.delete.cancel"}}</button>
306 </p>
307 </div>
308 </div>
309 <fieldset slides-blocks-list>
310 <legend>{{.L "inspector.blocks"}}</legend>
311 <p rst-text-muted slides-blocks-none>{{.L "inspector.blocks.none"}}</p>
312 <div slides-blocks-buttons role="group"></div>
313 </fieldset>
314 <details rst-dropdown slides-add-block name="rst-menus">
315 <summary rst-btn="sm">{{.L "block.add"}} <span rst-caret aria-hidden="true">{{icon "chevron-down"}}</span></summary>
316 <div rst-dropdown-menu>
317 {{range .BlockTypes}}<button type="button" data-add-block="{{.}}">{{$.L (printf "type.%s" .)}}</button>
318 {{end}}
319 </div>
320 </details>
321 </form>
322
323 {{/* Block: the selected block's props (each field marked with the
324 types that have it), its placement (region on a flow slide, a
325 box and layer on a canvas one), its order, and its phone layer.
326 Hidden until a block is selected. */}}
327 {{/* The heading is the slide's, not the selection's: with a slide on
328 stage and nothing selected there is a Block section saying how to
329 get one, rather than a section that has silently vanished. The
330 bundle shows the line when a slide is on and no block is. */}}
331 <div rst-box-head slides-when-slide{{if not .HasSlides}} hidden{{end}}><h2>{{.L "inspector.block"}} <span slides-block-type></span></h2></div>
332 <p rst-text-muted slides-inspector-empty{{if .HasSlides}}{{else}} hidden{{end}}>{{.InspectorEmpty}}</p>
333 <form rst-form rst-box slides-inspector-form data-section="block" slides-when-block hidden>
334 <p slides-phone-badge hidden><span rst-status rst-tone="warning">{{.L "block.phone.override"}}</span>
335 <button rst-btn="sm" type="button" data-command="phone-reset">{{.L "block.phone.reset"}}</button></p>
336 <div rst-field data-types="heading">
337 <label rst-field-label for="rst-block-level">{{.L "block.level"}}</label>
338 <select rst-input="short" id="rst-block-level" name="rst-block-level" data-prop="level" data-kind="int">
339 <option value="1">1</option><option value="2">2</option><option value="3">3</option>
340 </select>
341 </div>
342 <div rst-field data-types="text list code">
343 <label rst-field-label for="rst-block-size">{{.L "block.size"}}</label>
344 <select rst-input="short" id="rst-block-size" name="rst-block-size" data-prop="size">
345 <option value="s">{{.L "size.s"}}</option><option value="m">{{.L "size.m"}}</option><option value="l">{{.L "size.l"}}</option>
346 </select>
347 </div>
348 <div rst-field data-types="text">
349 <label rst-field-label for="rst-block-align">{{.L "block.align"}}</label>
350 <select rst-input="short" id="rst-block-align" name="rst-block-align" data-prop="align">
351 <option value="start">{{.L "align.start"}}</option><option value="center">{{.L "align.center"}}</option><option value="end">{{.L "align.end"}}</option>
352 </select>
353 </div>
354 <div rst-field data-types="list">
355 <label rst-field-label for="rst-block-style">{{.L "block.style"}}</label>
356 <select rst-input="short" id="rst-block-style" name="rst-block-style" data-prop="style">
357 <option value="bullet">{{.L "style.bullet"}}</option><option value="number">{{.L "style.number"}}</option>
358 </select>
359 </div>
360 <div rst-field data-types="quote">
361 <label rst-field-label for="rst-block-cite">{{.L "block.cite"}}</label>
362 <input rst-input id="rst-block-cite" name="rst-block-cite" type="text" data-prop="cite">
363 </div>
364 {{/* The image's own upload (assets.go, spec §2.7). A real labelled
365 file input, with the accept list the endpoint validates
366 against; the hint is its description, so a screen reader hears
367 the limits before the picker opens. The status line is text
368 with role="status" — progress and every failure are read out
369 and are never colour alone.
370
371 With no object store bound the input is disabled and the
372 no-store line replaces the hint. The page says so, rather than
373 the bundle finding out from a 503 after somebody has chosen a
374 file. */}}
375 <div rst-field data-types="image">
376 <label rst-field-label for="rst-block-image">{{.L "block.image.file"}}</label>
377 <input rst-input id="rst-block-image" name="rst-block-image" type="file" slides-asset-input
378 accept="{{.AcceptTypes}}" aria-describedby="slides-asset-hint"{{if not .HasStore}} disabled{{end}}>
379 <p rst-field-hint id="slides-asset-hint">{{if .HasStore}}{{.L "block.image.hint"}}{{else}}{{.L "block.image.nostore"}}{{end}}</p>
380 <p rst-text-muted slides-asset-status role="status" aria-live="polite"></p>
381 </div>
382 <div rst-field data-types="image">
383 <label rst-field-label for="rst-block-fit">{{.L "block.fit"}}</label>
384 <select rst-input="short" id="rst-block-fit" name="rst-block-fit" data-prop="fit">
385 <option value="cover">{{.L "fit.cover"}}</option><option value="contain">{{.L "fit.contain"}}</option>
386 </select>
387 </div>
388 <div rst-field data-types="image">
389 <label rst-field-label for="rst-block-alt">{{.L "block.alt"}}</label>
390 <input rst-input id="rst-block-alt" name="rst-block-alt" type="text" data-prop="alt">
391 </div>
392 <div rst-field data-types="image">
393 <label rst-switch><input type="checkbox" name="rst-block-decorative" data-prop="decorative" data-kind="bool"><span rst-switch-track aria-hidden="true"></span> {{.L "block.decorative"}}</label>
394 </div>
395 <div rst-field data-types="image shape">
396 <label rst-field-label for="rst-block-radius">{{.L "block.radius"}}</label>
397 <input rst-input="short" id="rst-block-radius" name="rst-block-radius" type="number" min="0" max="50" step="1" data-prop="radius" data-kind="int">
398 </div>
399 <div rst-field data-types="columns">
400 <label rst-field-label for="rst-block-count">{{.L "block.count"}}</label>
401 <select rst-input="short" id="rst-block-count" name="rst-block-count" data-prop="count" data-kind="int">
402 <option value="2">2</option><option value="3">3</option>
403 </select>
404 </div>
405 <div rst-field data-types="code">
406 <label rst-field-label for="rst-block-language">{{.L "block.language"}}</label>
407 <input rst-input="short" id="rst-block-language" name="rst-block-language" type="text" data-prop="language">
408 </div>
409 <div rst-field data-types="shape">
410 <label rst-field-label for="rst-block-kind">{{.L "block.kind"}}</label>
411 <select rst-input="short" id="rst-block-kind" name="rst-block-kind" data-prop="kind">
412 <option value="rect">{{.L "kind.rect"}}</option><option value="ellipse">{{.L "kind.ellipse"}}</option>
413 <option value="line">{{.L "kind.line"}}</option><option value="arrow">{{.L "kind.arrow"}}</option>
414 </select>
415 </div>
416 <div rst-field-row data-types="shape">
417 <div rst-field>
418 <label rst-field-label for="rst-block-fill">{{.L "block.fill"}}</label>
419 <input rst-input="short" id="rst-block-fill" name="rst-block-fill" type="color" data-prop="fill">
420 </div>
421 <div rst-field>
422 <label rst-field-label for="rst-block-stroke">{{.L "block.stroke"}}</label>
423 <input rst-input="short" id="rst-block-stroke" name="rst-block-stroke" type="color" data-prop="stroke">
424 </div>
425 <div rst-field>
426 <label rst-field-label for="rst-block-strokeWidth">{{.L "block.strokeWidth"}}</label>
427 <input rst-input="short" id="rst-block-strokeWidth" name="rst-block-strokeWidth" type="number" min="0" max="50" step="1" data-prop="strokeWidth" data-kind="int">
428 </div>
429 </div>
430 <div rst-field data-types="shape">
431 <label rst-field-label for="rst-block-label">{{.L "block.label"}}</label>
432 <input rst-input id="rst-block-label" name="rst-block-label" type="text" data-text="label">
433 </div>
434 <div rst-field-row data-types="table">
435 <div rst-field>
436 <label rst-field-label for="rst-block-rows">{{.L "block.rows"}}</label>
437 <input rst-input="short" id="rst-block-rows" name="rst-block-rows" type="number" min="1" max="100" step="1" data-prop="rows" data-kind="int">
438 </div>
439 <div rst-field>
440 <label rst-field-label for="rst-block-cols">{{.L "block.cols"}}</label>
441 <input rst-input="short" id="rst-block-cols" name="rst-block-cols" type="number" min="1" max="100" step="1" data-prop="cols" data-kind="int">
442 </div>
443 </div>
444 <div rst-field data-types="table">
445 <label rst-switch><input type="checkbox" name="rst-block-header" data-prop="header" data-kind="bool"><span rst-switch-track aria-hidden="true"></span> {{.L "block.header"}}</label>
446 </div>
447 <div rst-field data-types="spacer">
448 <label rst-field-label for="rst-block-spacer-size">{{.L "block.size"}}</label>
449 <select rst-input="short" id="rst-block-spacer-size" name="rst-block-spacer-size" data-prop="size">
450 <option value="s">{{.L "size.s"}}</option><option value="m">{{.L "size.m"}}</option><option value="l">{{.L "size.l"}}</option>
451 </select>
452 </div>
453 <div rst-field data-layout="flow">
454 <label rst-field-label for="rst-block-region">{{.L "block.region"}}</label>
455 <select rst-input="short" id="rst-block-region" name="rst-block-region" data-place="region">
456 <option value="heading">{{.L "region.heading"}}</option><option value="body">{{.L "region.body"}}</option>
457 <option value="aside">{{.L "region.aside"}}</option><option value="media">{{.L "region.media"}}</option>
458 </select>
459 </div>
460 <fieldset rst-field-range data-layout="canvas">
461 <legend>{{.L "block.box"}}</legend>
462 <div rst-field-row>
463 <div rst-field>
464 <label rst-field-label for="rst-block-x">{{.L "box.x"}}</label>
465 <input rst-input="short" id="rst-block-x" name="rst-block-x" type="number" min="0" max="100" step="0.1" data-box="x">
466 </div>
467 <div rst-field>
468 <label rst-field-label for="rst-block-y">{{.L "box.y"}}</label>
469 <input rst-input="short" id="rst-block-y" name="rst-block-y" type="number" min="0" max="100" step="0.1" data-box="y">
470 </div>
471 <div rst-field>
472 <label rst-field-label for="rst-block-w">{{.L "box.w"}}</label>
473 <input rst-input="short" id="rst-block-w" name="rst-block-w" type="number" min="1" max="100" step="0.1" data-box="w">
474 </div>
475 <div rst-field>
476 <label rst-field-label for="rst-block-h">{{.L "box.h"}}</label>
477 <input rst-input="short" id="rst-block-h" name="rst-block-h" type="number" min="1" max="100" step="0.1" data-box="h">
478 </div>
479 </div>
480 </fieldset>
481 <div slides-inspector-actions data-layout="canvas">
482 <button rst-btn="sm" type="button" data-command="block-forward">{{.L "block.forward"}}</button>
483 <button rst-btn="sm" type="button" data-command="block-back">{{.L "block.back"}}</button>
484 </div>
485 <div slides-inspector-actions>
486 <button rst-btn="sm" type="button" data-command="block-move-up">{{.L "block.move.up"}}</button>
487 <button rst-btn="sm" type="button" data-command="block-move-down">{{.L "block.move.down"}}</button>
488 <button rst-btn="sm" type="button" data-command="block-delete">{{.L "block.delete"}}</button>
489 </div>
490 <fieldset rst-field-range slides-phone-fields>
491 <legend>{{.L "block.phone"}}</legend>
492 <div rst-field>
493 <label rst-switch><input type="checkbox" name="rst-block-phone-hidden" data-phone="hidden"><span rst-switch-track aria-hidden="true"></span> {{.L "block.phone.hidden"}}</label>
494 </div>
495 <div rst-field-row>
496 <div rst-field>
497 <label rst-field-label for="rst-block-phone-order">{{.L "block.phone.order"}}</label>
498 <input rst-input="short" id="rst-block-phone-order" name="rst-block-phone-order" type="number" step="1" data-phone="order">
499 </div>
500 <div rst-field>
501 <label rst-field-label for="rst-block-phone-size">{{.L "block.phone.size"}}</label>
502 <select rst-input="short" id="rst-block-phone-size" name="rst-block-phone-size" data-phone="size">
503 <option value="">{{.L "block.phone.same"}}</option>
504 <option value="s">{{.L "size.s"}}</option><option value="m">{{.L "size.m"}}</option><option value="l">{{.L "size.l"}}</option>
505 </select>
506 </div>
507 </div>
508 </fieldset>
509 </form>
510</details>
511
512{{/* The link dialog (Ctrl+K): a labelled field, three named buttons.
513 A <dialog> rather than window.prompt because a prompt has no label
514 the catalog controls and no way to remove a link. Closed until the
515 bundle opens it. */}}
516<dialog slides-link-dialog aria-labelledby="slides-link-title">
517 <form method="dialog" rst-form>
518 <h2 id="slides-link-title">{{.LinkTitle}}</h2>
519 <div rst-field>
520 <label rst-field-label for="slides-link-url">{{.LinkURLLabel}}</label>
521 <input rst-input id="slides-link-url" name="href" type="url" inputmode="url" autocomplete="off" spellcheck="false">
522 </div>
523 <div rst-form-actions>
524 <button rst-btn="primary" type="submit" value="insert">{{.LinkInsert}}</button>
525 <button rst-btn type="submit" value="remove" formnovalidate>{{.LinkRemove}}</button>
526 <button rst-btn type="submit" value="cancel" formnovalidate>{{.LinkCancel}}</button>
527 </div>
528 </form>
529</dialog>
530</div>
531{{end}}
532