Remove YouTube embedding in the chat (#1080)
* Remove YouTube embedding in the chat * Remove youtube-lite dependency * Implelment Feedback
This commit is contained in:
@@ -5,7 +5,6 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@joeattardi/emoji-button": "^4.6.0",
|
"@joeattardi/emoji-button": "^4.6.0",
|
||||||
"@justinribeiro/lite-youtube": "^0.9.1",
|
|
||||||
"@videojs/http-streaming": "2.8.2",
|
"@videojs/http-streaming": "2.8.2",
|
||||||
"@videojs/themes": "^1.0.1",
|
"@videojs/themes": "^1.0.1",
|
||||||
"htm": "^3.0.4",
|
"htm": "^3.0.4",
|
||||||
@@ -26,7 +25,6 @@
|
|||||||
"@videojs/http-streaming/dist/videojs-http-streaming.min.js",
|
"@videojs/http-streaming/dist/videojs-http-streaming.min.js",
|
||||||
"video.js/dist/video-js.min.css",
|
"video.js/dist/video-js.min.css",
|
||||||
"@joeattardi/emoji-button",
|
"@joeattardi/emoji-button",
|
||||||
"@justinribeiro/lite-youtube",
|
|
||||||
"htm",
|
"htm",
|
||||||
"preact",
|
"preact",
|
||||||
"mark.js/dist/mark.es6.min.js",
|
"mark.js/dist/mark.es6.min.js",
|
||||||
|
|||||||
@@ -63,7 +63,6 @@
|
|||||||
<script type="preload" src="/js/web_modules/common/core-fed3ccd8.js"></script>
|
<script type="preload" src="/js/web_modules/common/core-fed3ccd8.js"></script>
|
||||||
<script type="preload" src="/js/web_modules/htm.js"></script>
|
<script type="preload" src="/js/web_modules/htm.js"></script>
|
||||||
<script type="preload" src="/js/web_modules/@joeattardi/emoji-button.js"></script>
|
<script type="preload" src="/js/web_modules/@joeattardi/emoji-button.js"></script>
|
||||||
<script type="preload" src="/js/web_modules/@justinribeiro/lite-youtube.js"></script>
|
|
||||||
<script type="preload" src="/js/web_modules/markjs/dist/mark.es6.min.js"></script>
|
<script type="preload" src="/js/web_modules/markjs/dist/mark.es6.min.js"></script>
|
||||||
<script type="preload" src="/js/web_modules/preact.js"></script>
|
<script type="preload" src="/js/web_modules/preact.js"></script>
|
||||||
<script type="preload" src="/js/web_modules/@videojs/http-streaming/dist/videojs-http-streaming.min.js"></script>
|
<script type="preload" src="/js/web_modules/@videojs/http-streaming/dist/videojs-http-streaming.min.js"></script>
|
||||||
|
|||||||
@@ -122,10 +122,7 @@ function getMessageWithEmbeds(message) {
|
|||||||
var anchors = container.getElementsByTagName('a');
|
var anchors = container.getElementsByTagName('a');
|
||||||
for (var i = 0; i < anchors.length; i++) {
|
for (var i = 0; i < anchors.length; i++) {
|
||||||
const url = anchors[i].href;
|
const url = anchors[i].href;
|
||||||
if (getYoutubeIdFromURL(url)) {
|
if (url.indexOf('instagram.com/p/') > -1) {
|
||||||
const youtubeID = getYoutubeIdFromURL(url);
|
|
||||||
embedText += getYoutubeEmbedFromID(youtubeID);
|
|
||||||
} else if (url.indexOf('instagram.com/p/') > -1) {
|
|
||||||
embedText += getInstagramEmbedFromURL(url);
|
embedText += getInstagramEmbedFromURL(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,26 +139,6 @@ function getMessageWithEmbeds(message) {
|
|||||||
return message + embedText;
|
return message + embedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getYoutubeIdFromURL(url) {
|
|
||||||
try {
|
|
||||||
var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
|
|
||||||
var match = url.match(regExp);
|
|
||||||
|
|
||||||
if (match && match[2].length == 11) {
|
|
||||||
return match[2];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getYoutubeEmbedFromID(id) {
|
|
||||||
return `<div class="chat-embed youtube-embed"><lite-youtube videoid="${id}" /></div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInstagramEmbedFromURL(url) {
|
function getInstagramEmbedFromURL(url) {
|
||||||
const urlObject = new URL(url.replace(/\/$/, ''));
|
const urlObject = new URL(url.replace(/\/$/, ''));
|
||||||
urlObject.pathname += '/embed';
|
urlObject.pathname += '/embed';
|
||||||
|
|||||||
@@ -1,302 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* The shadowDom / Intersection Observer version of Paul's concept:
|
|
||||||
* https://github.com/paulirish/lite-youtube-embed
|
|
||||||
*
|
|
||||||
* A lightweight YouTube embed. Still should feel the same to the user, just
|
|
||||||
* MUCH faster to initialize and paint.
|
|
||||||
*
|
|
||||||
* Thx to these as the inspiration
|
|
||||||
* https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html
|
|
||||||
* https://autoplay-youtube-player.glitch.me/
|
|
||||||
*
|
|
||||||
* Once built it, I also found these (👍👍):
|
|
||||||
* https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube
|
|
||||||
* https://github.com/Daugilas/lazyYT https://github.com/vb/lazyframe
|
|
||||||
*/
|
|
||||||
class LiteYTEmbed extends HTMLElement {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.iframeLoaded = false;
|
|
||||||
this.setupDom();
|
|
||||||
}
|
|
||||||
static get observedAttributes() {
|
|
||||||
return ['videoid'];
|
|
||||||
}
|
|
||||||
connectedCallback() {
|
|
||||||
this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {
|
|
||||||
once: true,
|
|
||||||
});
|
|
||||||
this.addEventListener('click', () => this.addIframe());
|
|
||||||
}
|
|
||||||
get videoId() {
|
|
||||||
return encodeURIComponent(this.getAttribute('videoid') || '');
|
|
||||||
}
|
|
||||||
set videoId(id) {
|
|
||||||
this.setAttribute('videoid', id);
|
|
||||||
}
|
|
||||||
get videoTitle() {
|
|
||||||
return this.getAttribute('videotitle') || 'Video';
|
|
||||||
}
|
|
||||||
set videoTitle(title) {
|
|
||||||
this.setAttribute('videotitle', title);
|
|
||||||
}
|
|
||||||
get videoPlay() {
|
|
||||||
return this.getAttribute('videoPlay') || 'Play';
|
|
||||||
}
|
|
||||||
set videoPlay(name) {
|
|
||||||
this.setAttribute('videoPlay', name);
|
|
||||||
}
|
|
||||||
get videoStartAt() {
|
|
||||||
return Number(this.getAttribute('videoStartAt') || '0');
|
|
||||||
}
|
|
||||||
set videoStartAt(time) {
|
|
||||||
this.setAttribute('videoStartAt', String(time));
|
|
||||||
}
|
|
||||||
get autoLoad() {
|
|
||||||
return this.hasAttribute('autoload');
|
|
||||||
}
|
|
||||||
set autoLoad(value) {
|
|
||||||
if (value) {
|
|
||||||
this.setAttribute('autoload', '');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.removeAttribute('autoload');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get params() {
|
|
||||||
return `start=${this.videoStartAt}&${this.getAttribute('params')}`;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Define our shadowDOM for the component
|
|
||||||
*/
|
|
||||||
setupDom() {
|
|
||||||
const shadowDom = this.attachShadow({ mode: 'open' });
|
|
||||||
shadowDom.innerHTML = `
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
contain: content;
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
padding-bottom: calc(100% / (16 / 9));
|
|
||||||
}
|
|
||||||
|
|
||||||
#frame, #fallbackPlaceholder, iframe {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#frame {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fallbackPlaceholder {
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
#frame::before {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==);
|
|
||||||
background-position: top;
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
height: 60px;
|
|
||||||
padding-bottom: 50px;
|
|
||||||
width: 100%;
|
|
||||||
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
/* play button */
|
|
||||||
.lty-playbtn {
|
|
||||||
width: 70px;
|
|
||||||
height: 46px;
|
|
||||||
background-color: #212121;
|
|
||||||
z-index: 1;
|
|
||||||
opacity: 0.8;
|
|
||||||
border-radius: 14%; /* TODO: Consider replacing this with YT's actual svg. Eh. */
|
|
||||||
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
#frame:hover .lty-playbtn {
|
|
||||||
background-color: #f00;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
/* play button triangle */
|
|
||||||
.lty-playbtn:before {
|
|
||||||
content: '';
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 11px 0 11px 19px;
|
|
||||||
border-color: transparent transparent transparent #fff;
|
|
||||||
}
|
|
||||||
.lty-playbtn,
|
|
||||||
.lty-playbtn:before {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate3d(-50%, -50%, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Post-click styles */
|
|
||||||
.lyt-activated {
|
|
||||||
cursor: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
#frame.lyt-activated::before,
|
|
||||||
.lyt-activated .lty-playbtn {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div id="frame">
|
|
||||||
<picture>
|
|
||||||
<source id="webpPlaceholder" type="image/webp">
|
|
||||||
<source id="jpegPlaceholder" type="image/jpeg">
|
|
||||||
<img id="fallbackPlaceholder" referrerpolicy="origin">
|
|
||||||
</picture>
|
|
||||||
<button class="lty-playbtn"></button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
this.domRefFrame = this.shadowRoot.querySelector('#frame');
|
|
||||||
this.domRefImg = {
|
|
||||||
fallback: this.shadowRoot.querySelector('#fallbackPlaceholder'),
|
|
||||||
webp: this.shadowRoot.querySelector('#webpPlaceholder'),
|
|
||||||
jpeg: this.shadowRoot.querySelector('#jpegPlaceholder'),
|
|
||||||
};
|
|
||||||
this.domRefPlayButton = this.shadowRoot.querySelector('.lty-playbtn');
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Parse our attributes and fire up some placeholders
|
|
||||||
*/
|
|
||||||
setupComponent() {
|
|
||||||
this.initImagePlaceholder();
|
|
||||||
this.domRefPlayButton.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`);
|
|
||||||
this.setAttribute('title', `${this.videoPlay}: ${this.videoTitle}`);
|
|
||||||
if (this.autoLoad) {
|
|
||||||
this.initIntersectionObserver();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Lifecycle method that we use to listen for attribute changes to period
|
|
||||||
* @param {*} name
|
|
||||||
* @param {*} oldVal
|
|
||||||
* @param {*} newVal
|
|
||||||
*/
|
|
||||||
attributeChangedCallback(name, oldVal, newVal) {
|
|
||||||
switch (name) {
|
|
||||||
case 'videoid': {
|
|
||||||
if (oldVal !== newVal) {
|
|
||||||
this.setupComponent();
|
|
||||||
// if we have a previous iframe, remove it and the activated class
|
|
||||||
if (this.domRefFrame.classList.contains('lyt-activated')) {
|
|
||||||
this.domRefFrame.classList.remove('lyt-activated');
|
|
||||||
this.shadowRoot.querySelector('iframe').remove();
|
|
||||||
this.iframeLoaded = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Inject the iframe into the component body
|
|
||||||
*/
|
|
||||||
addIframe() {
|
|
||||||
if (!this.iframeLoaded) {
|
|
||||||
const iframeHTML = `
|
|
||||||
<iframe frameborder="0"
|
|
||||||
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen
|
|
||||||
src="https://www.youtube.com/embed/${this.videoId}?autoplay=1&${this.params}"
|
|
||||||
></iframe>`;
|
|
||||||
this.domRefFrame.insertAdjacentHTML('beforeend', iframeHTML);
|
|
||||||
this.domRefFrame.classList.add('lyt-activated');
|
|
||||||
this.iframeLoaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Setup the placeholder image for the component
|
|
||||||
*/
|
|
||||||
initImagePlaceholder() {
|
|
||||||
// we don't know which image type to preload, so warm the connection
|
|
||||||
LiteYTEmbed.addPrefetch('preconnect', 'https://i.ytimg.com/');
|
|
||||||
const posterUrlWebp = `https://i.ytimg.com/vi_webp/${this.videoId}/hqdefault.webp`;
|
|
||||||
const posterUrlJpeg = `https://i.ytimg.com/vi/${this.videoId}/hqdefault.jpg`;
|
|
||||||
this.domRefImg.webp.srcset = posterUrlWebp;
|
|
||||||
this.domRefImg.jpeg.srcset = posterUrlJpeg;
|
|
||||||
this.domRefImg.fallback.src = posterUrlJpeg;
|
|
||||||
this.domRefImg.fallback.setAttribute('aria-label', `${this.videoPlay}: ${this.videoTitle}`);
|
|
||||||
this.domRefImg.fallback.setAttribute('alt', `${this.videoPlay}: ${this.videoTitle}`);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Setup the Intersection Observer to load the iframe when scrolled into view
|
|
||||||
*/
|
|
||||||
initIntersectionObserver() {
|
|
||||||
if ('IntersectionObserver' in window &&
|
|
||||||
'IntersectionObserverEntry' in window) {
|
|
||||||
const options = {
|
|
||||||
root: null,
|
|
||||||
rootMargin: '0px',
|
|
||||||
threshold: 0,
|
|
||||||
};
|
|
||||||
const observer = new IntersectionObserver((entries, observer) => {
|
|
||||||
entries.forEach(entry => {
|
|
||||||
if (entry.isIntersecting && !this.iframeLoaded) {
|
|
||||||
LiteYTEmbed.warmConnections();
|
|
||||||
this.addIframe();
|
|
||||||
observer.unobserve(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, options);
|
|
||||||
observer.observe(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Add a <link rel={preload | preconnect} ...> to the head
|
|
||||||
* @param {*} kind
|
|
||||||
* @param {*} url
|
|
||||||
* @param {*} as
|
|
||||||
*/
|
|
||||||
static addPrefetch(kind, url, as) {
|
|
||||||
const linkElem = document.createElement('link');
|
|
||||||
linkElem.rel = kind;
|
|
||||||
linkElem.href = url;
|
|
||||||
if (as) {
|
|
||||||
linkElem.as = as;
|
|
||||||
}
|
|
||||||
linkElem.crossOrigin = 'true';
|
|
||||||
document.head.append(linkElem);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Begin preconnecting to warm up the iframe load Since the embed's netwok
|
|
||||||
* requests load within its iframe, preload/prefetch'ing them outside the
|
|
||||||
* iframe will only cause double-downloads. So, the best we can do is warm up
|
|
||||||
* a few connections to origins that are in the critical path.
|
|
||||||
*
|
|
||||||
* Maybe `<link rel=preload as=document>` would work, but it's unsupported:
|
|
||||||
* http://crbug.com/593267 But TBH, I don't think it'll happen soon with Site
|
|
||||||
* Isolation and split caches adding serious complexity.
|
|
||||||
*/
|
|
||||||
static warmConnections() {
|
|
||||||
if (LiteYTEmbed.preconnected)
|
|
||||||
return;
|
|
||||||
// Host that YT uses to serve JS needed by player, per amp-youtube
|
|
||||||
LiteYTEmbed.addPrefetch('preconnect', 'https://s.ytimg.com');
|
|
||||||
// The iframe document and most of its subresources come right off
|
|
||||||
// youtube.com
|
|
||||||
LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube.com');
|
|
||||||
// The botguard script is fetched off from google.com
|
|
||||||
LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com');
|
|
||||||
// TODO: Not certain if these ad related domains are in the critical path.
|
|
||||||
// Could verify with domain-specific throttling.
|
|
||||||
LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net');
|
|
||||||
LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net');
|
|
||||||
LiteYTEmbed.preconnected = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LiteYTEmbed.preconnected = false;
|
|
||||||
// Register custom element
|
|
||||||
customElements.define('lite-youtube', LiteYTEmbed);
|
|
||||||
|
|
||||||
export { LiteYTEmbed };
|
|
||||||
@@ -179,11 +179,6 @@
|
|||||||
/* height: 15rem; */
|
/* height: 15rem; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-text .youtube-embed {
|
|
||||||
width: 90%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* MESSAGE TEXT CONTENT */
|
/* MESSAGE TEXT CONTENT */
|
||||||
/* MESSAGE TEXT CONTENT */
|
/* MESSAGE TEXT CONTENT */
|
||||||
/* MESSAGE TEXT CONTENT */
|
/* MESSAGE TEXT CONTENT */
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ The styles in this file mostly ovveride those coming from chat.css
|
|||||||
|
|
||||||
#messages-only .message-text .chat-embed,
|
#messages-only .message-text .chat-embed,
|
||||||
#messages-only .message-text .instagram-embed,
|
#messages-only .message-text .instagram-embed,
|
||||||
#messages-only .message-text .embedded-image,
|
#messages-only .message-text .embedded-image {
|
||||||
#messages-only .message-text .youtube-embed {
|
|
||||||
max-width: 350px;
|
max-width: 350px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user