UX: Remove lazy-yt and include lazy-videos

This commit is contained in:
Jan Cernik 2023-03-16 21:24:15 -03:00
parent 32ad46c551
commit cc5c17a01e
No known key found for this signature in database
23 changed files with 487 additions and 428 deletions

2
.gitignore vendored
View File

@ -38,7 +38,7 @@
!/plugins/discourse-local-dates
!/plugins/discourse-narrative-bot
!/plugins/discourse-presence
!/plugins/lazy-yt/
!/plugins/discourse-lazy-videos/
!/plugins/chat/
!/plugins/poll/
!/plugins/styleguide

View File

@ -0,0 +1,12 @@
## Discourse Lazy Videos
Adds lazy loading support for embedded videos
### Supported providers
- YouTube
- Vimeo
### Experimental
- TikTok

View File

@ -0,0 +1,11 @@
{{#if @providerName}}
<iframe
src={{this.iframeSrc}}
title={{@title}}
allowFullScreen
scrolling="no"
frameborder="0"
seamless="seamless"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
></iframe>
{{/if}}

View File

@ -0,0 +1,14 @@
import Component from "@glimmer/component";
export default class LazyVideo extends Component {
get iframeSrc() {
switch (this.args.providerName) {
case "youtube":
return `https://www.youtube.com/embed/${this.args.videoId}?autoplay=1`;
case "vimeo":
return `https://player.vimeo.com/video/${this.args.videoId}?autoplay=1`;
case "tiktok":
return `https://www.tiktok.com/embed/v2/${this.args.videoId}`;
}
}
}

View File

@ -0,0 +1,51 @@
<div
class={{concat-class
"lazy-video-container"
(concat @videoAttributes.providerName "-onebox")
(if this.isLoaded "video-loaded")
}}
data-video-id={{@videoAttributes.id}}
data-video-title={{@videoAttributes.title}}
data-provider-name={{@videoAttributes.providerName}}
>
{{#if this.isLoaded}}
<LazyIframe
@providerName={{@videoAttributes.providerName}}
@title={{@videoAttributes.title}}
@videoId={{@videoAttributes.id}}
/>
{{else}}
<div
class={{concat-class "video-thumbnail" @videoAttributes.providerName}}
tabindex="0"
{{on "click" this.loadEmbed}}
{{on "keypress" this.loadEmbed}}
>
<img
class={{concat @videoAttributes.providerName "-thumbnail"}}
src={{@videoAttributes.thumbnail}}
title={{@videoAttributes.title}}
loading="lazy"
/>
<div
class={{concat-class
"icon"
(concat @videoAttributes.providerName "-icon")
}}
></div>
</div>
<div class="title-container">
<div class="title-wrapper">
<a
target="_blank"
rel="noopener noreferrer"
class="title-link"
href={{@videoAttributes.url}}
title={{@videoAttributes.title}}
>
{{@videoAttributes.title}}
</a>
</div>
</div>
{{/if}}
</div>

View File

@ -0,0 +1,23 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { tracked } from "@glimmer/tracking";
export default class LazyVideo extends Component {
@tracked isLoaded = false;
@action
loadEmbed() {
if (!this.isLoaded) {
this.isLoaded = true;
this.args.callback?.();
}
}
@action
onKeyPress(event) {
if (event.key === "Enter") {
event.preventDefault();
this.loadEmbed();
}
}
}

View File

@ -0,0 +1,42 @@
import { withPluginApi } from "discourse/lib/plugin-api";
import getVideoAttributes from "../lib/lazy-video-attributes";
import { hbs } from "ember-cli-htmlbars";
function initLazyEmbed(api) {
api.decorateCookedElement(
(cooked, helper) => {
if (cooked.classList.contains("d-editor-preview")) {
return;
}
const lazyContainers = cooked.querySelectorAll(".lazy-video-container");
lazyContainers.forEach((container) => {
const callback = () => {
const postId = cooked.closest("article")?.dataset?.postId;
if (postId) {
api.preventCloak(parseInt(postId, 10));
}
};
const videoAttributes = getVideoAttributes(container);
const lazyVideo = helper.renderGlimmer(
"p.lazy-video-wrapper",
hbs`<LazyVideo @videoAttributes={{@data.param}} @callback={{@data.callback}}/>`,
{ param: videoAttributes, callback }
);
container.replaceWith(lazyVideo);
});
},
{ onlyStream: true, id: "discourse-lazy-videos" }
);
}
export default {
name: "discourse-lazy-videos",
initialize() {
withPluginApi("1.6.0", initLazyEmbed);
},
};

View File

@ -0,0 +1,15 @@
import { sanitize } from "discourse/lib/text";
export default function getVideoAttributes(cooked) {
if (!cooked.classList.contains("lazy-video-container")) {
return {};
}
const url = cooked.querySelector("a")?.getAttribute("href");
const thumbnail = cooked.querySelector("img")?.getAttribute("src");
const title = sanitize(cooked.dataset.videoTitle);
const providerName = cooked.dataset.providerName;
const id = sanitize(cooked.dataset.videoId);
return { url, thumbnail, title, providerName, id };
}

View File

@ -0,0 +1,172 @@
.lazy-video-container {
z-index: z("base");
position: relative;
display: block;
height: 0;
padding: 0 0 56.25% 0;
background-color: #000;
margin-bottom: 12px;
.video-thumbnail {
cursor: pointer;
img {
object-fit: cover;
width: 100%;
}
&:hover,
&:focus {
.icon {
transform: translate(-50%, -50%) scale(1.1);
}
}
&:focus {
outline: 5px auto Highlight;
outline: 5px auto -webkit-focus-ring-color;
}
&:active {
outline: 0px;
}
}
.title-container {
position: absolute;
display: flex;
align-items: center;
top: 0;
z-index: 935;
width: 100%;
height: 60px;
overflow: hidden;
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(255, 0, 0, 0));
.title-wrapper {
overflow: hidden;
padding-inline: 20px;
padding-block: 10px;
.title-link {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #fff;
text-decoration: none;
font-size: 18px;
font-family: Arial, sans-serif;
&:hover {
text-decoration: underline;
}
}
}
}
iframe {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
.icon {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
transition: 150ms;
// Default play button
background: svg-uri(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><path fill='#181818' d='M512 256c0 141.4-114.6 256-256 256S0 397.4 0 256S114.6 0 256 0S512 114.6 512 256zM188.3 147.1c-7.6 4.2-12.3 12.3-12.3 20.9V344c0 8.7 4.7 16.7 12.3 20.9s16.8 4.1 24.3-.5l144-88c7.1-4.4 11.5-12.1 11.5-20.5s-4.4-16.1-11.5-20.5l-144-88c-7.4-4.5-16.7-4.7-24.3-.5z'/></svg>"
);
width: 60px;
height: 60px;
&.youtube-icon {
width: 68px;
height: 48px;
background: svg-uri(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 68 48'><path d='M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z' fill='#f00'></path><path d='M 45,24 27,14 27,34' fill='#fff'></path></svg>"
);
}
&.vimeo-icon {
width: 77px;
height: 44px;
background: svg-uri(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 37.042 21.167'><g transform='translate(.026013)'><rect x='-.026013' y='8.8818e-16' width='37.042' height='21.167' rx='1.3229' ry='1.3229' fill='#00adef' stroke-width='.33658'/><g transform='matrix(.39688 0 0 .39688 10.557 2.6459)' display='block' fill='none'><path d='m31.666 20c0 0.5928-0.3148 1.141-0.8269 1.4397l-20 11.667c-0.5155 0.3007-1.1524 0.3029-1.6699 0.0056-0.51749-0.2972-0.83656-0.8484-0.83656-1.4452v-23.333c0-0.59677 0.31907-1.148 0.83656-1.4452s1.1544-0.29509 1.6699 0.00561l20 11.667c0.5121 0.2987 0.8269 0.8469 0.8269 1.4396z' fill='#fff'/></g></g></svg>"
);
}
&.tiktok-icon {
width: 58px;
height: 64px;
background: svg-uri(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 29 32'> <g stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' > <g id='编组-2' transform='translate(0.979236, 0.000000)' fill-rule='nonzero' > <path d='M10.7907645,12.33 L10.7907645,11.11 C10.3672629,11.0428887 9.93950674,11.0061284 9.51076448,10.9999786 C5.35996549,10.9912228 1.68509679,13.6810205 0.438667694,17.6402658 C-0.807761399,21.5995112 0.663505842,25.9093887 4.07076448,28.28 C1.51848484,25.5484816 0.809799545,21.5720834 2.26126817,18.1270053 C3.71273679,14.6819273 7.05329545,12.4115428 10.7907645,12.33 L10.7907645,12.33 Z' id='路径' fill='#25F4EE' ></path> <path d='M11.0207645,26.15 C13.3415287,26.1468776 15.2491662,24.3185414 15.3507645,22 L15.3507645,1.31 L19.1307645,1.31 C19.0536068,0.877682322 19.0167818,0.439130992 19.0207645,0 L13.8507645,0 L13.8507645,20.67 C13.764798,23.0003388 11.8526853,24.846212 9.52076448,24.85 C8.82390914,24.844067 8.13842884,24.6726969 7.52076448,24.35 C8.33268245,25.4749154 9.63346203,26.1438878 11.0207645,26.15 Z' id='路径' fill='#25F4EE' ></path> <path d='M26.1907645,8.33 L26.1907645,7.18 C24.79964,7.18047625 23.4393781,6.76996242 22.2807645,6 C23.2964446,7.18071769 24.6689622,7.99861177 26.1907645,8.33 L26.1907645,8.33 Z' id='路径' fill='#25F4EE' ></path> <path d='M22.2807645,6 C21.1394675,4.70033161 20.5102967,3.02965216 20.5107645,1.3 L19.1307645,1.3 C19.4909812,3.23268519 20.6300383,4.93223067 22.2807645,6 L22.2807645,6 Z' id='路径' fill='#FE2C55' ></path> <path d='M9.51076448,16.17 C7.51921814,16.1802178 5.79021626,17.544593 5.31721201,19.4791803 C4.84420777,21.4137677 5.74860956,23.4220069 7.51076448,24.35 C6.55594834,23.0317718 6.42106871,21.2894336 7.16162883,19.8399613 C7.90218896,18.3904889 9.39306734,17.4787782 11.0207645,17.48 C11.4547752,17.4854084 11.8857908,17.5527546 12.3007645,17.68 L12.3007645,12.42 C11.8769919,12.3565056 11.4492562,12.3230887 11.0207645,12.32 L10.7907645,12.32 L10.7907645,16.32 C10.3736368,16.2081544 9.94244934,16.1576246 9.51076448,16.17 Z' id='路径' fill='#FE2C55' ></path> <path d='M26.1907645,8.33 L26.1907645,12.33 C23.61547,12.3250193 21.107025,11.5098622 19.0207645,10 L19.0207645,20.51 C19.0097352,25.7544158 14.7551919,30.0000116 9.51076448,30 C7.56312784,30.0034556 5.66240321,29.4024912 4.07076448,28.28 C6.72698674,31.1368108 10.8608257,32.0771989 14.4914706,30.6505586 C18.1221155,29.2239183 20.5099375,25.7208825 20.5107645,21.82 L20.5107645,11.34 C22.604024,12.8399663 25.1155724,13.6445013 27.6907645,13.64 L27.6907645,8.49 C27.1865925,8.48839535 26.6839313,8.43477816 26.1907645,8.33 Z' id='路径' fill='#FE2C55' ></path> <path d='M19.0207645,20.51 L19.0207645,10 C21.1134087,11.5011898 23.6253623,12.3058546 26.2007645,12.3 L26.2007645,8.3 C24.6792542,7.97871265 23.3034403,7.17147491 22.2807645,6 C20.6300383,4.93223067 19.4909812,3.23268519 19.1307645,1.3 L15.3507645,1.3 L15.3507645,22 C15.2751521,23.8467664 14.0381991,25.4430201 12.268769,25.9772302 C10.4993389,26.5114403 8.58570942,25.8663815 7.50076448,24.37 C5.73860956,23.4420069 4.83420777,21.4337677 5.30721201,19.4991803 C5.78021626,17.564593 7.50921814,16.2002178 9.50076448,16.19 C9.934903,16.1938693 10.3661386,16.2612499 10.7807645,16.39 L10.7807645,12.39 C7.0223379,12.4536691 3.65653929,14.7319768 2.20094561,18.1976761 C0.745351938,21.6633753 1.47494493,25.6617476 4.06076448,28.39 C5.66809542,29.4755063 7.57158782,30.0378224 9.51076448,30 C14.7551919,30.0000116 19.0097352,25.7544158 19.0207645,20.51 Z' id='路径' fill='#000000' ></path> </g> </g> </svg> "
);
}
}
}
// TikTok iframe isn't fluid
.lazy-video-container.tiktok-onebox {
width: 332px;
height: 745px;
padding: 0;
.video-thumbnail.tiktok img {
height: 745px;
}
iframe {
min-width: 332px;
height: 742px;
background-color: #fff;
border-top: 3px solid #fff;
border-radius: 9px;
}
}
// Temporary styles until we support chat
.chat-message-content .lazy-video-container {
padding: 0;
margin-bottom: 0;
background-color: transparent;
object-fit: contain;
height: 175px;
text-overflow: ellipsis;
&::before {
content: attr(data-provider-name) " | " attr(data-video-title);
text-transform: capitalize;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
height: 25px;
}
a {
display: block;
height: 150px;
img {
height: 100%;
pointer-events: none;
}
}
}
.ytp-thumbnail-image {
max-height: 150px !important;
width: unset !important;
pointer-events: none !important;
display: block;
}

View File

@ -0,0 +1,3 @@
en:
site_settings:
lazy_videos_enabled: "Enable the Lazy Videos plugin"

View File

@ -0,0 +1,17 @@
plugins:
lazy_videos_enabled:
default: true
client: true
hidden: false
lazy_youtube_enabled:
default: true
client: true
hidden: false
lazy_vimeo_enabled:
default: true
client: true
hidden: false
lazy_tiktok_enabled:
default: false
client: true
hidden: false

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
require "onebox"
class Onebox::Engine::TiktokOnebox
include Onebox::Engine
alias_method :default_onebox_to_html, :to_html
def to_html
if SiteSetting.lazy_videos_enabled && SiteSetting.lazy_tiktok_enabled &&
oembed_data.embed_product_id
thumbnail_url = oembed_data.thumbnail_url
escaped_title = ERB::Util.html_escape(oembed_data.title)
<<~HTML
<div class="tiktok-onebox lazy-video-container"
data-video-id="#{oembed_data.embed_product_id}"
data-video-title="#{escaped_title}"
data-provider-name="tiktok">
<a href="#{url}" target="_blank">
<img class="tiktok-thumbnail"
src="#{thumbnail_url}"
title="#{escaped_title}">
</a>
</div>
HTML
else
default_onebox_to_html
end
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
require "onebox"
class Onebox::Engine::VimeoOnebox
include Onebox::Engine
alias_method :default_onebox_to_html, :to_html
def to_html
if SiteSetting.lazy_videos_enabled && SiteSetting.lazy_vimeo_enabled
video_id = oembed_data[:video_id]
thumbnail_url = "https://vumbnail.com/#{oembed_data[:video_id]}.jpg"
escaped_title = ERB::Util.html_escape(og_data.title)
<<~HTML
<div class="vimeo-onebox lazy-video-container"
data-video-id="#{video_id}"
data-video-title="#{escaped_title}"
data-provider-name="vimeo">
<a href="https://vimeo.com/#{video_id}" target="_blank">
<img class="vimeo-thumbnail"
src="#{thumbnail_url}"
title="#{escaped_title}">
</a>
</div>
HTML
else
default_onebox_to_html
end
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
require "onebox"
class Onebox::Engine::YoutubeOnebox
include Onebox::Engine
alias_method :default_onebox_to_html, :to_html
def to_html
if SiteSetting.lazy_videos_enabled && SiteSetting.lazy_youtube_enabled && video_id &&
!params["list"]
result = parse_embed_response
result ||= get_opengraph.data
thumbnail_url = "https://img.youtube.com/vi/#{video_id}/maxresdefault.jpg"
thumbnail_response = Net::HTTP.get_response(URI(thumbnail_url))
thumbnail_url = result[:image] if !thumbnail_response.kind_of?(Net::HTTPSuccess)
escaped_title = ERB::Util.html_escape(video_title)
<<~HTML
<div class="youtube-onebox lazy-video-container"
data-video-id="#{video_id}"
data-video-title="#{escaped_title}"
data-provider-name="youtube">
<a href="https://www.youtube.com/watch?v=#{video_id}" target="_blank">
<img class="youtube-thumbnail"
src="#{thumbnail_url}"
title="#{escaped_title}">
</a>
</div>
HTML
else
default_onebox_to_html
end
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
# name: discourse-lazy-videos
# about: Lazy loading for embedded videos
# version: 0.1
# authors: Jan Cernik
# url: https://github.com/discourse/discourse-lazy-videos
hide_plugin if self.respond_to?(:hide_plugin)
enabled_site_setting :lazy_videos_enabled
register_asset "stylesheets/lazy-videos.scss"
require_relative "lib/lazy-videos/lazy_youtube"
require_relative "lib/lazy-videos/lazy_vimeo"
require_relative "lib/lazy-videos/lazy_tiktok"
after_initialize do
on(:reduce_cooked) do |fragment|
fragment
.css(".lazy-video-container a")
.each do |video|
href = video["href"]
video.inner_html += "<p><a href=" #{href}">#{href}</a></p>"
end
end
end

View File

@ -1,3 +0,0 @@
# lazy-yt
Lazy load YouTube videos plugin for [Discourse](http://discourse.org), highly inspired by the [lazyYT](https://github.com/tylerpearson/lazyYT) jQuery plugin.

View File

@ -1,33 +0,0 @@
import { withPluginApi } from "discourse/lib/plugin-api";
import initLazyYt from "../lib/lazyYT";
export default {
name: "apply-lazyYT",
initialize() {
withPluginApi("0.1", (api) => {
initLazyYt($);
api.decorateCooked(
($elem) => {
const iframes = $(".lazyYT", $elem);
if (iframes.length === 0) {
return;
}
$(".lazyYT", $elem).lazyYT({
onPlay(e, $el) {
// don't cloak posts that have playing videos in them
const postId = parseInt(
$el.closest("article").data("post-id"),
10
);
if (postId) {
api.preventCloak(postId);
}
},
});
},
{ id: "discourse-lazyyt" }
);
});
},
};

View File

@ -1,179 +0,0 @@
/*!
* lazyYT (lazy load YouTube videos)
* v1.0.1 - 2014-12-30
* (CC) This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
* http://creativecommons.org/licenses/by-sa/4.0/
* Contributors: https://github.com/tylerpearson/lazyYT/graphs/contributors || https://github.com/daugilas/lazyYT/graphs/contributors
*
* Usage: <div class="lazyYT" data-youtube-id="laknj093n" data-parameters="rel=0">loading...</div>
*
* Note: Discourse has forked this from the original, beware when updating the file.
*
*/
import escape from "discourse-common/lib/escape";
export default function initLazyYt($) {
"use strict";
function setUp($el, settings) {
let width = $el.data("width"),
height = $el.data("height"),
ratio = $el.data("ratio") ? $el.data("ratio") : settings.default_ratio,
id = $el.data("youtube-id"),
title = $el.data("youtube-title"),
padding_bottom,
innerHtml = [],
$thumb,
thumb_img,
youtube_parameters = $el.data("parameters") || "";
ratio = ratio.split(":");
// width and height might override default_ratio value
if (typeof width === "number" && typeof height === "number") {
$el.width(width);
padding_bottom = height + "px";
} else if (typeof width === "number") {
$el.width(width);
padding_bottom = (width * ratio[1]) / ratio[0] + "px";
} else {
width = $el.width();
// no width means that container is fluid and will be the size of its parent
if (width === 0) {
width = $el.parent().width();
}
padding_bottom = (ratio[1] / ratio[0]) * 100 + "%";
}
//
// This HTML will be placed inside 'lazyYT' container
innerHtml.push('<div class="ytp-thumbnail" tabIndex="0">');
// Play button from YouTube (exactly as it is in YouTube)
innerHtml.push('<div class="ytp-large-play-button"');
if (width <= 640) {
innerHtml.push(' style="transform: scale(0.563888888888889);"');
}
innerHtml.push(">");
innerHtml.push("<svg>");
innerHtml.push(
'<path fill-rule="evenodd" clip-rule="evenodd" fill="#1F1F1F" class="ytp-large-play-button-svg" d="M84.15,26.4v6.35c0,2.833-0.15,5.967-0.45,9.4c-0.133,1.7-0.267,3.117-0.4,4.25l-0.15,0.95c-0.167,0.767-0.367,1.517-0.6,2.25c-0.667,2.367-1.533,4.083-2.6,5.15c-1.367,1.4-2.967,2.383-4.8,2.95c-0.633,0.2-1.316,0.333-2.05,0.4c-0.767,0.1-1.3,0.167-1.6,0.2c-4.9,0.367-11.283,0.617-19.15,0.75c-2.434,0.034-4.883,0.067-7.35,0.1h-2.95C38.417,59.117,34.5,59.067,30.3,59c-8.433-0.167-14.05-0.383-16.85-0.65c-0.067-0.033-0.667-0.117-1.8-0.25c-0.9-0.133-1.683-0.283-2.35-0.45c-2.066-0.533-3.783-1.5-5.15-2.9c-1.033-1.067-1.9-2.783-2.6-5.15C1.317,48.867,1.133,48.117,1,47.35L0.8,46.4c-0.133-1.133-0.267-2.55-0.4-4.25C0.133,38.717,0,35.583,0,32.75V26.4c0-2.833,0.133-5.95,0.4-9.35l0.4-4.25c0.167-0.966,0.417-2.05,0.75-3.25c0.7-2.333,1.567-4.033,2.6-5.1c1.367-1.434,2.967-2.434,4.8-3c0.633-0.167,1.333-0.3,2.1-0.4c0.4-0.066,0.917-0.133,1.55-0.2c4.9-0.333,11.283-0.567,19.15-0.7C35.65,0.05,39.083,0,42.05,0L45,0.05c2.467,0,4.933,0.034,7.4,0.1c7.833,0.133,14.2,0.367,19.1,0.7c0.3,0.033,0.833,0.1,1.6,0.2c0.733,0.1,1.417,0.233,2.05,0.4c1.833,0.566,3.434,1.566,4.8,3c1.066,1.066,1.933,2.767,2.6,5.1c0.367,1.2,0.617,2.284,0.75,3.25l0.4,4.25C84,20.45,84.15,23.567,84.15,26.4z M33.3,41.4L56,29.6L33.3,17.75V41.4z"></path>'
);
innerHtml.push(
'<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" points="33.3,41.4 33.3,17.75 56,29.6"></polygon>'
);
innerHtml.push("</svg>");
innerHtml.push("</div>"); // end of .ytp-large-play-button
innerHtml.push("</div>"); // end of .ytp-thumbnail
// Video title (info bar)
innerHtml.push('<div class="html5-info-bar">');
innerHtml.push('<div class="html5-title">');
innerHtml.push('<div class="html5-title-text-wrapper">');
innerHtml.push(
'<a class="html5-title-text" target="_blank" tabindex="3100" href="https://www.youtube.com/watch?v=',
escape(id),
'">'
);
if (title === undefined || title === null || title === "") {
innerHtml.push("youtube.com/watch?v=" + escape(id));
} else {
innerHtml.push(escape(title));
}
innerHtml.push("</a>");
innerHtml.push("</div>"); // .html5-title
innerHtml.push("</div>"); // .html5-title-text-wrapper
innerHtml.push("</div>"); // end of Video title .html5-info-bar
let prefetchedThumbnail = $el[0].querySelector(".ytp-thumbnail-image");
$el
.css({
"padding-bottom": padding_bottom,
})
.html(innerHtml.join(""));
if (width > 640) {
thumb_img = "maxresdefault.jpg";
} else if (width > 480) {
thumb_img = "sddefault.jpg";
} else if (width > 320) {
thumb_img = "hqdefault.jpg";
} else if (width > 120) {
thumb_img = "mqdefault.jpg";
} else if (width === 0) {
// sometimes it fails on fluid layout
thumb_img = "hqdefault.jpg";
} else {
thumb_img = "default.jpg";
}
if (prefetchedThumbnail) {
$el.find(".ytp-thumbnail").append(prefetchedThumbnail);
} else {
// Fallback for old posts which were baked before the lazy-yt onebox prefetched a thumbnail
$el
.find(".ytp-thumbnail")
.append(
$(
[
'<img class="ytp-thumbnail-image" src="https://img.youtube.com/vi/',
escape(id),
"/",
thumb_img,
'">',
].join("")
)
);
}
$thumb = $el
.find(".ytp-thumbnail")
.addClass("lazyYT-image-loaded")
.on("keypress click", function (e) {
// Only support Enter for keypress
if (e.type === "keypress" && e.keyCode !== 13) {
return;
}
e.preventDefault();
if (
!$el.hasClass("lazyYT-video-loaded") &&
$thumb.hasClass("lazyYT-image-loaded")
) {
$el
.html(
'<iframe src="//www.youtube.com/embed/' +
escape(id) +
"?autoplay=1&" +
youtube_parameters +
'" frameborder="0" allowfullscreen></iframe>'
)
.addClass("lazyYT-video-loaded");
}
if (settings.onPlay) {
settings.onPlay(e, $el);
}
});
}
$.fn.lazyYT = function (newSettings) {
let defaultSettings = {
default_ratio: "16:9",
callback: null, // TODO: execute callback if given
container_class: "lazyYT-container",
};
let settings = Object.assign(defaultSettings, newSettings);
return this.each(function () {
let $el = $(this).addClass(settings.container_class);
setUp($el, settings);
});
};
}

View File

@ -1,127 +0,0 @@
/*!
* lazyYT (lazy load YouTube videos)
* v1.0.1 - 2014-12-30
* (CC) This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
* http://creativecommons.org/licenses/by-sa/4.0/
* Contributors: https://github.com/tylerpearson/lazyYT/graphs/contributors || https://github.com/daugilas/lazyYT/graphs/contributors
*/
.lazyYT-container {
position: relative;
z-index: z("base");
display: block;
height: 0;
padding: 0 0 56.25% 0;
overflow: hidden;
background-color: #000000;
margin-bottom: 12px;
}
.lazyYT-container iframe {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
/*
* Video Title (YouTube style)
*/
.lazyYT-container .html5-info-bar {
position: absolute;
top: 0;
z-index: 935;
width: 100%;
height: 30px;
overflow: hidden;
font-family: Arial, sans-serif;
font-size: 12px;
color: #fff;
background-color: rgba(0, 0, 0, 0.8);
-webkit-transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1);
-moz-transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1);
transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1);
}
.lazyYT-container .html5-title {
padding-right: 6px;
padding-left: 12px;
}
.lazyYT-container .html5-title-text-wrapper {
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
word-wrap: normal;
white-space: nowrap;
}
.lazyYT-container .html5-title-text {
width: 100%;
font-size: 13px;
line-height: 30px;
color: #ccc;
text-decoration: none;
}
.lazyYT-container .html5-title-text:hover {
color: #fff;
text-decoration: underline;
}
/*
* Thumbnail
*/
.ytp-thumbnail {
padding-bottom: inherit;
cursor: pointer;
background-position: 50% 50%;
background-repeat: no-repeat;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
/*
* Play button (YouTube style)
*/
.ytp-large-play-button {
position: absolute;
top: 50% !important;
left: 50% !important;
width: 86px !important;
height: 60px !important;
padding: 0 !important;
margin: -29px 0 0 -42px !important;
font-size: normal !important;
font-weight: normal !important;
line-height: 1 !important;
opacity: 0.9;
z-index: 935;
}
.ytp-large-play-button-svg {
opacity: 0.9;
fill: #1f1f1f;
}
.lazyYT-image-loaded:hover .ytp-large-play-button-svg,
.lazyYT-image-loaded:focus .ytp-large-play-button-svg,
.ytp-large-play-button:focus .ytp-large-play-button-svg {
opacity: 1;
fill: #cc181e;
}
.ytp-thumbnail-image {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
}

View File

@ -1,3 +0,0 @@
.lazyYT {
max-width: 100%;
}

View File

@ -1,3 +0,0 @@
en:
site_settings:
lazy_yt_enabled: "Enable the LazyYT plugin"

View File

@ -1,5 +0,0 @@
plugins:
lazy_yt_enabled:
default: true
client: false
hidden: true

View File

@ -1,74 +0,0 @@
# frozen_string_literal: true
# name: lazy-yt
# about: Uses the lazyYT plugin to lazy load Youtube videos
# version: 1.0.1
# authors: Arpit Jalan
# url: https://github.com/discourse/discourse/tree/main/plugins/lazy-yt
hide_plugin if self.respond_to?(:hide_plugin)
enabled_site_setting :lazy_yt_enabled
require "onebox"
# stylesheet
register_asset "stylesheets/lazyYT.css"
register_asset "stylesheets/lazyYT_mobile.scss", :mobile
# freedom patch YouTube Onebox
class Onebox::Engine::YoutubeOnebox
include Onebox::Engine
alias_method :yt_onebox_to_html, :to_html
def to_html
if SiteSetting.lazy_yt_enabled && video_id && !params["list"]
size_restricted = [params["width"], params["height"]].any?
video_width = (params["width"] && params["width"].to_i <= 695) ? params["width"] : 690 # embed width
video_height = (params["height"] && params["height"].to_i <= 500) ? params["height"] : 388 # embed height
size_tags = ["width=\"#{video_width}\"", "height=\"#{video_height}\""]
result = parse_embed_response
result ||= get_opengraph.data
thumbnail_url = result[:image] || "https://img.youtube.com/vi/#{video_id}/hqdefault.jpg"
# Put in the LazyYT div instead of the iframe
escaped_title = ERB::Util.html_escape(video_title)
<<~HTML
<div class="onebox lazyYT lazyYT-container"
data-youtube-id="#{video_id}"
data-youtube-title="#{escaped_title}"
#{size_restricted ? size_tags.map { |t| "data-#{t}" }.join(" ") : ""}
data-parameters="#{embed_params}">
<a href="https://www.youtube.com/watch?v=#{video_id}" target="_blank">
<img class="ytp-thumbnail-image"
src="#{thumbnail_url}"
#{size_restricted ? size_tags.join(" ") : ""}
title="#{escaped_title}">
</a>
</div>
HTML
else
yt_onebox_to_html
end
end
end
after_initialize do
on(:reduce_cooked) do |fragment|
fragment
.css(".lazyYT")
.each do |yt|
begin
youtube_id = yt["data-youtube-id"]
parameters = yt["data-parameters"]
uri = URI("https://www.youtube.com/embed/#{youtube_id}?autoplay=1&#{parameters}")
yt.replace %{<p><a href="#{uri.to_s}">https://#{uri.host}#{uri.path}</a></p>}
rescue URI::InvalidURIError
# remove any invalid/weird URIs
yt.remove
end
end
end
end