glideshow 0.1.2__py3-none-any.whl

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.
Files changed (103) hide show
  1. glideshow/__init__.py +15 -0
  2. glideshow/__main__.py +3 -0
  3. glideshow/agent.py +681 -0
  4. glideshow/build.py +54 -0
  5. glideshow/cli.py +320 -0
  6. glideshow/comments.py +35 -0
  7. glideshow/decks.py +230 -0
  8. glideshow/docs/agents.md +223 -0
  9. glideshow/docs/deck-format.md +125 -0
  10. glideshow/editor/.assetsignore +5 -0
  11. glideshow/editor/apple-touch-icon.png +0 -0
  12. glideshow/editor/data/font-metrics.json +1 -0
  13. glideshow/editor/data/fonts.json +1 -0
  14. glideshow/editor/data/icons.json +1 -0
  15. glideshow/editor/data/symbols.js +42 -0
  16. glideshow/editor/data/themes.js +15 -0
  17. glideshow/editor/deck/base.css +92 -0
  18. glideshow/editor/deck/present.js +222 -0
  19. glideshow/editor/deck/shapes.js +377 -0
  20. glideshow/editor/deck/template.html +37 -0
  21. glideshow/editor/editor.css +657 -0
  22. glideshow/editor/glideshow-1024.png +0 -0
  23. glideshow/editor/glideshow.svg +10 -0
  24. glideshow/editor/globals.d.ts +21 -0
  25. glideshow/editor/home.html +358 -0
  26. glideshow/editor/index.html +386 -0
  27. glideshow/editor/js/arrange.js +122 -0
  28. glideshow/editor/js/boot.js +66 -0
  29. glideshow/editor/js/boxes.js +25 -0
  30. glideshow/editor/js/comments.js +85 -0
  31. glideshow/editor/js/context-menus.js +93 -0
  32. glideshow/editor/js/dialogs.js +168 -0
  33. glideshow/editor/js/dock.js +22 -0
  34. glideshow/editor/js/draw.js +107 -0
  35. glideshow/editor/js/file.js +318 -0
  36. glideshow/editor/js/find.js +72 -0
  37. glideshow/editor/js/format-panel.js +101 -0
  38. glideshow/editor/js/geometry.js +153 -0
  39. glideshow/editor/js/guides.js +37 -0
  40. glideshow/editor/js/insert.js +73 -0
  41. glideshow/editor/js/keyboard.js +256 -0
  42. glideshow/editor/js/live.js +48 -0
  43. glideshow/editor/js/menubar.js +290 -0
  44. glideshow/editor/js/menus.js +93 -0
  45. glideshow/editor/js/motion.js +148 -0
  46. glideshow/editor/js/notes.js +63 -0
  47. glideshow/editor/js/objects.js +202 -0
  48. glideshow/editor/js/people.js +73 -0
  49. glideshow/editor/js/phone-comments.js +43 -0
  50. glideshow/editor/js/phone-edit.js +253 -0
  51. glideshow/editor/js/phone-home.js +51 -0
  52. glideshow/editor/js/phone-mode.js +73 -0
  53. glideshow/editor/js/phone-sheets.js +153 -0
  54. glideshow/editor/js/phone-touch.js +145 -0
  55. glideshow/editor/js/phone-view.js +150 -0
  56. glideshow/editor/js/pointer.js +407 -0
  57. glideshow/editor/js/render.js +353 -0
  58. glideshow/editor/js/shortcuts.js +67 -0
  59. glideshow/editor/js/slides.js +120 -0
  60. glideshow/editor/js/spacing.js +23 -0
  61. glideshow/editor/js/state.js +58 -0
  62. glideshow/editor/js/storage.js +250 -0
  63. glideshow/editor/js/text.js +366 -0
  64. glideshow/editor/js/theme-toggle.js +41 -0
  65. glideshow/editor/js/theme.js +173 -0
  66. glideshow/editor/js/toolbar-groups.js +109 -0
  67. glideshow/editor/js/toolbar.js +372 -0
  68. glideshow/editor/lib/browser-db.js +47 -0
  69. glideshow/editor/lib/library.js +612 -0
  70. glideshow/editor/lib/merge.js +174 -0
  71. glideshow/editor/lib/move-local.js +51 -0
  72. glideshow/editor/lib/onefile.js +108 -0
  73. glideshow/editor/lib/store-browser.js +75 -0
  74. glideshow/editor/lib/store-folder.js +109 -0
  75. glideshow/editor/local/sw.js +66 -0
  76. glideshow/editor/local/tsconfig.json +5 -0
  77. glideshow/editor/looks/glider.svg +9 -0
  78. glideshow/editor/looks/thermal-phone.css +144 -0
  79. glideshow/editor/looks/thermal.css +268 -0
  80. glideshow/editor/manifest.json +23 -0
  81. glideshow/editor/phone.css +345 -0
  82. glideshow/editor/share/share.css +124 -0
  83. glideshow/editor/share/share.js +256 -0
  84. glideshow/editor/share/speaker.html +88 -0
  85. glideshow/editor/tsconfig.json +6 -0
  86. glideshow/figure.py +33 -0
  87. glideshow/fonts.py +63 -0
  88. glideshow/gslides.py +1065 -0
  89. glideshow/history.py +210 -0
  90. glideshow/merge.py +210 -0
  91. glideshow/osutil.py +44 -0
  92. glideshow/pptx_export.py +207 -0
  93. glideshow/pptx_import.py +115 -0
  94. glideshow/publish.py +106 -0
  95. glideshow/render.py +288 -0
  96. glideshow/server.py +549 -0
  97. glideshow/service.py +65 -0
  98. glideshow/skills/glideshow/SKILL.md +85 -0
  99. glideshow-0.1.2.dist-info/METADATA +69 -0
  100. glideshow-0.1.2.dist-info/RECORD +103 -0
  101. glideshow-0.1.2.dist-info/WHEEL +4 -0
  102. glideshow-0.1.2.dist-info/entry_points.txt +2 -0
  103. glideshow-0.1.2.dist-info/licenses/LICENSE +662 -0
