diff --git a/files/assets/css/lite-youtube.css b/files/assets/css/lite-youtube.css
new file mode 100644
index 000000000..78eb5459c
--- /dev/null
+++ b/files/assets/css/lite-youtube.css
@@ -0,0 +1,85 @@
+lite-youtube {
+ background-color: #000;
+ position: relative;
+ display: block;
+ contain: content;
+ background-position: center center;
+ background-size: cover;
+ cursor: pointer;
+ max-width: 720px;
+}
+
+/* gradient */
+lite-youtube::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);
+}
+
+/* responsive iframe with a 16:9 aspect ratio
+ thanks https://css-tricks.com/responsive-iframes/
+*/
+lite-youtube::after {
+ content: "";
+ display: block;
+ padding-bottom: calc(100% / (16 / 9));
+}
+lite-youtube > iframe {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ border: 0;
+}
+
+/* play button */
+lite-youtube > .lty-playbtn {
+ width: 68px;
+ height: 48px;
+ position: absolute;
+ cursor: pointer;
+ transform: translate3d(-50%, -50%, 0);
+ top: 50%;
+ left: 50%;
+ z-index: 1;
+ background-color: transparent;
+ /* YT's actual play button svg */
+ background-image: url('data:image/svg+xml;utf8,');
+ filter: grayscale(100%);
+ transition: filter .1s cubic-bezier(0, 0, 0.2, 1);
+ border: none;
+}
+
+lite-youtube:hover > .lty-playbtn,
+lite-youtube .lty-playbtn:focus {
+ filter: none;
+}
+
+/* Post-click styles */
+lite-youtube.lyt-activated {
+ cursor: unset;
+}
+lite-youtube.lyt-activated::before,
+lite-youtube.lyt-activated > .lty-playbtn {
+ opacity: 0;
+ pointer-events: none;
+}
+
+.lyt-visually-hidden {
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+ }
diff --git a/files/assets/js/comments_v.js b/files/assets/js/comments_v.js
index 839574c76..6d69a7482 100644
--- a/files/assets/js/comments_v.js
+++ b/files/assets/js/comments_v.js
@@ -234,6 +234,17 @@ document.onpaste = function(event) {
document.getElementById('filename-edit-reply-' + id).textContent = filename;
}
}
+ else if (focused.id.includes('post-edit-box-')) {
+ var id = focused.dataset.id;
+ f=document.getElementById('file-upload-edit-' + id);
+ files = event.clipboardData.files
+ filename = files[0].name.toLowerCase()
+ if (filename.endsWith(".jpg") || filename.endsWith(".jpeg") || filename.endsWith(".png") || filename.endsWith(".webp") || filename.endsWith(".gif"))
+ {
+ f.files = files;
+ document.getElementById('filename-show-edit-' + id).textContent = filename;
+ }
+ }
}
function markdown(first, second) {
diff --git a/files/assets/js/lite-youtube.js b/files/assets/js/lite-youtube.js
new file mode 100644
index 000000000..74af1bed8
--- /dev/null
+++ b/files/assets/js/lite-youtube.js
@@ -0,0 +1,122 @@
+/**
+ * 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 {
+ connectedCallback() {
+ this.videoId = this.getAttribute('videoid');
+
+ let playBtnEl = this.querySelector('.lty-playbtn');
+ // A label for the button takes priority over a [playlabel] attribute on the custom-element
+ this.playLabel = (playBtnEl && playBtnEl.textContent.trim()) || this.getAttribute('playlabel') || 'Play';
+
+ /**
+ * Lo, the youtube placeholder image! (aka the thumbnail, poster image, etc)
+ *
+ * See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md
+ *
+ * TODO: Do the sddefault->hqdefault fallback
+ * - When doing this, apply referrerpolicy (https://github.com/ampproject/amphtml/pull/3940)
+ * TODO: Consider using webp if supported, falling back to jpg
+ */
+ if (!this.style.backgroundImage) {
+ this.style.backgroundImage = `url("https://i.ytimg.com/vi/${this.videoId}/hqdefault.jpg")`;
+ }
+
+ // Set up play button, and its visually hidden label
+ if (!playBtnEl) {
+ playBtnEl = document.createElement('button');
+ playBtnEl.type = 'button';
+ playBtnEl.classList.add('lty-playbtn');
+ this.append(playBtnEl);
+ }
+ if (!playBtnEl.textContent) {
+ const playBtnLabelEl = document.createElement('span');
+ playBtnLabelEl.className = 'lyt-visually-hidden';
+ playBtnLabelEl.textContent = this.playLabel;
+ playBtnEl.append(playBtnLabelEl);
+ }
+
+ // On hover (or tap), warm up the TCP connections we're (likely) about to use.
+ this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {once: true});
+
+ // Once the user clicks, add the real iframe and drop our play button
+ // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time
+ // We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003
+ this.addEventListener('click', this.addIframe);
+ }
+
+ // // TODO: Support the the user changing the [videoid] attribute
+ // attributeChangedCallback() {
+ // }
+
+ /**
+ * Add a to the head
+ */
+ static addPrefetch(kind, url, as) {
+ const linkEl = document.createElement('link');
+ linkEl.rel = kind;
+ linkEl.href = url;
+ if (as) {
+ linkEl.as = as;
+ }
+ document.head.append(linkEl);
+ }
+
+ /**
+ * Begin pre-connecting to warm up the iframe load
+ * Since the embed's network 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 `` 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;
+
+ // The iframe document and most of its subresources come right off youtube.com
+ LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com');
+ // The botguard script is fetched off from google.com
+ LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com');
+
+ // 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;
+ }
+
+ addIframe() {
+ if (this.classList.contains('lyt-activated')) return;
+ this.classList.add('lyt-activated');
+
+ const params = new URLSearchParams(this.getAttribute('params') || []);
+ params.append('autoplay', '1');
+
+ const iframeEl = document.createElement('iframe');
+ iframeEl.width = 560;
+ iframeEl.height = 315;
+ // No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include
+ iframeEl.title = this.playLabel;
+ iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture';
+ iframeEl.allowFullscreen = true;
+ // AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL
+ // https://stackoverflow.com/q/64959723/89484
+ iframeEl.src = `https://www.youtube-nocookie.com/embed/${encodeURIComponent(this.videoId)}?${params.toString()}`;
+ this.append(iframeEl);
+
+ // Set focus for a11y
+ iframeEl.focus();
+ }
+}
+// Register custom element
+customElements.define('lite-youtube', LiteYTEmbed);
\ No newline at end of file
diff --git a/files/helpers/sanitize.py b/files/helpers/sanitize.py
index 17d1bc03b..9526ddcf4 100644
--- a/files/helpers/sanitize.py
+++ b/files/helpers/sanitize.py
@@ -129,17 +129,17 @@ def sanitize(sanitized, noimages=False):
if site not in tag["src"] and not tag["src"].startswith('/'): tag["rel"] = "nofollow noopener noreferrer"
tag["class"] = "in-comment-image"
+ tag["width"] = "150"
+ tag["height"] = "150"
tag["loading"] = "lazy"
- # tag["data-src"] = tag["src"]
- # tag["src"] = "/assets/images/loading.webp"
+ tag["data-src"] = tag["src"]
+ tag["src"] = "/assets/images/loading.webp"
link = soup.new_tag("a")
- # link["href"] = tag["data-src"]
- link["href"] = tag["src"]
+ link["href"] = tag["data-src"]
if site not in link["href"] and not link["href"].startswith('/'): link["rel"] = "nofollow noopener noreferrer"
link["target"] = "_blank"
- # link["onclick"] = f"expandDesktopImage('{tag['data-src']}');"
- link["onclick"] = f"expandDesktopImage('{tag['src']}');"
+ link["onclick"] = f"expandDesktopImage('{tag['data-src']}');"
link["data-bs-toggle"] = "modal"
link["data-bs-target"] = "#expandImageModal"
@@ -174,13 +174,13 @@ def sanitize(sanitized, noimages=False):
for i in re.finditer('(?', sanitized)
+ sanitized = re.sub(f'(?', sanitized)
if emoji in session["favorite_emojis"]: session["favorite_emojis"][emoji] += 1
else: session["favorite_emojis"][emoji] = 1
elif path.isfile(f'./files/assets/images/emojis/{emoji}.webp'):
- sanitized = re.sub(f'(?', sanitized)
+ sanitized = re.sub(f'(?', sanitized)
if emoji in session["favorite_emojis"]: session["favorite_emojis"][emoji] += 1
else: session["favorite_emojis"][emoji] = 1
@@ -211,28 +211,36 @@ def sanitize(sanitized, noimages=False):
sanitized = sanitized.replace("https://www.", "https://").replace("https://youtu.be/", "https://youtube.com/watch?v=").replace("https://music.youtube.com/watch?v=", "https://youtube.com/watch?v=").replace("https://open.spotify.com/", "https://open.spotify.com/embed/").replace("https://streamable.com/", "https://streamable.com/e/").replace("https://youtube.com/shorts/", "https://youtube.com/watch?v=").replace("https://mobile.twitter", "https://twitter").replace("https://m.facebook", "https://facebook").replace("https://m.wikipedia", "https://wikipedia").replace("https://m.youtube", "https://youtube")
+ if "https://youtube.com/watch?v=" in sanitized: sanitized = sanitized.replace("?t=", "&t=")
- for i in re.finditer('" target="_blank">(https://youtube\.com/watch\?v\=.*?)', sanitized):
+ for i in re.finditer('" target="_blank">(https://youtube\.com/watch\?v\=(.*?))', sanitized):
url = i.group(1)
+ yt_id = i.group(2).split('&')[0]
replacing = f'{url}'
- url = url.replace("watch?v=", "embed/").replace("&t", "?start").replace("?t", "?start")
- url = re.sub('(\?start=([0-9]*?))s', r'\1', url)
- htmlsource = f''
+
+ params = parse_qs(urlparse(url).query)
+ t = params.get('t', params.get('start', [0]))[0]
+ if isinstance(t, str): t = t.replace('s','')
+
+ htmlsource = f'
(https:.*?\.mp4)
', sanitized): - sanitized = sanitized.replace(i.group(0), f'') + sanitized = sanitized.replace(i.group(0), f'
') for i in re.finditer('{url}' - htmlsource = f'' + htmlsource = f'' sanitized = sanitized.replace(replacing, htmlsource) for rd in ["https://reddit.com/", "https://new.reddit.com/", "https://www.reddit.com/", "https://redd.it/"]: @@ -258,7 +266,7 @@ def filter_title(title): if emoji.startswith("!"): emoji = emoji[1:] if path.isfile(f'./files/assets/images/emojis/{emoji}.webp'): - title = re.sub(f'(?', title) + title = re.sub(f'(?', title) elif path.isfile(f'./files/assets/images/emojis/{emoji}.webp'): title = re.sub(f'(?', title) diff --git a/files/templates/authforms.html b/files/templates/authforms.html index 5d0ba5617..ab3848b48 100644 --- a/files/templates/authforms.html +++ b/files/templates/authforms.html @@ -15,11 +15,11 @@ {% if v %} - + {% if v.agendaposter %}{% elif v.css %}{% endif %} {% else %} - + {% endif %} diff --git a/files/templates/comments.html b/files/templates/comments.html index d17652fb7..08ca7997a 100644 --- a/files/templates/comments.html +++ b/files/templates/comments.html @@ -749,7 +749,7 @@ {% if v %} - + {% endif %} diff --git a/files/templates/default.html b/files/templates/default.html index 4a723dc1d..950e31e80 100644 --- a/files/templates/default.html +++ b/files/templates/default.html @@ -3,18 +3,16 @@
{% if v %} - + {% if v.agendaposter %}{% elif v.css %}{% endif %} {% else %} - + {% endif %} - - @@ -355,8 +353,11 @@ {% endif %} + + + -