@markdy/astro 0.7.0 → 0.7.2
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.
- package/package.json +2 -2
- package/src/Markdy.astro +97 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markdy/astro",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Astro island component for MarkdyScript animations.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@markdy/renderer-dom": "0.7.
|
|
40
|
+
"@markdy/renderer-dom": "0.7.2"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"astro": ">=4.0.0"
|
package/src/Markdy.astro
CHANGED
|
@@ -88,20 +88,20 @@ const {
|
|
|
88
88
|
<!--
|
|
89
89
|
SSR placeholder: keeps layout stable before the island hydrates.
|
|
90
90
|
Background matches the scene bg prop to avoid a visible flash.
|
|
91
|
-
|
|
92
|
-
accessible name — this inner text is decorative.
|
|
91
|
+
A <button> so it is keyboard-accessible and semantically interactive.
|
|
93
92
|
-->
|
|
94
|
-
<
|
|
93
|
+
<button
|
|
95
94
|
class="markdy-placeholder"
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
type="button"
|
|
96
|
+
aria-label={`Play ${title}`}
|
|
97
|
+
style={`width:100%;height:100%;background:${bg};display:flex;align-items:center;justify-content:center;cursor:pointer;border:none;padding:0`}
|
|
98
98
|
>
|
|
99
99
|
<span
|
|
100
|
-
style="font-family:sans-serif;font-size:12px;color:#bbb;letter-spacing:0.05em"
|
|
100
|
+
style="font-family:sans-serif;font-size:12px;color:#bbb;letter-spacing:0.05em;pointer-events:none"
|
|
101
101
|
>
|
|
102
102
|
▶ markdy
|
|
103
103
|
</span>
|
|
104
|
-
</
|
|
104
|
+
</button>
|
|
105
105
|
|
|
106
106
|
<!--
|
|
107
107
|
noscript fallback: rendered for crawlers / users that do not run JS.
|
|
@@ -121,13 +121,59 @@ const {
|
|
|
121
121
|
</noscript>
|
|
122
122
|
</div>
|
|
123
123
|
|
|
124
|
+
<script is:inline>
|
|
125
|
+
// ── Rocket Loader rescue ──────────────────────────────────────────────────
|
|
126
|
+
// Cloudflare Rocket Loader rewrites <script type="module" src="..."> to a
|
|
127
|
+
// proprietary type (e.g. "abc123-module") and then fails to execute them,
|
|
128
|
+
// which silently kills ALL hydration logic in this component.
|
|
129
|
+
//
|
|
130
|
+
// This inline script is NOT touched by Rocket Loader (no src, no type attr).
|
|
131
|
+
// It detects mangled module scripts by the Rocket Loader type pattern and
|
|
132
|
+
// re-injects them dynamically — dynamically-created scripts also bypass
|
|
133
|
+
// Rocket Loader entirely.
|
|
134
|
+
(function () {
|
|
135
|
+
if (window.__markdyRescue) return;
|
|
136
|
+
window.__markdyRescue = true;
|
|
137
|
+
|
|
138
|
+
var rescued = false;
|
|
139
|
+
function rescueModuleScripts() {
|
|
140
|
+
if (rescued) return;
|
|
141
|
+
rescued = true;
|
|
142
|
+
// Rocket Loader mangles type="module" → "{uuid}-module".
|
|
143
|
+
// Re-inject as real module scripts so the browser executes them.
|
|
144
|
+
document.querySelectorAll('script[type$="-module"][src]').forEach(function (s) {
|
|
145
|
+
var fix = document.createElement('script');
|
|
146
|
+
fix.type = 'module';
|
|
147
|
+
fix.src = s.src;
|
|
148
|
+
document.head.appendChild(fix);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Rescue when user clicks the placeholder (fallback if autoplay failed).
|
|
153
|
+
document.addEventListener('click', function (e) {
|
|
154
|
+
var t = e.target;
|
|
155
|
+
if (t && typeof t.closest === 'function' && t.closest('.markdy-placeholder')) {
|
|
156
|
+
rescueModuleScripts();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Rescue automatically after a short delay for autoplay.
|
|
161
|
+
// Only fires if initAll() never ran (no data-markdy-init on any root).
|
|
162
|
+
setTimeout(function () {
|
|
163
|
+
if (document.querySelector('.markdy-root:not([data-markdy-init])')) {
|
|
164
|
+
rescueModuleScripts();
|
|
165
|
+
}
|
|
166
|
+
}, 1500);
|
|
167
|
+
}());
|
|
168
|
+
</script>
|
|
169
|
+
|
|
124
170
|
<script>
|
|
125
171
|
// ── Hydration ────────────────────────────────────────────────────────────
|
|
126
172
|
// The renderer is dynamically imported so it is code-split from the
|
|
127
173
|
// host-page bundle. The browser only downloads it when an element is
|
|
128
174
|
// about to enter the viewport, keeping Time-to-Interactive low.
|
|
129
175
|
|
|
130
|
-
async function hydrate(el: HTMLElement): Promise<void> {
|
|
176
|
+
async function hydrate(el: HTMLElement, forcePlay = false): Promise<void> {
|
|
131
177
|
const code = el.dataset.markdyCode;
|
|
132
178
|
if (!code) return;
|
|
133
179
|
|
|
@@ -141,7 +187,7 @@ const {
|
|
|
141
187
|
// Leave assets empty on parse failure — renderer falls back to DSL paths.
|
|
142
188
|
}
|
|
143
189
|
|
|
144
|
-
const autoplay = el.dataset.markdyAutoplay !== "false";
|
|
190
|
+
const autoplay = forcePlay || el.dataset.markdyAutoplay !== "false";
|
|
145
191
|
const loop = el.dataset.markdyLoop !== "false";
|
|
146
192
|
const copyright = el.dataset.markdyCopyright !== "false";
|
|
147
193
|
const progressBar = el.dataset.markdyProgressBar !== "false";
|
|
@@ -183,10 +229,10 @@ const {
|
|
|
183
229
|
}
|
|
184
230
|
|
|
185
231
|
// ── Intersection observer ────────────────────────────────────────────────
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
232
|
+
// threshold: 0.25 — trigger as soon as a quarter of the element is visible.
|
|
233
|
+
// We intentionally avoid threshold: 1.0 because it is unreliable: on first
|
|
234
|
+
// navigation (fresh link, no F5) the browser may not fire the callback when
|
|
235
|
+
// the element is very close to — but not fully inside — the viewport.
|
|
190
236
|
|
|
191
237
|
const observer = new IntersectionObserver(
|
|
192
238
|
(entries) => {
|
|
@@ -206,15 +252,40 @@ const {
|
|
|
206
252
|
}
|
|
207
253
|
}
|
|
208
254
|
},
|
|
209
|
-
{ threshold:
|
|
255
|
+
{ threshold: 0.25 },
|
|
210
256
|
);
|
|
211
257
|
|
|
258
|
+
function doHydrate(el: HTMLElement, forcePlay = false): void {
|
|
259
|
+
observer.unobserve(el);
|
|
260
|
+
el.dataset.markdyInit = "hydrating";
|
|
261
|
+
scheduleHydration(() => {
|
|
262
|
+
hydrate(el, forcePlay).catch(() => {
|
|
263
|
+
el.dataset.markdyInit = "error";
|
|
264
|
+
el.removeAttribute("aria-busy");
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
212
269
|
function initAll(): void {
|
|
213
270
|
document
|
|
214
271
|
.querySelectorAll<HTMLElement>(".markdy-root:not([data-markdy-init])")
|
|
215
272
|
.forEach((el) => {
|
|
216
273
|
el.dataset.markdyInit = "pending";
|
|
217
|
-
|
|
274
|
+
// Proactively hydrate elements already in the viewport so we never
|
|
275
|
+
// depend on the observer firing for elements visible at page load.
|
|
276
|
+
// This fixes autoplay on first navigation from an external site where
|
|
277
|
+
// IntersectionObserver callbacks may be delayed or skipped.
|
|
278
|
+
const rect = el.getBoundingClientRect();
|
|
279
|
+
const inViewport =
|
|
280
|
+
rect.bottom > 0 &&
|
|
281
|
+
rect.right > 0 &&
|
|
282
|
+
rect.top < (window.innerHeight || document.documentElement.clientHeight) &&
|
|
283
|
+
rect.left < (window.innerWidth || document.documentElement.clientWidth);
|
|
284
|
+
if (inViewport) {
|
|
285
|
+
doHydrate(el);
|
|
286
|
+
} else {
|
|
287
|
+
observer.observe(el);
|
|
288
|
+
}
|
|
218
289
|
});
|
|
219
290
|
}
|
|
220
291
|
|
|
@@ -227,4 +298,15 @@ const {
|
|
|
227
298
|
|
|
228
299
|
// Re-run after Astro View Transitions / client-side navigation.
|
|
229
300
|
document.addEventListener("astro:page-load", initAll);
|
|
301
|
+
|
|
302
|
+
// Click on placeholder to play immediately without waiting for full visibility.
|
|
303
|
+
document.addEventListener("click", (e) => {
|
|
304
|
+
const placeholder = (e.target as Element).closest(".markdy-placeholder");
|
|
305
|
+
if (!placeholder) return;
|
|
306
|
+
const root = placeholder.closest<HTMLElement>(".markdy-root");
|
|
307
|
+
if (!root) return;
|
|
308
|
+
const state = root.dataset.markdyInit;
|
|
309
|
+
if (state === "done" || state === "hydrating") return;
|
|
310
|
+
doHydrate(root, true);
|
|
311
|
+
});
|
|
230
312
|
</script>
|