@@ -0,0 +1,386 @@
1
+ <!doctype html>
2
+ <html lang="en" data-phone-viewport>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <script src="/editor/js/phone-mode.js"></script>
6
+ <title>Glideshow</title>
7
+ <link rel="icon" href="/editor/glideshow.svg" type="image/svg+xml">
8
+ <link rel="apple-touch-icon" href="/editor/apple-touch-icon.png">
9
+ <link rel="manifest" href="/editor/manifest.json"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-title" content="Glideshow"> <!-- Add to Home Screen: opens with no browser bars -->
10
+ <base href="/deck/">
11
+ <link rel="preconnect" href="https://fonts.googleapis.com">
12
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap">
14
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..24,400,0..1,0&display=block">
15
+ <link rel="stylesheet" href="/editor/deck/base.css">
16
+ <link rel="stylesheet" href="/editor/editor.css">
17
+ <link rel="stylesheet" href="/editor/phone.css">
18
+ <script>/* the look of Glideshow's own screens (editor/looks/): Thermal, on a computer and on a phone;
19
+ ?look=google gives Google Slides' look and ?look=thermal brings Thermal back, each remembered in this browser */
20
+ { const q = new URLSearchParams(location.search); let l = 'thermal';
21
+ try { if (q.has('look')) localStorage.setItem('glideshow-look', q.get('look') || 'google'); l = localStorage.getItem('glideshow-look') || l; } catch {}
22
+ if (q.has('look')) l = q.get('look') || 'google';
23
+ if (l !== 'google') document.documentElement.dataset.look = l; }</script>
24
+ <link rel="stylesheet" href="/editor/looks/thermal.css">
25
+ <link rel="stylesheet" href="/editor/looks/thermal-phone.css">
26
+ <style id="ed-deck-theme-live"></style>
27
+ </head>
28
+ <body>
29
+ <header id="ed-bar">
30
+ <div id="ed-top">
31
+ <a id="ed-app-icon" href="/" title="Glideshow home (alpha)"><img src="/editor/glideshow.svg" alt="Glideshow"><span class="stage">alpha</span></a>
32
+ <div id="ed-title-col">
33
+ <div id="ed-title-row"><input id="ed-title" spellcheck="false" aria-label="Deck title"><input type="date" id="ed-date" title="The deck's date (the talk's or meeting's)" aria-label="Deck date"><span id="ed-status"></span></div>
34
+
35
+ <nav id="ed-menubar">
36
+ <button data-menu="File">File</button><button data-menu="Edit">Edit</button><button data-menu="View">View</button>
37
+ <button data-menu="Insert">Insert</button><button data-menu="Format">Format</button><button data-menu="Slide">Slide</button>
38
+ <button data-menu="Arrange">Arrange</button><button data-menu="Tools">Tools</button><button data-menu="Help">Help</button>
39
+ </nav>
40
+ </div>
41
+ <button id="ed-comments-btn" class="icon-btn" title="Comments (⌘⌥M adds one)"><span class="icon">comment</span><b id="ed-comments-n"></b></button>
42
+ <div id="ed-slideshow" class="split">
43
+ <button id="ed-present" title="Present from this slide">Slideshow</button><button id="ed-present-menu" title="Slideshow options" aria-label="Slideshow options"><span class="icon">arrow_drop_down</span></button>
44
+ </div>
45
+ <div id="ed-people" hidden></div>
46
+ <button id="ed-share" title="Share"><span class="icon">lock</span>Share</button>
47
+ </div>
48
+ <div id="ed-toolbar">
49
+ <label id="ed-menus" title="Search the menus (⌥/)"><span class="icon">search</span><input placeholder="Menus" aria-label="Search the menus"></label>
50
+ <div class="grp" data-i="0">
51
+ <button id="ed-new-slide-btn" title="New slide (⌃M)"><span class="icon">add</span></button><button id="ed-new-slide-more" class="drop" title="New slide with layout"><span class="icon">arrow_drop_down</span></button>
52
+
53
+ <button id="ed-undo" title="Undo (⌘Z)"><span class="icon">undo</span></button>
54
+ <button id="ed-redo" title="Redo (⇧⌘Z)"><span class="icon">redo</span></button>
55
+ <button id="ed-print" title="Print (⌘P)"><span class="icon">print</span></button>
56
+ <button id="ed-paint-format" title="Paint format (double-click to keep it on)"><span class="icon">format_paint</span></button>
57
+ <button id="ed-zoom" class="zoom-pick" title="Zoom"><span class="icon zoom-glyph">zoom_in</span><span class="zoom-label">Fit</span><span class="icon">arrow_drop_down</span></button>
58
+ </div>
59
+ <div class="grp" data-i="1">
60
+ <button id="ed-select-tool" class="on" title="Select"><span class="icon">arrow_selector_tool</span></button>
61
+ <button id="ed-add-text" title="Text box"><span class="icon">text_fields</span></button>
62
+ <button id="ed-add-shape" title="Shape"><span class="icon">shapes</span></button>
63
+ <button id="ed-add-line" title="Line"><span class="icon">pen_size_2</span></button>
64
+ <input type="file" id="ed-pptx-file" accept=".pptx" hidden><input type="file" id="ed-file" accept="image/*" multiple hidden>
65
+ <input type="file" id="ed-video-file" accept="video/mp4,video/webm,video/quicktime,.mov" hidden>
66
+ </div>
67
+ <div class="grp sel-only" data-i="2">
68
+ <button class="swatch" data-for="fill" title="Fill color"><span class="icon">format_color_fill</span><i class="bar"></i></button><input type="color" class="color-input" id="ed-fill">
69
+ <button class="swatch" data-for="stroke" title="Border color"><span class="icon">border_color</span><i class="bar"></i></button><input type="color" class="color-input" id="ed-stroke">
70
+ <button id="ed-sw" title="Border weight"><span class="icon">line_weight</span></button>
71
+ <button id="ed-dash" title="Border dash"><span class="icon">line_style</span></button>
72
+ <button id="ed-lstart" class="line-only" title="Line start"><span class="icon">line_start</span></button>
73
+ <button id="ed-lend" class="line-only" title="Line end"><span class="icon">line_end_arrow</span></button>
74
+ </div>
75
+ <div class="grp sel-only" data-i="3">
76
+ <button id="ed-font" class="font-pick" title="Font"><span class="font-name">Default</span><span class="icon">arrow_drop_down</span></button>
77
+ <button id="ed-size-down" title="Decrease font size (⇧⌘,)"><span class="icon">remove</span></button>
78
+ <input type="number" id="ed-size" min="8" max="400" step="2" title="Font size">
79
+ <button id="ed-size-up" title="Increase font size (⇧⌘.)"><span class="icon">add</span></button>
80
+ <button id="ed-bold" title="Bold (⌘B)"><span class="icon">format_bold</span></button>
81
+ <button id="ed-italic" title="Italic (⌘I)"><span class="icon">format_italic</span></button>
82
+ <button id="ed-underline" title="Underline (⌘U)"><span class="icon">format_underlined</span></button>
83
+ <button class="swatch" data-for="color" title="Text color"><span class="icon">format_color_text</span><i class="bar"></i></button><input type="color" class="color-input" id="ed-color">
84
+ <button class="swatch" data-for="hilite" title="Highlight color"><span class="icon">ink_highlighter</span><i class="bar"></i></button><input type="color" class="color-input" id="ed-hilite" value="#ffff00">
85
+ <button id="ed-link" title="Insert link (⌘K)"><span class="icon">link</span></button>
86
+ </div>
87
+ <div class="grp sel-only" data-i="4">
88
+ <button id="ed-align" class="dropdown" title="Align"><span class="icon">format_align_left</span><span class="icon caret">arrow_drop_down</span></button>
89
+ <button id="ed-lh" title="Line spacing"><span class="icon">format_line_spacing</span></button>
90
+ <button id="ed-ul" title="Bulleted list (⇧⌘8)"><span class="icon">format_list_bulleted</span></button>
91
+ <button id="ed-ol" title="Numbered list (⇧⌘7)"><span class="icon">format_list_numbered</span></button>
92
+ <button id="ed-indent-less" title="Decrease indent (⌘[)"><span class="icon">format_indent_decrease</span></button>
93
+ <button id="ed-indent-more" title="Increase indent (⌘])"><span class="icon">format_indent_increase</span></button>
94
+ </div>
95
+ <div class="grp sel-only" data-i="5">
96
+ <button id="ed-clear-fmt" title="Clear formatting (⌘\)"><span class="icon">format_clear</span></button>
97
+ <button id="ed-crop" class="image-only" title="Crop image"><span class="icon">crop</span></button>
98
+ <button id="ed-replace-image" class="text image-only" title="Replace image">Replace image</button><input type="file" id="ed-replace-file" accept="image/*" hidden>
99
+ <button id="ed-format-options" class="text" title="Format options">Format options</button>
100
+ <button id="ed-animate" class="text" title="Animate">Animate</button>
101
+ </div>
102
+ <div class="grp nosel-only" data-i="6">
103
+ <button id="ed-background" class="text" data-for="bg" title="Background">Background</button><input type="color" class="color-input" id="ed-bg">
104
+ <button id="ed-layout-btn" class="text" title="Layout">Layout</button>
105
+ <button id="ed-theme-btn" class="text" title="Theme">Theme</button>
106
+ <button id="ed-transition" class="text" title="Transition">Transition</button>
107
+ </div>
108
+ <div id="ed-more-wrap">
109
+ <button id="ed-more" title="More" aria-label="More"><span class="icon">more_vert</span></button>
110
+ <div id="ed-more-pop"></div>
111
+ </div>
112
+ <button id="ed-collapse" title="Hide the menus (⌃⇧F)"><span class="icon">keyboard_arrow_up</span></button>
113
+ </div>
114
+ </header>
115
+ <main>
116
+ <aside id="ed-side">
117
+ <div id="ed-thumbs"></div>
118
+ <div id="ed-film-foot"><button id="ed-grid-toggle" title="Grid view (⌘⌥1)"><span class="icon">grid_view</span></button><button id="ed-film-toggle" title="Hide the filmstrip"><span class="icon">chevron_left</span></button>
119
+ <span id="ed-grid-zoom"><button id="ed-grid-out" title="Smaller"><span class="icon">remove</span></button><span class="icon">zoom_in</span><button id="ed-grid-in" title="Larger"><span class="icon">add</span></button></span></div>
120
+ <div id="ed-slide-ops" hidden>
121
+ <button id="ed-new-slide" title="New slide">+ Slide</button>
122
+ <button id="ed-dup-slide" title="Duplicate slide">Dup</button>
123
+ <button id="ed-up-slide" title="Move up">↑</button>
124
+ <button id="ed-down-slide" title="Move down">↓</button>
125
+ <button id="ed-del-slide" title="Delete slide">Delete</button>
126
+ </div>
127
+ </aside>
128
+ <div id="ed-work">
129
+ <div id="ed-load-bar" hidden><span class="icon">speed</span><span id="ed-load-what"></span><button id="ed-load-close" title="Dismiss"><span class="icon">close</span></button></div>
130
+ <div id="ed-merge-bar" hidden><span class="icon">call_merge</span><span id="ed-merge-what"></span><button id="ed-merge-history" class="tonal">Version history</button><button id="ed-merge-close" title="Dismiss"><span class="icon">close</span></button></div>
131
+ <div id="ed-theme-bar" hidden><span id="ed-theme-what"></span><button id="ed-theme-reapply" class="tonal">Reapply to all</button><button id="ed-theme-done" title="Close"><span class="icon">close</span></button></div>
132
+ <div id="ed-stage-wrap">
133
+ <canvas id="ed-ruler-h" class="ruler"></canvas><canvas id="ed-ruler-v" class="ruler"></canvas>
134
+ <div id="ed-stage">
135
+ <div id="ed-frame" class="reveal">
136
+ <div id="ed-canvas" class="slides"><div id="ed-rules"></div><div id="ed-pins"></div><div id="ed-flash"></div><div id="ed-people-on"></div><div id="ed-ui"></div><div id="ed-guides"></div></div>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ <div id="ed-dock" role="toolbar" aria-label="Insert">
141
+ <button data-do="select" class="on" title="Select"><span class="icon">arrow_selector_tool</span></button>
142
+ <button data-do="text" title="Text box"><span class="icon">text_fields</span></button>
143
+ <button data-do="shape" title="Shape"><span class="icon">shapes</span></button>
144
+ <button data-do="line" title="Line"><span class="icon">pen_size_2</span></button>
145
+ <button data-do="image" title="Image"><span class="icon">image</span></button>
146
+ <i class="sep"></i>
147
+ <button data-do="table" title="Table"><span class="icon">table_chart</span></button>
148
+ <button data-do="code" title="Code block"><span class="icon">code</span></button>
149
+ <button data-do="equation" title="Equation"><span class="icon">function</span></button>
150
+ <i class="sep"></i>
151
+ <button data-do="note" title="Note for AI"><span class="icon">sticky_note_2</span></button>
152
+ </div>
153
+ <div id="ed-splitter" title="Drag to resize speaker notes"></div>
154
+ <div id="ed-ai-notes" hidden></div>
155
+ <div id="ed-notes" contenteditable="true" spellcheck="true" data-placeholder="Click to add speaker notes" aria-label="Speaker notes"></div>
156
+ </div>
157
+ <aside id="ed-comments" class="panel" hidden>
158
+ <header><span class="icon head-icon">comment</span><span>Comments</span><button id="ed-cm-close" title="Close"><span class="icon">close</span></button></header>
159
+ <div class="cm-tools"><button id="ed-cm-add" class="tonal"><span class="icon">add_comment</span>Comment</button><label class="cm-res"><input type="checkbox" id="ed-cm-resolved"> Show resolved</label></div>
160
+ <div id="ed-cm-list"></div>
161
+ </aside>
162
+ <aside id="ed-history" class="panel" hidden>
163
+ <header><span class="icon head-icon">history</span><span>Version history</span><button id="ed-hist-close" title="Close"><span class="icon">close</span></button></header>
164
+ <select id="ed-hist-scope" aria-label="Which versions"><option value="">All versions</option><option value="slide">This slide</option></select>
165
+ <div id="ed-hist-list"></div>
166
+ <div class="hist-foot"><button id="ed-hist-restore" class="tonal" hidden>Restore this version</button><label class="hist-hl"><input type="checkbox" id="ed-hist-highlight" checked> Highlight changes</label></div>
167
+ </aside>
168
+ <aside id="ed-theme" class="panel" hidden>
169
+ <header><span class="icon head-icon">palette</span><span>Themes</span><button id="ed-theme-close" title="Close"><span class="icon">close</span></button></header>
170
+ <div id="ed-theme-list"></div>
171
+ <details open><summary>Customize</summary>
172
+ <label>Font<select id="ed-th-font"></select></label>
173
+ <div class="row"><label>Title size<span class="unit"><input type="number" id="ed-th-title-size" min="8" max="200" class="short"> pt</span></label><label>Body size<span class="unit"><input type="number" id="ed-th-body-size" min="6" max="200" class="short"> pt</span></label></div>
174
+ <div class="row between"><span class="lbl">Title color</span><input type="color" id="ed-th-title" class="color-chip"></div>
175
+ <div class="row between"><span class="lbl">Text color</span><input type="color" id="ed-th-text" class="color-chip"></div>
176
+ <div class="row between"><span class="lbl">Background</span><input type="color" id="ed-th-bg" class="color-chip"></div>
177
+ </details>
178
+ </aside>
179
+ <aside id="ed-trans" class="panel" hidden>
180
+ <header><span class="icon head-icon motion">animation</span><span>Motion</span><button id="ed-trans-close" title="Close"><span class="icon">close</span></button></header>
181
+ <h3>Slide Transition</h3>
182
+ <details class="item"><summary><span class="thumbmark"></span><span id="ed-t-name">None</span></summary>
183
+ <label>Type<select id="ed-t-kind"></select></label>
184
+ <div class="range">Slow<input type="range" id="ed-t-speed" min="0" max="2" step="1" value="1">Fast</div>
185
+ <div class="actions"><button id="ed-t-all">Apply to all slides</button></div>
186
+ </details>
187
+ <h3>Object Animations</h3>
188
+ <div id="ed-anim-list"></div>
189
+ <div class="actions pad"><button id="ed-anim-add" class="add"><span class="icon">add</span>Add animation</button></div>
190
+ <div class="actions pad"><button id="ed-anim-play" class="play">Play</button></div>
191
+ </aside>
192
+ <aside id="ed-fmt" class="panel" hidden>
193
+ <header><span class="icon head-icon fmt">format_paint</span><span>Format options</span><button id="ed-fmt-close" title="Close"><span class="icon">close</span></button></header>
194
+ <details><summary>Size &amp; Rotation</summary>
195
+ <h4>Size</h4>
196
+ <div class="row"><label>Width<span class="unit"><input type="number" id="ed-f-w" min="0.01" step="0.01"> in</span></label><label>Height<span class="unit"><input type="number" id="ed-f-h" min="0" step="0.01"> in</span></label></div>
197
+ <label class="chk"><input type="checkbox" id="ed-f-lock"> Lock aspect ratio</label>
198
+ <h4>Rotate</h4>
199
+ <div class="row"><label>Angle<span class="unit"><input type="number" id="ed-f-rot" step="1" class="short"> °</span></label>
200
+ <label>90°<button id="ed-f-rotr" title="Rotate 90°"><span class="icon">rotate_90_degrees_cw</span></button></label>
201
+ <label>Flip<span><button id="ed-f-fliph" title="Flip horizontally"><span class="icon">flip</span></button><button id="ed-f-flipv" title="Flip vertically"><span class="icon" style="rotate: 90deg">flip</span></button></span></label></div>
202
+ </details>
203
+ <details><summary>Position</summary>
204
+ <label>From<select id="ed-f-from"><option value="tl">Top left</option><option value="c">Center</option></select></label>
205
+ <div class="row"><label>X<span class="unit"><input type="number" id="ed-f-x" step="0.01"> in</span></label><label>Y<span class="unit"><input type="number" id="ed-f-y" step="0.01"> in</span></label></div>
206
+ </details>
207
+ <details class="text-only"><summary>Text fitting</summary>
208
+ <h4>Resize</h4>
209
+ <div id="ed-f-fit" class="radios"><label class="chk"><input type="radio" name="ed-f-fit" value="none"> Don't resize</label>
210
+ <label class="chk"><input type="radio" name="ed-f-fit" value="shrink"> Shrink text to fit the shape</label>
211
+ <label class="chk"><input type="radio" name="ed-f-fit" value="resize"> Resize the shape to fit text</label></div>
212
+ <h4>Padding</h4>
213
+ <div class="row"><label>Top<span class="unit"><input type="number" id="ed-f-pt" min="0" step="0.01"> in</span></label><label>Bottom<span class="unit"><input type="number" id="ed-f-pb" min="0" step="0.01"> in</span></label></div>
214
+ <div class="row"><label>Left<span class="unit"><input type="number" id="ed-f-pl" min="0" step="0.01"> in</span></label><label>Right<span class="unit"><input type="number" id="ed-f-pr" min="0" step="0.01"> in</span></label></div>
215
+ </details>
216
+ <details class="img-only"><summary>Adjustments</summary>
217
+ <label>Opacity<span class="unit slider"><input type="range" id="ed-f-opacity" min="0" max="100" step="1"><input type="number" id="ed-f-opacity-n" min="0" max="100" class="short"> %</span></label>
218
+ <div class="row"><button id="ed-f-adj-reset" class="outlined">Reset</button></div>
219
+ </details>
220
+ <details><summary>Drop shadow<input type="checkbox" id="ed-f-shadow" title="Drop shadow"></summary>
221
+ <div class="row between"><span class="lbl">Color</span><input type="color" id="ed-f-sh-color" class="color-chip"></div>
222
+ <label>Opacity<span class="unit slider"><input type="range" id="ed-f-sh-opacity" min="0" max="100" step="1"><input type="number" id="ed-f-sh-opacity-n" min="0" max="100" class="short"> %</span></label>
223
+ <label>Angle<span class="unit slider"><input type="range" id="ed-f-sh-angle" min="0" max="359" step="1"><input type="number" id="ed-f-sh-angle-n" min="0" max="359" class="short"> °</span></label>
224
+ <label>Distance<span class="unit slider"><input type="range" id="ed-f-sh-distance" min="0" max="100" step="1"><input type="number" id="ed-f-sh-distance-n" min="0" max="100" class="short"> px</span></label>
225
+ <label>Blur radius<span class="unit slider"><input type="range" id="ed-f-sh-blur" min="0" max="100" step="1"><input type="number" id="ed-f-sh-blur-n" min="0" max="100" class="short"> px</span></label>
226
+ </details>
227
+ <details class="video-only"><summary>Video playback</summary>
228
+ <label class="chk"><input type="checkbox" id="ed-f-v-auto"> Play automatically when the slide appears</label>
229
+ <label class="chk"><input type="checkbox" id="ed-f-v-mute"> Mute audio</label>
230
+ <label class="chk"><input type="checkbox" id="ed-f-v-loop"> Loop</label>
231
+ <div class="row"><label>Start at<input id="ed-f-v-start" class="short" placeholder="0:00"></label><label>End at<input id="ed-f-v-end" class="short" placeholder="end"></label></div>
232
+ </details>
233
+ <details><summary>Alt Text</summary>
234
+ <label>Description<textarea id="ed-f-alt" rows="3" placeholder="Describe the object for screen readers"></textarea></label>
235
+ </details>
236
+ </aside>
237
+ </main>
238
+ <div id="ed-show" hidden></div>
239
+ <div id="ed-find" class="find" hidden role="dialog" aria-label="Find and replace">
240
+ <header><h2>Find and replace</h2><button class="find-close" title="Close"><span class="icon">close</span></button></header>
241
+ <div class="find-row"><input id="ed-find-q" placeholder="Find" aria-label="Find"><span class="find-count"></span></div>
242
+ <input id="ed-find-r" placeholder="Replace with" aria-label="Replace with">
243
+ <label class="chk"><input type="checkbox" id="ed-find-case"> Match case</label>
244
+ <label class="chk"><input type="checkbox" id="ed-find-re"> Use regular expressions (e.g. \n for newline, \t for tab)</label>
245
+ <label class="chk"><input type="checkbox" id="ed-find-dia" checked> Ignore diacritics (e.g. ä = a, E = É)</label>
246
+ <div class="find-actions"><button id="ed-find-one">Replace</button><button id="ed-find-all">Replace all</button>
247
+ <button id="ed-find-prev">Previous</button><button id="ed-find-next" class="tonal">Next</button></div>
248
+ </div>
249
+ <div id="ed-shortcuts" class="backdrop" hidden>
250
+ <div class="dialog" role="dialog" aria-label="Keyboard shortcuts">
251
+ <header><h2>Keyboard shortcuts</h2><button class="sc-close" title="Close"><span class="icon">close</span></button></header>
252
+ <label class="sc-search"><span class="icon">search</span><input type="search" placeholder="Search" aria-label="Search keyboard shortcuts"></label>
253
+ <div class="sc-body"></div>
254
+ </div>
255
+ </div>
256
+ <div id="ed-bgdlg" class="backdrop" hidden>
257
+ <div class="dialog bg-dialog" role="dialog" aria-label="Background">
258
+ <header><h2>Background</h2><button class="sc-close" title="Close"><span class="icon">close</span></button></header>
259
+ <div class="bg-row"><span>Color</span><button id="ed-bg-color" class="bg-swatch" data-for="bg" title="Color"><span class="chip"></span><span class="icon">arrow_drop_down</span></button></div>
260
+ <div class="bg-row"><span>Image</span><button id="ed-bg-image" class="outlined">Choose image</button><input type="file" id="ed-bg-file" accept="image/*" hidden></div>
261
+ <div class="bg-row"><span>Video</span><button id="ed-bg-video" class="outlined">Choose video</button><input type="file" id="ed-bg-vfile" accept="video/mp4,video/webm,video/quicktime,.mov" hidden></div>
262
+ <div class="bg-row"><span>Web page</span><button id="ed-bg-page" class="outlined">Choose page</button></div>
263
+ <div class="bg-row"><span>Reset to theme</span><button id="ed-bg-reset" class="outlined">Reset</button></div>
264
+ <footer><button id="ed-bg-theme" class="outlined">Add to theme</button><button id="ed-bg-done" class="done">Done</button></footer>
265
+ </div>
266
+ </div>
267
+ <div id="ed-spacedlg" class="backdrop" hidden>
268
+ <div class="dialog bg-dialog" role="dialog" aria-label="Custom spacing">
269
+ <header><h2>Custom spacing</h2><button class="sc-close" title="Close"><span class="icon">close</span></button></header>
270
+ <div class="dlg-sub">Paragraph spacing</div>
271
+ <div class="bg-row"><span>Before</span><input type="number" id="ed-sp-before" min="0" step="1"></div>
272
+ <div class="bg-row"><span>After</span><input type="number" id="ed-sp-after" min="0" step="1"></div>
273
+ <div class="dlg-sub">Line spacing</div>
274
+ <div class="bg-row"><span>Line spacing</span><input type="number" id="ed-sp-line" min="0.5" max="5" step="0.05"></div>
275
+ <footer><button id="ed-sp-cancel" class="outlined">Cancel</button><button id="ed-sp-apply" class="done">Apply</button></footer>
276
+ </div>
277
+ </div>
278
+ <div id="ed-chars" class="backdrop" hidden>
279
+ <div class="dialog chars-dialog" role="dialog" aria-label="Insert special characters">
280
+ <header><h2>Insert special characters</h2><button class="sc-close" title="Close"><span class="icon">close</span></button></header>
281
+ <div class="chars-body">
282
+ <div class="chars-left"><select id="ed-chars-cat" aria-label="Category"></select><div id="ed-chars-grid" class="chars-grid"></div></div>
283
+ <div class="chars-right">
284
+ <label class="chars-search"><span class="icon">search</span><input type="search" id="ed-chars-q" placeholder="Search: arrow, \alpha or 2192" aria-label="Search characters"></label>
285
+ <div id="ed-chars-pick" class="chars-pick"></div>
286
+ </div>
287
+ </div>
288
+ </div>
289
+ </div>
290
+ <div id="ed-icons" class="backdrop" hidden>
291
+ <div class="dialog icons-dialog" role="dialog" aria-label="Insert icon">
292
+ <header><h2>Insert icon</h2><label class="chars-search"><span class="icon">search</span><input type="search" id="ed-icons-q" placeholder="Search icons" aria-label="Search icons"></label><button class="sc-close" title="Close"><span class="icon">close</span></button></header>
293
+ <div id="ed-icons-grid" class="icons-grid"></div>
294
+ </div>
295
+ </div>
296
+ <div id="ed-draftdlg" class="backdrop" hidden>
297
+ <div class="dialog bg-dialog" role="dialog" aria-label="Not saved">
298
+ <header><h2>This presentation won't be saved</h2><button class="sc-close" title="Close"><span class="icon">close</span></button></header>
299
+ <div class="bg-row"><span>It has no title. Give it one to keep it in your library; otherwise it is discarded when you leave.</span></div>
300
+ <footer><button id="ed-draft-discard" class="outlined">Discard</button><button id="ed-draft-name" class="primary">Add a title</button></footer>
301
+ </div>
302
+ </div>
303
+ <div id="ed-sharedlg" class="backdrop" hidden>
304
+
305
+ <div class="dialog bg-dialog share-dialog" role="dialog" aria-label="Share">
306
+ <header><h2>Share “<span id="ed-share-title"></span>”</h2><button class="sc-close" title="Close"><span class="icon">close</span></button></header>
307
+ <label class="bg-row"><span><b>Share a link</b><small id="ed-share-hint">The link always shows the latest version of the deck. It isn't listed anywhere, and search engines and AI crawlers are asked to stay out.</small></span>
308
+ <input type="checkbox" id="ed-share-on" class="switch"></label>
309
+ <div class="bg-row share-link" id="ed-share-linkrow" hidden><input id="ed-share-url" class="url-in" readonly aria-label="Link"><button id="ed-share-copy" class="outlined"><span class="icon">link</span>Copy link</button></div>
310
+ <div class="share-access" id="ed-share-accessrow" hidden>
311
+ <label class="bg-row"><span>Who can open it</span><select id="ed-share-access"><option value="link">Anyone with the link</option><option value="people">Only people I add</option></select></label>
312
+ <div class="bg-row share-people" id="ed-share-peoplerow" hidden><div class="people-box" id="ed-share-chips"><input id="ed-share-people" type="text" autocomplete="off" spellcheck="false" placeholder="Emails, @harvard.edu for everyone there, or GitHub usernames" aria-label="People"></div>
313
+ <small>They sign in with Google or GitHub to open it.</small></div>
314
+ <div class="bg-row share-people share-editors" id="ed-share-editrow"><span>Can edit</span><div class="people-box" id="ed-share-echips"><input id="ed-share-editors" type="text" autocomplete="off" spellcheck="false" placeholder="Emails, @domain or GitHub usernames of people who can edit" aria-label="Can edit"></div>
315
+ <small>They open the deck from the same link and edit it with you, live. They can't add scripts or change the theme.</small></div>
316
+ <label class="bg-row"><span>Link expires</span><span class="share-exp"><input type="date" id="ed-share-expires" aria-label="Expires"><button id="ed-share-noexp" class="icon" title="Never expires">close</button></span></label>
317
+ </div>
318
+ <div class="bg-row share-status" id="ed-share-status"></div>
319
+ <footer><button id="ed-share-done" class="done">Done</button></footer>
320
+ </div>
321
+ </div>
322
+ <div id="ed-livedlg" class="backdrop" hidden>
323
+
324
+ <div class="dialog bg-dialog live-dialog" role="dialog" aria-label="Code">
325
+ <header><h2 id="ed-code-title">Live box</h2><button class="sc-close" title="Close"><span class="icon">close</span></button></header>
326
+ <div class="bg-row"><small id="ed-code-help"></small></div>
327
+ <div class="bg-row live-code"><textarea id="ed-live-code" spellcheck="false" aria-label="Code"></textarea></div>
328
+ <footer><button id="ed-live-cancel" class="outlined">Cancel</button><button id="ed-live-ok" class="done">Save</button></footer>
329
+ </div>
330
+ </div>
331
+ <div id="ed-urldlg" class="backdrop" hidden>
332
+
333
+ <div class="dialog bg-dialog" role="dialog" aria-label="Insert by URL">
334
+ <header><h2 id="ed-url-title">Embed a web page</h2><button class="sc-close" title="Close"><span class="icon">close</span></button></header>
335
+ <div class="bg-row"><input type="url" id="ed-url-in" class="url-in" placeholder="Paste a URL" aria-label="URL"></div>
336
+ <footer><button id="ed-url-cancel" class="outlined">Cancel</button><button id="ed-url-ok" class="done">Insert</button></footer>
337
+ </div>
338
+ </div>
339
+ <script src="/editor/data/themes.js"></script>
340
+ <script src="/editor/data/symbols.js"></script>
341
+ <script src="/editor/deck/shapes.js"></script>
342
+ <!-- the editor, in load order (classic scripts sharing one global scope) -->
343
+ <script src="/editor/js/state.js"></script>
344
+ <script src="/editor/js/geometry.js"></script>
345
+ <script src="/editor/lib/merge.js"></script>
346
+ <script src="/editor/js/storage.js"></script>
347
+ <script src="/editor/js/render.js"></script>
348
+ <script src="/editor/js/boxes.js"></script>
349
+ <script src="/editor/js/theme.js"></script>
350
+ <script src="/editor/js/objects.js"></script>
351
+ <script src="/editor/js/text.js"></script>
352
+ <script src="/editor/js/pointer.js"></script>
353
+ <script src="/editor/js/keyboard.js"></script>
354
+ <script src="/editor/js/slides.js"></script>
355
+ <script src="/editor/js/toolbar.js"></script>
356
+ <script src="/editor/js/arrange.js"></script>
357
+ <script src="/editor/js/format-panel.js"></script>
358
+ <script src="/editor/js/motion.js"></script>
359
+ <script src="/editor/js/notes.js"></script>
360
+ <script src="/editor/js/find.js"></script>
361
+ <script src="/editor/js/file.js"></script>
362
+ <script src="/editor/js/comments.js"></script>
363
+ <script src="/editor/js/live.js"></script>
364
+ <script src="/editor/js/people.js"></script>
365
+ <script src="/editor/js/shortcuts.js"></script>
366
+ <script src="/editor/js/dialogs.js"></script>
367
+ <script src="/editor/js/insert.js"></script>
368
+ <script src="/editor/js/menus.js"></script>
369
+ <script src="/editor/js/context-menus.js"></script>
370
+ <script src="/editor/js/guides.js"></script>
371
+ <script src="/editor/js/spacing.js"></script>
372
+ <script src="/editor/js/draw.js"></script>
373
+ <script src="/editor/js/menubar.js"></script>
374
+ <script src="/editor/js/dock.js"></script>
375
+ <script src="/editor/js/toolbar-groups.js"></script>
376
+ <script src="/editor/js/theme-toggle.js"></script>
377
+ <script src="/editor/js/phone-view.js"></script>
378
+ <script src="/editor/js/phone-edit.js"></script>
379
+ <script src="/editor/js/phone-sheets.js"></script>
380
+ <script src="/editor/js/phone-comments.js"></script>
381
+ <script src="/editor/js/phone-touch.js"></script>
382
+ <script src="/editor/js/boot.js"></script>
383
+
384
+
385
+ </body>
386
+ </html>
@@ -0,0 +1,122 @@
1
+ /* Arrange → Align: to the selection's bounds, or to the slide when one object is selected (as Slides).
2
+ `where` is 0 (left/top), .5 (center/middle) or 1 (right/bottom); bounds are as drawn, so rotated boxes line up visually. */
3
+ function alignSel(axis, where) {
4
+ const [p, s, size] = axis === 'h' ? ['x', 'w', W] : ['y', 'h', H];
5
+ const g = sel.size === 1 ? { [p]: 0, [s]: size } : bbox([...sel].map(aabb));
6
+ sel.forEach(b => { const a = aabb(b), o = geo(b), d = g[p] + (g[s] - a[s]) * where - a[p]; setGeo(b, { ...o, [p]: o[p] + d }); });
7
+ drawOverlay(); commit();
8
+ }
9
+ /* Arrange → Distribute: equal gaps between three or more objects, keeping the outermost ones in place. */
10
+ function distributeSel(axis) {
11
+ const [p, s] = axis === 'h' ? ['x', 'w'] : ['y', 'h'];
12
+ const items = [...sel].map(b => [b, aabb(b)]).sort((a, c) => (a[1][p] + a[1][s] / 2) - (c[1][p] + c[1][s] / 2));
13
+ const first = items[0][1], last = items.at(-1)[1], used = items.reduce((t, [, a]) => t + a[s], 0);
14
+ const gap = (last[p] + last[s] - first[p] - used) / (items.length - 1);
15
+ let at = first[p];
16
+ for (const [b, a] of items) { const o = geo(b); setGeo(b, { ...o, [p]: o[p] + at - a[p] }); at += a[s] + gap; }
17
+ drawOverlay(); commit();
18
+ }
19
+ function rotateSel(d) { sel.forEach(b => { if (!isLine(b)) setRot(b, rot(b) + d); }); drawOverlay(); commit(); }
20
+ function flipSel(axis) {
21
+ sel.forEach(b => {
22
+ const f = new Set(b.dataset.flip || ''); f.has(axis) ? f.delete(axis) : f.add(axis);
23
+ const v = ['h', 'v'].filter(a => f.has(a)).join(''); if (v) b.dataset.flip = v; else delete b.dataset.flip; shadeBox(b);
24
+ });
25
+ drawOverlay(); commit();
26
+ }
27
+ /* Slide → Skip slide: reveal.js leaves data-visibility="hidden" slides out of the slideshow. */
28
+ function skipSlides() {
29
+ const on = !pickedIdx().every(i => slides[i].dataset.visibility === 'hidden');
30
+ pickedIdx().forEach(i => { if (on) slides[i].dataset.visibility = 'hidden'; else delete slides[i].dataset.visibility; });
31
+ commit();
32
+ }
33
+ /* Arrange → Group (⌥⌘G): the selection moves into one `.box.group` sized to its bounds, members keep
34
+ their geometry relative to it. Ungroup (⇧⌥⌘G) puts them back, carrying the group's rotation. */
35
+ function groupSel() {
36
+ const items = boxes().filter(b => sel.has(b));
37
+ if (items.length < 2) return;
38
+ stopEdit();
39
+ const g = bbox(items.map(aabb)), grp = div('box group'); grp.id = uid(); setGeo(grp, g);
40
+ items.at(-1).after(grp);
41
+ for (const b of items) { const o = geo(b); setGeo(b, { ...o, x: o.x - g.x, y: o.y - g.y }); grp.append(b); }
42
+ sel = new Set([grp]); drawOverlay(); updateToolbar(); commit();
43
+ }
44
+ function ungroupSel() {
45
+ const out = [];
46
+ for (const grp of boxes().filter(b => sel.has(b) && b.classList.contains('group'))) {
47
+ const g = geo(grp), r = rot(grp), t = r * Math.PI / 180, cx = g.x + g.w / 2, cy = g.y + g.h / 2;
48
+ for (const c of kids(grp)) {
49
+ const o = geo(c), lx = g.x + o.x + o.w / 2 - cx, ly = g.y + o.y + o.h / 2 - cy;
50
+ const nx = cx + lx * Math.cos(t) - ly * Math.sin(t), ny = cy + lx * Math.sin(t) + ly * Math.cos(t);
51
+ setGeo(c, { x: nx - o.w / 2, y: ny - o.h / 2, w: o.w, h: o.h }); if (r) setRot(c, rot(c) + r);
52
+ grp.before(c); out.push(c);
53
+ }
54
+ grp.remove();
55
+ }
56
+ if (!out.length) return;
57
+ sel = new Set(out); drawOverlay(); updateToolbar(); commit();
58
+ }
59
+ function toFront() { boxes().filter(b => sel.has(b)).forEach(b => slides[cur].append(b)); commit(); }
60
+ function toBack() { boxes().filter(b => sel.has(b)).reverse().forEach(b => slides[cur].prepend(b)); commit(); }
61
+ function flipLines() { sel.forEach(b => { if (isLine(b)) { b.dataset.dir = b.dataset.dir === 'up' ? 'down' : 'up'; renderShape(b); } }); commit(); }
62
+ $('#ed-bg').oninput = e => { slides[cur].dataset.backgroundColor = e.target.value; paintBg(frame, slides[cur]); syncBgDialog(); };
63
+ $('#ed-bg').onchange = commit;
64
+ $('#ed-title').oninput = scheduleSave;
65
+ $('#ed-date').onchange = scheduleSave;
66
+
67
+ /* Present in place, as Slides but windowed (like Keynote's Play in Window): the real deck.html fills the
68
+ editor window over the chrome, so it behaves exactly like the shared deck. Esc comes back to the slide
69
+ you stopped on. Fullscreen is opt-in (F while presenting, or Slideshow ▾ → Present fullscreen). */
70
+ async function flushSave() { if (saveTimer) { clearTimeout(saveTimer); await save(); } }
71
+ async function present(from = cur, opts = {}) {
72
+ await flushSave();
73
+ if (savedCode !== currentCode() && !(/** @type {any} */ (window.GLIDESHOW_CLOUD)?.collab)) await save(); // the deck's slideshow code is this editor's (storage.js deckCode)
74
+ stopEdit(); closeMenu();
75
+ const ov = $('#ed-show'), f = document.createElement('iframe');
76
+ f.src = 'deck.html?embedded#/' + hv(from).join('/'); f.allow = 'fullscreen';
77
+ ov.replaceChildren(f); ov.hidden = false;
78
+ f.addEventListener('load', () => f.contentWindow.focus());
79
+ if (opts.fullscreen) ov.requestFullscreen?.().catch(() => {});
80
+ }
81
+ function endPresent(h, v) {
82
+ const ov = $('#ed-show');
83
+ if (ov.hidden) return;
84
+ ov.hidden = true; ov.replaceChildren();
85
+ if (document.fullscreenElement) document.exitFullscreen();
86
+ if (h != null) go(indexOf(h, v || 0));
87
+ }
88
+ const presenting = () => !$('#ed-show').hidden;
89
+ addEventListener('message', e => {
90
+ if (e.data?.slides === 'exit') endPresent(e.data.h, e.data.v);
91
+ if (e.data?.slides === 'note') addNote(indexOf(e.data.h, e.data.v), e.data.x, e.data.y, e.data.text); // M while presenting
92
+ });
93
+ /* A note for the AI (AGENTS.md): yellow in the editor, hidden in the deck, stamped with its time. */
94
+ function addNote(i, x, y, text) {
95
+ const s = slides[i]; if (!s) return;
96
+ if (!stickiesShown()) setStickies(true); // a note just added is seen (notes.js)
97
+ const b = makeBox({ x: Math.max(0, Math.min(W - 500, x)), y: Math.max(0, Math.min(H - 220, y)), w: 500, h: 220, cls: 'note' });
98
+ b.dataset.valign = 'top'; b.dataset.time = new Date().toISOString().slice(0, 16); content(b).textContent = text || '';
99
+ s.append(b); if (i === cur) render(); commit(); return b;
100
+ }
101
+ addEventListener('keydown', e => { if (presenting() && e.key === 'Escape') { e.preventDefault(); e.stopImmediatePropagation(); endPresent(); } }, true);
102
+ /* Live reloads keep applying while presenting (the deck's URL hash keeps the slide). */
103
+ /* The slideshow brought up to date (a change saved elsewhere, an agent's), where it was: the slide and the step. The
104
+ deck doesn't keep its place in its address, so a plain reload went back to the slide it was opened on. */
105
+ function refreshPresent() {
106
+ const f = /** @type {HTMLIFrameElement | null} */ ($('#ed-show iframe')); if (!f) return;
107
+ const w = /** @type {any} */ (f.contentWindow), at = w?.Reveal?.getIndices?.();
108
+ if (at) f.src = `deck.html?embedded&t=${Date.now()}#/${at.h}/${at.v || 0}${at.f != null && at.f >= 0 ? '/' + at.f : ''}`;
109
+ else w?.location.reload();
110
+ }
111
+ /* The standalone deck in a new tab (a second screen), optionally with reveal's speaker window. */
112
+ async function openDeckTab(from = cur, notes = false) {
113
+ await flushSave();
114
+ const w = window.open('deck.html#/' + hv(from).join('/'), '_blank');
115
+ if (!notes || !w) return;
116
+ setStatus('Presenter view: press S in the slideshow if it doesn\'t open');
117
+ const t0 = Date.now(), poll = setInterval(() => {
118
+ const plugin = w.closed ? null : w.Reveal?.getPlugin?.('notes');
119
+ if (plugin || w.closed || Date.now() - t0 > 5000) { clearInterval(poll); plugin?.open(); }
120
+ }, 100);
121
+ }
122
+ $('#ed-present').onclick = () => present();
@@ -0,0 +1,66 @@
1
+ /* ---------- boot ---------- */
2
+
3
+ /* The library's load today, as glideshow.org says on its responses (cloud/library.js meter): 'slow', changes made
4
+ elsewhere are checked once a minute rather than every 8 s; 'paused', no background traffic at all (no checks, no live
5
+ socket) until a reload, and a bar says why. Saving still works. (The Mac and a library in the browser never say.) */
6
+ let loadLevel = '', changesFeed = /** @type {EventSource | null} */ (null), changesTimer = 0;
7
+ /* How changes made elsewhere come in: on the Mac and in a library kept in the browser, a stream (/api/events); on
8
+ glideshow.org with GitHub's push webhook (worker.js githubWebhook), over the live socket, with a look every 5 minutes
9
+ in case a push went unheard; a Drive library once a minute; otherwise every 8 s, as the stream's retry says. */
10
+ const checkChanges = () => fetch('/api/events').then(r => r.text()).then(t => remoteVersion((t.match(/^data: (.*)$/m) || [])[1] || '')).catch(() => {});
11
+ function watchChanges() {
12
+ changesFeed?.close(); changesFeed = null; clearInterval(changesTimer); changesTimer = 0;
13
+ if (loadLevel === 'paused') return;
14
+ const how = /** @type {any} */ (window.GLIDESHOW_CLOUD)?.changes;
15
+ const every = how === 'push' ? 300_000 : how === 'minute' || loadLevel === 'slow' ? 60_000 : 0;
16
+ if (every) { changesTimer = window.setInterval(checkChanges, every); return; }
17
+ changesFeed = new EventSource('/api/events'); changesFeed.onmessage = ev => remoteVersion(ev.data);
18
+ }
19
+ function setLoad(level) {
20
+ if (!level || level === loadLevel || loadLevel === 'paused') return;
21
+ loadLevel = level; watchChanges();
22
+ if (level === 'paused') { const ws = liveSocket; liveSocket = null; try { ws?.close(); } catch {} setPeople([]); }
23
+ const bar = $('#ed-load-bar'); if (!bar) return; // (a page from before the bar, kept by a cache)
24
+ $('#ed-load-what').textContent = level === 'paused'
25
+ ? 'Glideshow paused syncing for this library until 00:00 UTC: it made an unusual number of requests today. Saving still works; reload to see changes made elsewhere.'
26
+ : 'This library is busy today, so changes made elsewhere are checked once a minute.';
27
+ bar.classList.toggle('paused', level === 'paused'); bar.hidden = false;
28
+ }
29
+ { const f = window.fetch.bind(window); window.fetch = async (u, o) => { const r = await f(u, o); const l = r.headers.get('x-glideshow-load'); if (l) setLoad(l); return r; }; }
30
+ if ($('#ed-load-close')) $('#ed-load-close').onclick = () => { $('#ed-load-bar').hidden = true; };
31
+
32
+ (async () => {
33
+ [BASE_CSS, SHAPES_JS, PRESENT_JS] = await Promise.all(['/editor/deck/base.css', '/editor/deck/shapes.js', '/editor/deck/present.js'].map(u => fetch(u, { cache: 'no-store' }).then(r => r.text())));
34
+ fit();
35
+ addEventListener('resize', fit);
36
+ await load(false);
37
+ setStatus('');
38
+ const startTheme = new URLSearchParams(location.search).get('theme'); // the home screen's template gallery
39
+ if (startTheme) { const t = THEMES.find(x => x.name === startTheme); if (t) setTheme({ ...t }); history.replaceState(null, '', '/edit'); }
40
+ watchChanges();
41
+ if (window.GLIDESHOW_CLOUD && !/** @type {any} */ (window.GLIDESHOW_CLOUD).local) live(); // (a library kept in the browser has no server to hold one)
42
+ })();
43
+ /* The deck was saved elsewhere (another editor, an agent, the Mac): take it in. While this editor is mid-gesture or
44
+ typing, after that; with changes of its own not yet saved, merged with them (storage.js mergeIn). */
45
+ function remoteVersion(v, by = '', both = null) { // both: slides where a library's merge kept this side's version over theirs
46
+ if (!v || v === mtime || saving) return;
47
+ remoteBy = by;
48
+ if (busy || editing) { pendingReload = true; setStatus('Changed elsewhere: bringing it in after this edit', 'sync'); return; }
49
+ (unsaved() || saveTimer ? (clearTimeout(saveTimer), saveTimer = null, mergeIn().then(() => scheduleSave())) : load(true, true)).then(() => { if (presenting()) refreshPresent(); if (both?.length) mergeNote(both, by); });
50
+ }
51
+ /* On glideshow.org a save anywhere reaches every open editor of the deck at once, over a WebSocket (cloud/library.js);
52
+ the 8 s poll of /api/events stays for what comes from GitHub or Drive. */
53
+ let liveSocket = null;
54
+ setInterval(sendHere, 200); // (people.js) started here, once liveSocket exists
55
+ function live(wait = 1000) {
56
+ try { liveSocket?.close(); } catch {}
57
+ const ws = liveSocket = new WebSocket(`${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/api/live`);
58
+ ws.onmessage = ev => {
59
+ let m; try { m = JSON.parse(ev.data); } catch { return; }
60
+ if (m.you) { meId = m.you; hereKey = ''; sendHere(); } // who else is here: people.js
61
+ if (m.people) setPeople(m.people);
62
+ if (m.v) remoteVersion(m.v, m.by || '', m.both || null);
63
+ };
64
+ ws.onopen = () => { if (wait > 1000) checkChanges(); wait = 1000; }; // back after a break: anything missed meanwhile
65
+ ws.onclose = ev => { if (ev.code === 4429) setLoad('paused'); if (ws === liveSocket && loadLevel !== 'paused') { setPeople([]); setTimeout(() => live(Math.min(wait * 2, 30000)), wait); } };
66
+ }