embed-manager 1.0.0 → 1.0.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.
- package/dist/embedManager.js +67 -13
- package/dist/embedManager.min.js +1 -1
- package/package.json +1 -1
- package/src/lib/embedManager.js +67 -13
package/dist/embedManager.js
CHANGED
|
@@ -26,6 +26,7 @@ class EmbedManager {
|
|
|
26
26
|
constructor(options = {}) {
|
|
27
27
|
this.options = {
|
|
28
28
|
rootMargin: '200px 0px',
|
|
29
|
+
embedTimeout: 15000, // ms before a special embed is declared failed
|
|
29
30
|
...options
|
|
30
31
|
};
|
|
31
32
|
this.injectCSS();
|
|
@@ -427,8 +428,17 @@ class EmbedManager {
|
|
|
427
428
|
handleSpecialEmbed(embed, type) {
|
|
428
429
|
const src = embed.getAttribute('data-src');
|
|
429
430
|
const title = embed.getAttribute('data-title') || 'Untitled Embed';
|
|
431
|
+
const timeoutMs = this.options.embedTimeout;
|
|
430
432
|
|
|
431
|
-
//
|
|
433
|
+
// twitter/x may use a plain numeric tweet ID instead of a full URL
|
|
434
|
+
if (type !== 'twitter' && type !== 'x') {
|
|
435
|
+
if (!src || !this.isValidUrl(src)) {
|
|
436
|
+
this.showError(embed, `Invalid ${type} source URL`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Show loading placeholder
|
|
432
442
|
const loadingMessage = document.createElement('div');
|
|
433
443
|
loadingMessage.className = 'embed-placeholder';
|
|
434
444
|
loadingMessage.setAttribute('aria-live', 'polite');
|
|
@@ -439,7 +449,7 @@ class EmbedManager {
|
|
|
439
449
|
try {
|
|
440
450
|
switch (type) {
|
|
441
451
|
case 'twitter':
|
|
442
|
-
case 'x':
|
|
452
|
+
case 'x': {
|
|
443
453
|
// Create a blockquote for Twitter to transform
|
|
444
454
|
const tweetUrl = this.buildEmbedSrc(embed, src, type);
|
|
445
455
|
const tweetContainer = document.createElement('blockquote');
|
|
@@ -455,16 +465,24 @@ class EmbedManager {
|
|
|
455
465
|
embed.innerHTML = '';
|
|
456
466
|
embed.appendChild(tweetContainer);
|
|
457
467
|
|
|
458
|
-
// Initialize Twitter widgets
|
|
459
468
|
if (window.twttr && window.twttr.widgets) {
|
|
460
469
|
window.twttr.widgets.load(embed);
|
|
461
470
|
} else {
|
|
462
|
-
// The script will auto-process when loaded
|
|
463
471
|
this.loadExternalScript('https://platform.twitter.com/widgets.js', 'twitter-widget');
|
|
464
472
|
}
|
|
473
|
+
|
|
474
|
+
// Twitter widget.js replaces the blockquote with an <iframe> on success
|
|
475
|
+
if (timeoutMs > 0) {
|
|
476
|
+
setTimeout(() => {
|
|
477
|
+
if (!embed.querySelector('iframe')) {
|
|
478
|
+
this.showError(embed, 'Tweet failed to load. Check that the URL is correct and the tweet is publicly accessible.');
|
|
479
|
+
}
|
|
480
|
+
}, timeoutMs);
|
|
481
|
+
}
|
|
465
482
|
break;
|
|
483
|
+
}
|
|
466
484
|
|
|
467
|
-
case 'instagram':
|
|
485
|
+
case 'instagram': {
|
|
468
486
|
// Create an Instagram embed using blockquote format
|
|
469
487
|
const instagramUrl = this.buildEmbedSrc(embed, src, type);
|
|
470
488
|
const instagramContainer = document.createElement('blockquote');
|
|
@@ -476,7 +494,6 @@ class EmbedManager {
|
|
|
476
494
|
instagramContainer.style.width = '100%';
|
|
477
495
|
instagramContainer.style.maxWidth = '540px';
|
|
478
496
|
|
|
479
|
-
// Add a link inside the blockquote (required for Instagram's script)
|
|
480
497
|
const link = document.createElement('a');
|
|
481
498
|
link.href = instagramUrl;
|
|
482
499
|
link.textContent = title || 'View this post on Instagram';
|
|
@@ -486,25 +503,62 @@ class EmbedManager {
|
|
|
486
503
|
embed.innerHTML = '';
|
|
487
504
|
embed.appendChild(instagramContainer);
|
|
488
505
|
|
|
489
|
-
// Load Instagram's embed script and process this container
|
|
490
506
|
this.loadExternalScript('https://www.instagram.com/embed.js', 'instagram-embed');
|
|
491
507
|
|
|
492
|
-
// Need to tell instgrm to process this embed
|
|
493
508
|
if (window.instgrm) {
|
|
494
509
|
window.instgrm.Embeds.process();
|
|
495
510
|
}
|
|
511
|
+
|
|
512
|
+
// Instagram embed.js replaces the blockquote with an <iframe> on success
|
|
513
|
+
if (timeoutMs > 0) {
|
|
514
|
+
setTimeout(() => {
|
|
515
|
+
if (!embed.querySelector('iframe')) {
|
|
516
|
+
this.showError(embed, 'Instagram embed failed to load. Check that the URL is correct and the post is publicly accessible.');
|
|
517
|
+
}
|
|
518
|
+
}, timeoutMs);
|
|
519
|
+
}
|
|
496
520
|
break;
|
|
521
|
+
}
|
|
497
522
|
|
|
498
523
|
case 'gist':
|
|
499
|
-
case 'github':
|
|
500
|
-
// GitHub Gists use script tags
|
|
524
|
+
case 'github': {
|
|
501
525
|
const gistUrl = this.buildEmbedSrc(embed, src, type);
|
|
502
|
-
|
|
503
|
-
|
|
526
|
+
|
|
527
|
+
// Gist scripts use document.write(), which is blocked after page load.
|
|
528
|
+
// Using srcdoc gives the script a fresh document context to write into.
|
|
529
|
+
const iframe = document.createElement('iframe');
|
|
530
|
+
iframe.style.width = '100%';
|
|
531
|
+
iframe.style.border = 'none';
|
|
532
|
+
iframe.style.minHeight = '100px';
|
|
533
|
+
iframe.setAttribute('aria-label', title);
|
|
534
|
+
iframe.srcdoc = `<!DOCTYPE html><html><head><base target="_parent"><style>body{margin:0;font-family:sans-serif}</style></head><body><script src="${gistUrl}"><\/script></body></html>`;
|
|
535
|
+
|
|
536
|
+
let settled = false;
|
|
537
|
+
let timeoutId = null;
|
|
538
|
+
|
|
539
|
+
iframe.addEventListener('load', () => {
|
|
540
|
+
settled = true;
|
|
541
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
542
|
+
embed.querySelector('.embed-placeholder')?.remove();
|
|
543
|
+
});
|
|
544
|
+
iframe.addEventListener('error', () => {
|
|
545
|
+
settled = true;
|
|
546
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
547
|
+
this.showError(embed, 'Failed to load GitHub Gist. Ensure the Gist is public and the URL is correct.');
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
if (timeoutMs > 0) {
|
|
551
|
+
timeoutId = setTimeout(() => {
|
|
552
|
+
if (!settled) {
|
|
553
|
+
this.showError(embed, 'GitHub Gist timed out. Ensure the Gist is public and the URL is correct.');
|
|
554
|
+
}
|
|
555
|
+
}, timeoutMs);
|
|
556
|
+
}
|
|
504
557
|
|
|
505
558
|
embed.innerHTML = '';
|
|
506
|
-
embed.appendChild(
|
|
559
|
+
embed.appendChild(iframe);
|
|
507
560
|
break;
|
|
561
|
+
}
|
|
508
562
|
}
|
|
509
563
|
} catch (error) {
|
|
510
564
|
this.showError(embed, error.message);
|
package/dist/embedManager.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class EmbedManager{constructor(t={}){this.options={rootMargin:"200px 0px",...t},this.injectCSS(),this.init()}injectCSS(){const t=document.createElement("style");t.innerHTML="\n\t\t\t.embed-container {\n\t\t\t\tmargin: 20px auto;\n\t\t\t\tbackground: #f4f4f4;\n\t\t\t\tposition: relative;\n\t\t\t\toverflow: hidden;\n\t\t\t\tdisplay: flex;\n\t\t\t\tjustify-content: center;\n\t\t\t\talign-items: center;\n\t\t\t\t/* Default aspect ratio wrapper */\n\t\t\t\taspect-ratio: 16/9;\n\t\t\t}\n\t\t\t.embed-container iframe {\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\tborder: none;\n\t\t\t\tdisplay: block;\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t\tleft: 0;\n\t\t\t}\n\t\t\t.embed-container p {\n\t\t\t\tmargin: 0;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tcolor: #555;\n\t\t\t}\n\t\t\t.embed-container .embed-placeholder {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\ttext-align: center;\n\t\t\t\tpadding: 1rem;\n\t\t\t}\n\t\t\t.embed-container .embed-error {\n\t\t\t\tcolor: #721c24;\n\t\t\t\tbackground-color: #f8d7da;\n\t\t\t\tpadding: 0.75rem;\n\t\t\t\tborder-radius: 0.25rem;\n\t\t\t\tmargin: 0.5rem 0;\n\t\t\t\twidth: 100%;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t",document.head.appendChild(t)}init(){"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>this.setupEmbeds()):this.setupEmbeds()}setupEmbeds(){const t=document.querySelectorAll(".embed-container");this.setupObserver(t)}setupObserver(t){const e=new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting&&(this.lazyLoadEmbed(t.target),e.unobserve(t.target))})},{rootMargin:this.options.rootMargin});t.forEach(t=>{if(!t.innerHTML.trim()){const e=t.getAttribute("data-type")||"content",
|
|
1
|
+
class EmbedManager{constructor(t={}){this.options={rootMargin:"200px 0px",embedTimeout:15e3,...t},this.injectCSS(),this.init()}injectCSS(){const t=document.createElement("style");t.innerHTML="\n\t\t\t.embed-container {\n\t\t\t\tmargin: 20px auto;\n\t\t\t\tbackground: #f4f4f4;\n\t\t\t\tposition: relative;\n\t\t\t\toverflow: hidden;\n\t\t\t\tdisplay: flex;\n\t\t\t\tjustify-content: center;\n\t\t\t\talign-items: center;\n\t\t\t\t/* Default aspect ratio wrapper */\n\t\t\t\taspect-ratio: 16/9;\n\t\t\t}\n\t\t\t.embed-container iframe {\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\tborder: none;\n\t\t\t\tdisplay: block;\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t\tleft: 0;\n\t\t\t}\n\t\t\t.embed-container p {\n\t\t\t\tmargin: 0;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tcolor: #555;\n\t\t\t}\n\t\t\t.embed-container .embed-placeholder {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\ttext-align: center;\n\t\t\t\tpadding: 1rem;\n\t\t\t}\n\t\t\t.embed-container .embed-error {\n\t\t\t\tcolor: #721c24;\n\t\t\t\tbackground-color: #f8d7da;\n\t\t\t\tpadding: 0.75rem;\n\t\t\t\tborder-radius: 0.25rem;\n\t\t\t\tmargin: 0.5rem 0;\n\t\t\t\twidth: 100%;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t",document.head.appendChild(t)}init(){"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>this.setupEmbeds()):this.setupEmbeds()}setupEmbeds(){const t=document.querySelectorAll(".embed-container");this.setupObserver(t)}setupObserver(t){const e=new IntersectionObserver(t=>{t.forEach(t=>{t.isIntersecting&&(this.lazyLoadEmbed(t.target),e.unobserve(t.target))})},{rootMargin:this.options.rootMargin});t.forEach(t=>{if(!t.innerHTML.trim()){const e=t.getAttribute("data-type")||"content",i=document.createElement("div");i.className="embed-placeholder",i.innerHTML=`<p>Loading ${e} content when visible</p>`,t.appendChild(i)}e.observe(t)})}showError(t,e){console.error(`EmbedManager Error: ${e}`),t.innerHTML=`<div class="embed-error" role="alert">${e}</div>`}isValidUrl(t){try{const e=new URL(t);return["https:","http:"].includes(e.protocol)}catch(t){return!1}}buildEmbedSrc(t,e,i){let a=e;switch(i){case"codepen":{const e=t.getAttribute("data-theme-id")||"",i=t.getAttribute("data-default-tab")||"result",r="true"===t.getAttribute("data-editable")?"true":"false",n="true"===t.getAttribute("data-preview");a.includes("/pen/")?a=a.replace("/pen/",n?"/embed/preview/":"/embed/"):n&&a.includes("/embed/")&&!a.includes("/embed/preview/")&&(a=a.replace("/embed/","/embed/preview/"));const s=a.includes("?")?"&":"?";a=`${a}${s}theme-id=${e}&default-tab=${i}&editable=${r}`;break}case"vimeo":{const i=t.getAttribute("data-hash");if(i&&!e.includes("h=")){const t=e.includes("?")?"&":"?";a=`${e}${t}h=${i}`}const r=["badge=0","autopause=0","player_id=0","dnt=1"],n=t.getAttribute("data-app-id");n&&r.push(`app_id=${n}`),"true"===t.getAttribute("data-autoplay")&&r.push("autoplay=1"),r.forEach(t=>{const e=t.split("=")[0];if(!a.includes(e+"=")){const e=a.includes("?")?"&":"?";a=`${a}${e}${t}`}});break}case"youtube":{const i=[];"true"===t.getAttribute("data-autoplay")&&i.push("autoplay=1"),e.includes("youtube-nocookie.com")||(a=a.replace("youtube.com","youtube-nocookie.com")),i.push("rel=0","modestbranding=1");const r=a.includes("?")?"&":"?";a=`${a}${r}${i.join("&")}`;break}case"twitch":{const t=window.location.hostname;a=`${a}&parent=${t}`;break}case"twitter":case"x":this.loadExternalScript("https://platform.twitter.com/widgets.js","twitter-widget"),/^\d+$/.test(e)&&(a=`https://twitter.com/i/status/${e}`);break;case"instagram":this.loadExternalScript("https://www.instagram.com/embed.js","instagram-embed"),(a.includes("instagram.com/p/")||a.includes("instagram.com/reel/"))&&(a.includes("?")?a.includes("utm_source=ig_embed")||(a=`${a}&utm_source=ig_embed&utm_campaign=loading`):a=`${a}?utm_source=ig_embed&utm_campaign=loading`);break;case"tiktok":if(this.loadExternalScript("https://www.tiktok.com/embed.js","tiktok-embed"),!a.includes("embed")){const t=a.replace(/\?.*$/,"").replace(/\/$/,"").split("/").pop();a=`https://www.tiktok.com/embed/v2/${t}`}break;case"soundcloud":if(!a.includes("api.soundcloud.com")){const i=t.getAttribute("data-color")||"ff5500",r="true"===t.getAttribute("data-autoplay")?"true":"false",n="true"===t.getAttribute("data-show-comments")?"true":"false";a=`https://w.soundcloud.com/player/?url=${encodeURIComponent(e)}&color=${i}&auto_play=${r}&hide_related=false&show_comments=${n}&show_user=true&show_reposts=false&show_teaser=true`}break;case"spotify":if(a.includes("spotify.com")){const t=a.includes("/track/")?"track":a.includes("/album/")?"album":a.includes("/playlist/")?"playlist":a.includes("/episode/")?"episode":"track",e=a.split("/").pop().split("?")[0];a=`https://open.spotify.com/embed/${t}/${e}`}break;case"github":case"gist":if(a.includes("gist.github.com")&&!a.endsWith(".js")){const t=a.split("/").pop();a=`https://gist.github.com/${t}.js`}break;case"maps":case"google-maps":if(!a.includes("google.com/maps/embed")){let e="";a.includes("maps/place/")?e=a.split("maps/place/")[1].split("/")[0]:a.includes("maps?q=")&&(e=a.split("maps?q=")[1].split("&")[0]),e&&(a=`https://www.google.com/maps/embed/v1/place?key=${t.getAttribute("data-api-key")||""}&q=${e}`)}}return a}loadExternalScript(t,e){if(!document.getElementById(e)){const i=document.createElement("script");i.id=e,i.src=t,i.async=!0,i.defer=!0,document.body.appendChild(i)}}lazyLoadEmbed(t){const e=t.getAttribute("data-type"),i=t.getAttribute("data-src");if("twitter"===e||"x"===e||"gist"===e||"github"===e||"instagram"===e)return void this.handleSpecialEmbed(t,e);const a=t.getAttribute("data-title")||"Untitled Embed",r=t.getAttribute("data-width")||"100%",n=t.getAttribute("data-height"),s=t.getAttribute("data-aspect-ratio")||"16/9";if(!i||!this.isValidUrl(i))return void this.showError(t,"Invalid embed source URL");n?(t.style.height=n,t.style.width=r,t.style.aspectRatio="unset"):(t.style.width=r,t.style.aspectRatio=s);const o=document.createElement("div");o.className="embed-placeholder",o.setAttribute("aria-live","polite"),o.innerHTML=`<p>Loading ${e} content...</p>`,t.innerHTML="",t.appendChild(o);const d=document.createElement("iframe");d.allow="autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media",d.loading="lazy",d.title=a,d.setAttribute("allowfullscreen",""),d.setAttribute("aria-label",a),d.referrerPolicy="no-referrer-when-downgrade";try{let a=this.buildEmbedSrc(t,i,e);d.src=a,d.addEventListener("load",()=>{t.querySelector(".embed-placeholder")?.remove()}),d.addEventListener("error",()=>{this.showError(t,`Failed to load ${e} content`)}),"website"===e&&(d.sandbox="allow-scripts allow-same-origin allow-forms allow-popups"),t.appendChild(d)}catch(e){this.showError(t,e.message)}}handleSpecialEmbed(t,e){const i=t.getAttribute("data-src"),a=t.getAttribute("data-title")||"Untitled Embed",r=this.options.embedTimeout;if(!("twitter"===e||"x"===e||i&&this.isValidUrl(i)))return void this.showError(t,`Invalid ${e} source URL`);const n=document.createElement("div");n.className="embed-placeholder",n.setAttribute("aria-live","polite"),n.innerHTML=`<p>Loading ${e} content...</p>`,t.innerHTML="",t.appendChild(n);try{switch(e){case"twitter":case"x":{const n=this.buildEmbedSrc(t,i,e),s=document.createElement("blockquote");s.className="twitter-tweet",s.setAttribute("data-lang",t.getAttribute("data-lang")||"en"),s.setAttribute("data-theme",t.getAttribute("data-theme")||"light");const o=document.createElement("a");o.href=n,o.textContent=a,s.appendChild(o),t.innerHTML="",t.appendChild(s),window.twttr&&window.twttr.widgets?window.twttr.widgets.load(t):this.loadExternalScript("https://platform.twitter.com/widgets.js","twitter-widget"),r>0&&setTimeout(()=>{t.querySelector("iframe")||this.showError(t,"Tweet failed to load. Check that the URL is correct and the tweet is publicly accessible.")},r);break}case"instagram":{const n=this.buildEmbedSrc(t,i,e),s=document.createElement("blockquote");s.className="instagram-media",s.setAttribute("data-instgrm-captioned",""),s.setAttribute("data-instgrm-permalink",n),s.setAttribute("data-instgrm-version","14"),s.style.margin="0 auto",s.style.width="100%",s.style.maxWidth="540px";const o=document.createElement("a");o.href=n,o.textContent=a||"View this post on Instagram",o.target="_blank",s.appendChild(o),t.innerHTML="",t.appendChild(s),this.loadExternalScript("https://www.instagram.com/embed.js","instagram-embed"),window.instgrm&&window.instgrm.Embeds.process(),r>0&&setTimeout(()=>{t.querySelector("iframe")||this.showError(t,"Instagram embed failed to load. Check that the URL is correct and the post is publicly accessible.")},r);break}case"gist":case"github":{const n=this.buildEmbedSrc(t,i,e),s=document.createElement("iframe");s.style.width="100%",s.style.border="none",s.style.minHeight="100px",s.setAttribute("aria-label",a),s.srcdoc=`<!DOCTYPE html><html><head><base target="_parent"><style>body{margin:0;font-family:sans-serif}</style></head><body><script src="${n}"><\/script></body></html>`;let o=!1,d=null;s.addEventListener("load",()=>{o=!0,d&&clearTimeout(d),t.querySelector(".embed-placeholder")?.remove()}),s.addEventListener("error",()=>{o=!0,d&&clearTimeout(d),this.showError(t,"Failed to load GitHub Gist. Ensure the Gist is public and the URL is correct.")}),r>0&&(d=setTimeout(()=>{o||this.showError(t,"GitHub Gist timed out. Ensure the Gist is public and the URL is correct.")},r)),t.innerHTML="",t.appendChild(s);break}}}catch(e){this.showError(t,e.message)}}processContainer(t){t&&t.classList.contains("embed-container")&&this.lazyLoadEmbed(t)}addEmbed(t){t&&t.classList.contains("embed-container")&&this.setupObserver([t])}}"undefined"!=typeof module&&module.exports&&(module.exports=EmbedManager),"undefined"!=typeof window&&"undefined"==typeof module&&(window.EmbedManager=new EmbedManager);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "embed-manager",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A versatile JavaScript library for embedding various content types (YouTube, Vimeo, Twitch, CodePen, and websites) with lazy loading capabilities",
|
|
5
5
|
"main": "src/lib/embedManager.js",
|
|
6
6
|
"browser": "dist/embedManager.min.js",
|
package/src/lib/embedManager.js
CHANGED
|
@@ -26,6 +26,7 @@ class EmbedManager {
|
|
|
26
26
|
constructor(options = {}) {
|
|
27
27
|
this.options = {
|
|
28
28
|
rootMargin: '200px 0px',
|
|
29
|
+
embedTimeout: 15000, // ms before a special embed is declared failed
|
|
29
30
|
...options
|
|
30
31
|
};
|
|
31
32
|
this.injectCSS();
|
|
@@ -427,8 +428,17 @@ class EmbedManager {
|
|
|
427
428
|
handleSpecialEmbed(embed, type) {
|
|
428
429
|
const src = embed.getAttribute('data-src');
|
|
429
430
|
const title = embed.getAttribute('data-title') || 'Untitled Embed';
|
|
431
|
+
const timeoutMs = this.options.embedTimeout;
|
|
430
432
|
|
|
431
|
-
//
|
|
433
|
+
// twitter/x may use a plain numeric tweet ID instead of a full URL
|
|
434
|
+
if (type !== 'twitter' && type !== 'x') {
|
|
435
|
+
if (!src || !this.isValidUrl(src)) {
|
|
436
|
+
this.showError(embed, `Invalid ${type} source URL`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Show loading placeholder
|
|
432
442
|
const loadingMessage = document.createElement('div');
|
|
433
443
|
loadingMessage.className = 'embed-placeholder';
|
|
434
444
|
loadingMessage.setAttribute('aria-live', 'polite');
|
|
@@ -439,7 +449,7 @@ class EmbedManager {
|
|
|
439
449
|
try {
|
|
440
450
|
switch (type) {
|
|
441
451
|
case 'twitter':
|
|
442
|
-
case 'x':
|
|
452
|
+
case 'x': {
|
|
443
453
|
// Create a blockquote for Twitter to transform
|
|
444
454
|
const tweetUrl = this.buildEmbedSrc(embed, src, type);
|
|
445
455
|
const tweetContainer = document.createElement('blockquote');
|
|
@@ -455,16 +465,24 @@ class EmbedManager {
|
|
|
455
465
|
embed.innerHTML = '';
|
|
456
466
|
embed.appendChild(tweetContainer);
|
|
457
467
|
|
|
458
|
-
// Initialize Twitter widgets
|
|
459
468
|
if (window.twttr && window.twttr.widgets) {
|
|
460
469
|
window.twttr.widgets.load(embed);
|
|
461
470
|
} else {
|
|
462
|
-
// The script will auto-process when loaded
|
|
463
471
|
this.loadExternalScript('https://platform.twitter.com/widgets.js', 'twitter-widget');
|
|
464
472
|
}
|
|
473
|
+
|
|
474
|
+
// Twitter widget.js replaces the blockquote with an <iframe> on success
|
|
475
|
+
if (timeoutMs > 0) {
|
|
476
|
+
setTimeout(() => {
|
|
477
|
+
if (!embed.querySelector('iframe')) {
|
|
478
|
+
this.showError(embed, 'Tweet failed to load. Check that the URL is correct and the tweet is publicly accessible.');
|
|
479
|
+
}
|
|
480
|
+
}, timeoutMs);
|
|
481
|
+
}
|
|
465
482
|
break;
|
|
483
|
+
}
|
|
466
484
|
|
|
467
|
-
case 'instagram':
|
|
485
|
+
case 'instagram': {
|
|
468
486
|
// Create an Instagram embed using blockquote format
|
|
469
487
|
const instagramUrl = this.buildEmbedSrc(embed, src, type);
|
|
470
488
|
const instagramContainer = document.createElement('blockquote');
|
|
@@ -476,7 +494,6 @@ class EmbedManager {
|
|
|
476
494
|
instagramContainer.style.width = '100%';
|
|
477
495
|
instagramContainer.style.maxWidth = '540px';
|
|
478
496
|
|
|
479
|
-
// Add a link inside the blockquote (required for Instagram's script)
|
|
480
497
|
const link = document.createElement('a');
|
|
481
498
|
link.href = instagramUrl;
|
|
482
499
|
link.textContent = title || 'View this post on Instagram';
|
|
@@ -486,25 +503,62 @@ class EmbedManager {
|
|
|
486
503
|
embed.innerHTML = '';
|
|
487
504
|
embed.appendChild(instagramContainer);
|
|
488
505
|
|
|
489
|
-
// Load Instagram's embed script and process this container
|
|
490
506
|
this.loadExternalScript('https://www.instagram.com/embed.js', 'instagram-embed');
|
|
491
507
|
|
|
492
|
-
// Need to tell instgrm to process this embed
|
|
493
508
|
if (window.instgrm) {
|
|
494
509
|
window.instgrm.Embeds.process();
|
|
495
510
|
}
|
|
511
|
+
|
|
512
|
+
// Instagram embed.js replaces the blockquote with an <iframe> on success
|
|
513
|
+
if (timeoutMs > 0) {
|
|
514
|
+
setTimeout(() => {
|
|
515
|
+
if (!embed.querySelector('iframe')) {
|
|
516
|
+
this.showError(embed, 'Instagram embed failed to load. Check that the URL is correct and the post is publicly accessible.');
|
|
517
|
+
}
|
|
518
|
+
}, timeoutMs);
|
|
519
|
+
}
|
|
496
520
|
break;
|
|
521
|
+
}
|
|
497
522
|
|
|
498
523
|
case 'gist':
|
|
499
|
-
case 'github':
|
|
500
|
-
// GitHub Gists use script tags
|
|
524
|
+
case 'github': {
|
|
501
525
|
const gistUrl = this.buildEmbedSrc(embed, src, type);
|
|
502
|
-
|
|
503
|
-
|
|
526
|
+
|
|
527
|
+
// Gist scripts use document.write(), which is blocked after page load.
|
|
528
|
+
// Using srcdoc gives the script a fresh document context to write into.
|
|
529
|
+
const iframe = document.createElement('iframe');
|
|
530
|
+
iframe.style.width = '100%';
|
|
531
|
+
iframe.style.border = 'none';
|
|
532
|
+
iframe.style.minHeight = '100px';
|
|
533
|
+
iframe.setAttribute('aria-label', title);
|
|
534
|
+
iframe.srcdoc = `<!DOCTYPE html><html><head><base target="_parent"><style>body{margin:0;font-family:sans-serif}</style></head><body><script src="${gistUrl}"><\/script></body></html>`;
|
|
535
|
+
|
|
536
|
+
let settled = false;
|
|
537
|
+
let timeoutId = null;
|
|
538
|
+
|
|
539
|
+
iframe.addEventListener('load', () => {
|
|
540
|
+
settled = true;
|
|
541
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
542
|
+
embed.querySelector('.embed-placeholder')?.remove();
|
|
543
|
+
});
|
|
544
|
+
iframe.addEventListener('error', () => {
|
|
545
|
+
settled = true;
|
|
546
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
547
|
+
this.showError(embed, 'Failed to load GitHub Gist. Ensure the Gist is public and the URL is correct.');
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
if (timeoutMs > 0) {
|
|
551
|
+
timeoutId = setTimeout(() => {
|
|
552
|
+
if (!settled) {
|
|
553
|
+
this.showError(embed, 'GitHub Gist timed out. Ensure the Gist is public and the URL is correct.');
|
|
554
|
+
}
|
|
555
|
+
}, timeoutMs);
|
|
556
|
+
}
|
|
504
557
|
|
|
505
558
|
embed.innerHTML = '';
|
|
506
|
-
embed.appendChild(
|
|
559
|
+
embed.appendChild(iframe);
|
|
507
560
|
break;
|
|
561
|
+
}
|
|
508
562
|
}
|
|
509
563
|
} catch (error) {
|
|
510
564
|
this.showError(embed, error.message);
|