summaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
Diffstat (limited to 'static')
-rwxr-xr-xstatic/css/dump.css57
-rwxr-xr-xstatic/css/dumpnewuser.css3
-rwxr-xr-xstatic/css/front.css62
-rw-r--r--static/css/frontpage_legacy.css5
-rw-r--r--static/css/frontpage_profile.css3
-rw-r--r--static/css/meme_pages.css6
-rwxr-xr-xstatic/css/mgmt.css12
-rw-r--r--static/css/newlog.css1
-rw-r--r--static/css/req_reset.css1
-rw-r--r--static/css/rooms_vortex.css32
-rw-r--r--static/css/simplerlog.css2
-rw-r--r--static/js/pages/chat_init.js13
-rwxr-xr-xstatic/js/pichat.butt.js104
-rwxr-xr-xstatic/js/pichat.js106
-rwxr-xr-xstatic/js/src/text.js104
-rwxr-xr-xstatic/js/src/youtube.js2
16 files changed, 445 insertions, 68 deletions
diff --git a/static/css/dump.css b/static/css/dump.css
index f8cfbe2..c7d2217 100755
--- a/static/css/dump.css
+++ b/static/css/dump.css
@@ -106,7 +106,7 @@ a.img_rolldis:hover {
}
#dumplist {
- top: 0px:padding:0px;
+ padding: 0px;
font-size: 88%;
text-transform: uppercase;
text-decoration: none;
@@ -1031,7 +1031,6 @@ a:active {
#binfo {
font-family: Arial, Helvetica, sans-serif;
font-size: 11px;
- bottom
}
#preload {
@@ -1044,6 +1043,51 @@ a:active {
display: none !important;
}
+.is-hidden {
+ display: none;
+}
+
+.is-relative {
+ position: relative;
+}
+
+.loading-bar {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+}
+.loading-bar-label {
+ position: absolute;
+ font-family: arial;
+ font-size: 10px;
+ color: #ccc;
+ background: #000;
+ left: -50px;
+ top: -18px;
+ padding: 1px;
+}
+.loading-bar-track {
+ position: absolute;
+ left: -50px;
+ top: -5px;
+ font-size: 1px;
+ width: 100px;
+ height: 10px;
+ background: #333;
+}
+.loading-bar-fill {
+ position: absolute;
+ font-size: 1px;
+ width: 0px;
+ height: 10px;
+ background: #eee;
+}
+
+.embed-frame {
+ border: 7px inset #ccc;
+ margin: 0;
+}
+
/* profile shit */
@@ -2061,6 +2105,10 @@ a.msgbtn:active {
#fblike {
margin-top: 6px;
margin-left: 6px;
+ border: none;
+ overflow: hidden;
+ width: 100px;
+ height: 21px;
}
#logo {
float: left;
@@ -2512,6 +2560,11 @@ a#disregister:hover {
-webkit-background-clip: text;
}
+.rainbow-link {
+ border-bottom: 1px solid rgba(0, 255, 255, 0.3);
+ font-weight: bold;
+}
+
.nick_aids_enoch a:link, .nick_aids_enoch .content, .nick_aids_enoch .msg-hover { color: transparent; text-shadow: 0 0 5px #857E96; }
.nick_aids_enoch { opacity: 0.3; }
.nick_aids_enoch img { -webkit-filter: blur(4px); }
diff --git a/static/css/dumpnewuser.css b/static/css/dumpnewuser.css
index 1c92e62..9a6ff6c 100755
--- a/static/css/dumpnewuser.css
+++ b/static/css/dumpnewuser.css
@@ -498,7 +498,6 @@ left:0;
#binfo {
font-family: Arial, Helvetica, sans-serif;
font-size: 11px;
- bottom
}
#preload {
@@ -507,4 +506,4 @@ left:0;
top: 0px;
}
-.invisible { display: none !important; } \ No newline at end of file
+.invisible { display: none !important; }
diff --git a/static/css/front.css b/static/css/front.css
index ef0ee70..39e7700 100755
--- a/static/css/front.css
+++ b/static/css/front.css
@@ -119,6 +119,68 @@ filter: progid:DXImageTransform.Microsoft.dropShadow(color=#ccc, offX=3, offY=4,
#signin_menu p a {
color:#27B!important;
}
+
+.is-relative {
+ position: relative;
+}
+.is-hidden {
+ display: none;
+}
+
+.loading-bar {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+}
+.loading-bar-label {
+ position: absolute;
+ font-family: arial;
+ font-size: 10px;
+ color: #ccc;
+ background: #000;
+ left: -50px;
+ top: -18px;
+ padding: 1px;
+}
+.loading-bar-track {
+ position: absolute;
+ left: -50px;
+ top: -5px;
+ font-size: 1px;
+ width: 100px;
+ height: 10px;
+ background: #333;
+}
+.loading-bar-fill {
+ position: absolute;
+ font-size: 1px;
+ width: 0px;
+ height: 10px;
+ background: #eee;
+}
+
+#fblike {
+ border: none;
+ overflow: hidden;
+ width: 100px;
+ height: 21px;
+}
+.fullscreen-thumb {
+ overflow: hidden;
+ width: 240px;
+ height: 200px;
+ border: 0px;
+}
+.fullscreen-bg {
+ overflow: hidden;
+ width: 100%;
+ height: 100%;
+ border: 0px;
+ z-index: -1;
+ position: fixed;
+ top: 0px;
+ left: 0px;
+}
#signin-submit {
display:inline-block;
width:90px;
diff --git a/static/css/frontpage_legacy.css b/static/css/frontpage_legacy.css
index c667daa..21c0f94 100644
--- a/static/css/frontpage_legacy.css
+++ b/static/css/frontpage_legacy.css
@@ -17,7 +17,7 @@ img{border:0px;}
#posts a{text-decoration:none; font-family:Arial, Helvetica, sans-serif;letter-spacing:0px;color:#f0e;}
#posts a:hover{color:#333;text-decoration:none;}
#sideinfo{ color:#333; padding:12px;margin:7px;font-size:47px;float:left;min-height:210px;width:240px;border:1px inset #f1f1f1; border-radius:3px;-webkit-border-radius:3px;-moz-border-radius: 3px;background-color:white;font-family: 'HelveticaNeue-Light','Helvetica Neue Light','Helvetica Neue',Arial,Helvetica,sans-serif;font-weight:bold; line-height:55px;}
-.alttxt{font-size:18px;line-height:20px;}
+.alttxt{font-size:18px;line-height:20px;text-transform:uppercase;text-align:left;}
.logged-dump{border:1px inset #f1f1f1; border-radius:3px;-webkit-border-radius:3px;-moz-border-radius: 3px;font-family:Arial, Helvetica, sans-serif;float:left;max-width:250px;min-width:230px;padding:7px;margin:7px;background-color:white}
.logged-dump img{max-width:240px;max-height:240px;padding-top:3px; }
.buttons img{display:none;z-index:4;float:left;padding-top:5px;cursor:pointer;}
@@ -42,7 +42,7 @@ z-index:4;
#pgbuttons{position:fixed;bottom:6px;right:0px;/*border-top:1px ridge #333;*/min-width:70px;padding:5px;}
#pgbuttons a{text-decoration:none;}
#footer{word-spacing:20px;font-size:11px;font-family: Monaco, "Courier New", Courier, monospace;text-align:center;margin:7px;color:#333;width:900px;display:block;height:18px;overflow:hidden;}
-#footer a{text-decoration:none;color:#333;}
+#footer a{text-decoration:none;color:#333;word-spacing:normal;}
#footer a:hover{color:#000;}
#pgbuttons input{color:#eee;letter-spacing:1px;
text-decoration:none;
@@ -64,6 +64,7 @@ z-index:4;
.hallscore{margin-right:-20px;color:#fff;text-align:center;margin-top:-20px;float:right;font-size:40px;z-index:1;padding:6px;background-image:url('/static/img/hallheart.png');background-position:top left;font-style: oblique;font-weight:100;height:50px;width:50px;text-shadow:-1px 1px 0px #000;line-height:37px;text-indent:4px;}
body.logged-in .hallscore{cursor:pointer;}
.hallnick{font-size:16px;}
+#hallnick iframe{overflow:hidden;width:240px;height:200px;border:0px;}
a#disregister {
background: #FCF0AD;
color: #fe1409;
diff --git a/static/css/frontpage_profile.css b/static/css/frontpage_profile.css
index 247160e..2267600 100644
--- a/static/css/frontpage_profile.css
+++ b/static/css/frontpage_profile.css
@@ -25,7 +25,7 @@ img{border:0px;}
#pgbuttons{position:fixed;bottom:6px;right:0px;/*border-top:1px ridge #333;*/min-width:70px;padding:5px;}
#pgbuttons a{text-decoration:none;}
#footer{word-spacing:20px;font-size:11px;font-family: Monaco, "Courier New", Courier, monospace;text-align:center;margin:7px;color:#333;width:900px;display:block;height:18px;overflow:hidden;}
-#footer a{text-decoration:none;color:#333;}
+#footer a{text-decoration:none;color:#333;word-spacing:normal;}
#footer a:hover{color:#000;}
#pgbuttons input{color:#eee;letter-spacing:1px;
text-decoration:none;
@@ -47,6 +47,7 @@ img{border:0px;}
#hallscore{right:-11px;color:#fff;text-align:center;top:-13px;position:absolute;font-size:40px;z-index:1;padding:6px;background-image:url('/static/img/hallheart.png');background-position:top left;font-family: 'HelveticaNeue-Light','Helvetica Neue Light','Helvetica Neue',Arial,Helvetica,sans-serif;font-weight:100;height:50px;width:50px; text-shadow:-1px 1px 0px #000;line-height:37px;text-indent:5px;
}
#hallnick{font-size:16px;}
+#hallnick iframe{overflow:hidden;width:240px;height:200px;border:0px;}
a#disregister {
background: #FCF0AD;
color: #fe1409;
diff --git a/static/css/meme_pages.css b/static/css/meme_pages.css
index c53d420..22d5a2a 100644
--- a/static/css/meme_pages.css
+++ b/static/css/meme_pages.css
@@ -10,6 +10,12 @@ body {
overflow: auto;
}
+.preload-img {
+ visibility: hidden;
+ width: 0px;
+ height: 0px;
+}
+
#rapper {
background-color: #f2f9fe;
width: 800px;
diff --git a/static/css/mgmt.css b/static/css/mgmt.css
index 2ff6d8e..4360ad3 100755
--- a/static/css/mgmt.css
+++ b/static/css/mgmt.css
@@ -259,10 +259,10 @@ position:absolute;
top:21px;right:0px;
}
#dumplist{
-top:0px:width:100%;padding:0px;background-color:#fff;
-color:#000;font-size:60%;text-transform:uppercase;text-decoration: none;
-border-bottom:1px solid #999;text-align:left;text-indent:350px;
-padding-right:44px;line-height:1.1;background-color:#eff5fb;overflow:hidden;position:fixed;height:10px;width:100%;
+top:0px;width:100%;padding:0px;background-color:#fff;
+ color:#000;font-size:60%;text-transform:uppercase;text-decoration: none;
+ border-bottom:1px solid #999;text-align:left;text-indent:350px;
+ padding-right:44px;line-height:1.1;background-color:#eff5fb;overflow:hidden;position:fixed;height:10px;width:100%;
/*margin-left:40%;
border-bottom-left-radius:5px;
-webkit-border-bottom-left-radius:5px;
@@ -291,6 +291,7 @@ display:none;
.no-cursor { cursor: none; }
.invisible { display: none !important; }
+.preload-img { visibility: hidden; width: 0px; height: 0px; }
#cursor-big { position: absolute; z-index: 1000; }
.thumb {
@@ -1186,7 +1187,6 @@ left:0;
#binfo {
font-family: Arial, Helvetica, sans-serif;
font-size: 11px;
- bottom
}
#preload {
@@ -1682,5 +1682,3 @@ top:50px;
cursor: pointer;
display: none;
}
-
-
diff --git a/static/css/newlog.css b/static/css/newlog.css
index f0e0277..8cc8032 100644
--- a/static/css/newlog.css
+++ b/static/css/newlog.css
@@ -206,6 +206,7 @@ img {
#footer a {
text-decoration: none;
color: #333;
+ word-spacing: normal;
}
#footer a:hover {
diff --git a/static/css/req_reset.css b/static/css/req_reset.css
index 0807ec7..65d1c3c 100644
--- a/static/css/req_reset.css
+++ b/static/css/req_reset.css
@@ -1,3 +1,4 @@
#main { padding: 10px 2em 0px 2em; }
label { float: left; width: 150px; }
.error { border: 1px solid red; }
+#feedback { color: red; margin-bottom: 2em; }
diff --git a/static/css/rooms_vortex.css b/static/css/rooms_vortex.css
index 7403454..f13c797 100644
--- a/static/css/rooms_vortex.css
+++ b/static/css/rooms_vortex.css
@@ -62,3 +62,35 @@ text-decoration:none;
font-family: Arial, Helvetica, sans-serif;
color:blue;
}
+
+.loading-bar {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+}
+.loading-bar-label {
+ position: absolute;
+ font-family: arial;
+ font-size: 10px;
+ color: #ccc;
+ background: #000;
+ left: -50px;
+ top: -18px;
+ padding: 1px;
+}
+.loading-bar-track {
+ position: absolute;
+ left: -50px;
+ top: -5px;
+ font-size: 1px;
+ width: 100px;
+ height: 10px;
+ background: #333;
+}
+.loading-bar-fill {
+ position: absolute;
+ font-size: 1px;
+ width: 0px;
+ height: 10px;
+ background: #eee;
+}
diff --git a/static/css/simplerlog.css b/static/css/simplerlog.css
index 784253d..a158fd8 100644
--- a/static/css/simplerlog.css
+++ b/static/css/simplerlog.css
@@ -202,6 +202,7 @@ img {
#footer a {
text-decoration: none;
color: #333;
+ word-spacing: normal;
}
#footer a:hover {
@@ -301,6 +302,7 @@ a#disregister:hover {
text-align: center;
left: 0;
z-index: 1000;
+ background-color: white;
}
#inpage-search-results #search-results-images {
diff --git a/static/js/pages/chat_init.js b/static/js/pages/chat_init.js
index 58dba07..7149291 100644
--- a/static/js/pages/chat_init.js
+++ b/static/js/pages/chat_init.js
@@ -23,9 +23,17 @@
if (typeof window.Recips === 'undefined') { window.Recips = []; }
var hasMessageList = document.getElementById('messageList') !== null;
+ var hasChatInput = document.getElementById('msgInput') !== null;
+
+ if (hasMessageList && hasChatInput && typeof window.initChat === 'function') { window.initChat(); }
+ if (hasMessageList && hasChatInput && typeof window.initChatMsgs === 'function') { window.initChatMsgs(); }
+
+ // Some room pages render dumps in `.logged-dump` containers without the full chat UI.
+ // In that case, run the log initializer so URLs become hotlinked images.
+ if (!hasChatInput && typeof window.initLog === 'function' && jQuery('.logged-dump .content').length) {
+ window.initLog(window.Recips);
+ }
- if (hasMessageList && typeof window.initChat === 'function') { window.initChat(); }
- if (hasMessageList && typeof window.initChatMsgs === 'function') { window.initChatMsgs(); }
if (typeof window.Away !== 'undefined' && typeof window.Away.startTitleUpdater === 'function') { window.Away.startTitleUpdater(); }
if (window.Nick && typeof window.setupUpload === 'function' && typeof window.Room !== 'undefined') {
@@ -33,4 +41,3 @@
}
});
})(jQuery);
-
diff --git a/static/js/pichat.butt.js b/static/js/pichat.butt.js
index 220e873..f0baae5 100755
--- a/static/js/pichat.butt.js
+++ b/static/js/pichat.butt.js
@@ -65,18 +65,68 @@ function normalizeUrl(url) {
}
URLRegex = /((\b(http\:\/\/|https\:\/\/|ftp\:\/\/)|(www\.))+(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi;
-PicRegex = /\.(jpg|jpeg|png|gif|bmp)$/i;
+PicRegex = /\.(jpg|jpeg|png|gif|bmp|svg|webp|fid)$/i;
+VideoRegex = /\.(mp4|webm|mov|m4v|gifv)$/i;
+
+function splitTrailingPunctuation(url) {
+ if (!url) return { "url": url, "suffix": "" };
+ var match = url.match(/[)\]\}\.,!\?;:'"]+$/);
+ if (!match) return { "url": url, "suffix": "" };
+ return { "url": url.slice(0, -match[0].length), "suffix": match[0] };
+}
+
+function imgurIdFromUri(uri) {
+ var host = parseDomain(uri.host || "");
+ if (!host || !host.match(/(^|\\.)imgur\\.com$/i)) return null;
+ if (!uri.file) return null;
+
+ var fileLower = uri.file.toLowerCase();
+ if (PicRegex.test(fileLower) || VideoRegex.test(fileLower)) return null;
+
+ if (!uri.file.match(/^[A-Za-z0-9]+$/)) return null;
+ return uri.file;
+}
+
+function imgurCandidateUrls(id) {
+ var base = "https://i.imgur.com/" + id;
+ return [base + ".jpg", base + ".png", base + ".gif", base + ".jpeg"];
+}
+
+function imgurHotlinkFallback(img) {
+ try {
+ var candidates = (img.getAttribute("data-imgur-candidates") || "").split("|");
+ if (!candidates.length || !candidates[0]) return;
+
+ var idx = parseInt(img.getAttribute("data-imgur-idx") || "0", 10);
+ var next = idx + 1;
+ if (next >= candidates.length) {
+ var link = img.parentNode;
+ if (link && link.tagName == "A") {
+ var href = link.getAttribute("href") || "";
+ while (link.firstChild) link.removeChild(link.firstChild);
+ link.appendChild(document.createTextNode(href));
+ }
+ return;
+ }
+
+ img.setAttribute("data-imgur-idx", "" + next);
+ img.src = candidates[next];
+ } catch (e) {}
+}
function getImagesAsArray(text) {
var imgs = []
var urls = text.match(URLRegex)
if (urls === null) return imgs
for (var i = 0; i<urls.length; i++){
- var url = urls[i]
- var normalized = normalizeUrl(url);
- var urlWithoutParams = normalized.replace(/[?#].*$/i, "");
- if (PicRegex.test(urlWithoutParams))
- imgs.push(normalized)
+ var split = splitTrailingPunctuation(urls[i]);
+ var normalized = normalizeUrl(split.url);
+ var uri = parseUri(normalized);
+ var imgurId = imgurIdFromUri(uri);
+ var candidate = imgurId ? imgurCandidateUrls(imgurId)[0] : normalized;
+
+ var urlWithoutParams = candidate.replace(/[?#].*$/i, "");
+ if (PicRegex.test(urlWithoutParams)) imgs.push(candidate)
}
return imgs
}
@@ -89,22 +139,35 @@ function linkify(text) {
// durty hack to use a global to check this... but otherwise i'd have to rewrite the String.replace function? :/
var LastMsgContainsImage = false
function linkReplace(url) {
- //var urlWithoutParams = url.replace(/\?.*$/i, "");
-
- linkUrl = normalizeUrl(url);
-
- var uri = parseUri(url)
+ var split = splitTrailingPunctuation(url);
+ linkUrl = normalizeUrl(split.url);
+ var uri = parseUri(linkUrl);
+
+ var imgurId = imgurIdFromUri(uri);
+ if (imgurId) {
+ LastMsgContainsImage = true;
+ var candidates = imgurCandidateUrls(imgurId);
+ var first = candidates[0];
+ return "<a target=\"_blank\" href=\"" + linkUrl + "\">"
+ + "<img src=\"" + first + "\" class=\"imgur-hotlink\" data-imgur-idx=\"0\" data-imgur-candidates=\"" + candidates.join('|') + "\" onerror=\"imgurHotlinkFallback(this)\">"
+ + "</a>" + split.suffix;
+ }
+
switch(getUriType(uri)) {
case 'image':
LastMsgContainsImage = true;
- return "<a target='_blank' href='" + linkUrl + "'><img src='" + linkUrl + "'></a>"; break;
- case 'youtube':
+ return "<a target='_blank' href='" + linkUrl + "'><img src='" + linkUrl + "'></a>" + split.suffix;
+ case 'video':
+ LastMsgContainsImage = true;
+ var videoUrl = linkUrl.replace(/\.gifv([?#].*)?$/i, '.mp4$1');
+ return "<a target=\"_blank\" href=\"" + linkUrl + "\"><video src=\"" + videoUrl + "\" autoplay loop muted controls playsinline></video></a>" + split.suffix;
+ case 'youtube':
Youtube.startAnimation();
- return "<a target='_blank' class='youtube' href='" + linkUrl + "'>" +
- "<img class='youtube-thumb' width='130' height='97' src='"+Youtube.nextThumbUrl(uri.queryKey.v)+"'>" +
- "<img class='youtube-controls' src='/static/img/youtube.controls.png'></a>"; break;
+ return "<a target=\"_blank\" class=\"youtube\" href=\"" + linkUrl + "\">" +
+ "<img class=\"youtube-thumb\" width=\"130\" height=\"97\" src=\"" + Youtube.nextThumbUrl(uri.queryKey.v) + "\">" +
+ "<img class=\"youtube-controls\" src=\"/static/img/youtube.controls.png\"></a>" + split.suffix;
default:
- return "<a target='_blank' href='" + linkUrl + "'>" + url + "</a>";
+ return "<a target=\"_blank\" href=\"" + linkUrl + "\">" + split.url + "</a>" + split.suffix;
}
}
@@ -130,7 +193,7 @@ Youtube = {
var img = $(this);
// yt thumb url example: https://i.ytimg.com/vi/0123456789A/1.jpg
var src = img.attr("src") || ""
- var match = src.match(/\\/vi\\/([^/]{11})\\/(\\d)\\.jpg/i)
+ var match = src.match(/\/vi\/([^/]{11})\/(\d)\.jpg/i)
if (!match) return
var v = match[1]
var num = match[2]
@@ -149,8 +212,11 @@ Youtube = {
function getUriType(uri){
if (PicRegex.test(uri.file.toLowerCase()))
return "image";
+
+ if (VideoRegex.test(uri.file.toLowerCase()))
+ return "video";
- if (parseDomain(uri.host) == "youtube.com" && 'v' in uri.queryKey || uri.anchor.indexOf('v') != -1)
+ if (parseDomain(uri.host) == "youtube.com" && ('v' in uri.queryKey || uri.anchor.indexOf('v') != -1))
return "youtube";
return "link";
diff --git a/static/js/pichat.js b/static/js/pichat.js
index 2283baf..ad50fae 100755
--- a/static/js/pichat.js
+++ b/static/js/pichat.js
@@ -2114,21 +2114,74 @@ function escapeHtml(txt) {
URLRegex = /((\b(http\:\/\/|https\:\/\/|ftp\:\/\/)|(www\.))+(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi;
-PicRegex = /\.(jpg|jpeg|png|gif|bmp|svg|fid)$/i;
+PicRegex = /\.(jpg|jpeg|png|gif|bmp|svg|webp|fid)$/i;
+VideoRegex = /\.(mp4|webm|mov|m4v|gifv)$/i;
RecipRegex = /(^|\s)@\w+/g;
TopicRegex = /(^|\s)#\w+/g;
+function splitTrailingPunctuation(url) {
+ if (!url) return { "url": url, "suffix": "" };
+ // Common punctuation that follows pasted links in chat.
+ var match = url.match(/[)\]\}\.,!\?;:'"]+$/);
+ if (!match) return { "url": url, "suffix": "" };
+ return { "url": url.slice(0, -match[0].length), "suffix": match[0] };
+}
+
+function imgurIdFromUri(uri) {
+ var host = (uri.host || "").toLowerCase();
+ if (!host || !host.match(/(^|\.)imgur\.com$/i)) return null;
+ if (!uri.file) return null;
+
+ var fileLower = uri.file.toLowerCase();
+ if (PicRegex.test(fileLower) || VideoRegex.test(fileLower)) return null;
+
+ if (!uri.file.match(/^[A-Za-z0-9]+$/)) return null;
+ return uri.file;
+}
+
+function imgurCandidateUrls(id) {
+ var base = "https://i.imgur.com/" + id;
+ return [base + ".jpg", base + ".png", base + ".gif", base + ".jpeg"];
+}
+
+function imgurHotlinkFallback(img) {
+ try {
+ var candidates = (img.getAttribute("data-imgur-candidates") || "").split("|");
+ if (!candidates.length || !candidates[0]) return;
+
+ var idx = parseInt(img.getAttribute("data-imgur-idx") || "0", 10);
+ var next = idx + 1;
+ if (next >= candidates.length) {
+ var link = img.parentNode;
+ if (link && link.tagName == "A") {
+ var href = link.getAttribute("href") || "";
+ while (link.firstChild) link.removeChild(link.firstChild);
+ link.appendChild(document.createTextNode(href));
+ }
+ return;
+ }
+
+ img.setAttribute("data-imgur-idx", "" + next);
+ img.src = candidates[next];
+ } catch (e) {}
+}
+
function getImagesAsArray(text) {
var imgs = []
var urls = text.match(URLRegex)
if (urls === null) return imgs
for (var i = 0; i<urls.length; i++){
- var url = urls[i]
- var normalized = normalizeUrl(url);
- var urlWithoutParams = normalized.replace(/[?#].*$/i, "");
- if (PicRegex.test(urlWithoutParams))
- imgs.push(normalized)
+ var split = splitTrailingPunctuation(urls[i]);
+ var normalized = normalizeUrl(split.url);
+ var uri = parseUri(normalized);
+
+ // Try to turn common "page" links into direct image URLs (best-effort).
+ var imgurId = imgurIdFromUri(uri);
+ var candidate = imgurId ? imgurCandidateUrls(imgurId)[0] : normalized;
+
+ var urlWithoutParams = candidate.replace(/[?#].*$/i, "");
+ if (PicRegex.test(urlWithoutParams)) imgs.push(candidate)
}
return imgs
}
@@ -2204,25 +2257,43 @@ function imgClickHandler() {
// durty hack to use a global to check this... but otherwise i'd have to rewrite the String.replace function? :/
var LastMsgContainsImage = false
function linkReplace(url) {
- linkUrl = normalizeUrl(url);
+ var split = splitTrailingPunctuation(url);
+ var hrefUrl = normalizeUrl(split.url);
+ var uri = parseUri(hrefUrl);
+
+ // Best-effort support for Imgur page links (e.g. https://imgur.com/<id>).
+ var imgurId = imgurIdFromUri(uri);
+ if (imgurId) {
+ LastMsgContainsImage = true;
+ var candidates = imgurCandidateUrls(imgurId);
+ var first = candidates[0];
+ return "<a target=\"_blank\" href=\"" + hrefUrl + "\" class=\"img-wrapper\" onclick=\"return imgClickHandler()\">"
+ + "<img src=\"" + first + "\" class=\"unbound imgur-hotlink\" data-imgur-idx=\"0\" data-imgur-candidates=\"" + candidates.join("|") + "\" onerror=\"imgurHotlinkFallback(this)\">"
+ + "</a>" + split.suffix;
+ }
+
+ linkUrl = hrefUrl;
- var uri = parseUri(url)
var type = getUriType(uri)
if (type == 'image') {
LastMsgContainsImage = true;
- return "<a target='_blank' href='" + linkUrl + "' class='img-wrapper' onclick='return imgClickHandler()'><img src='" + linkUrl + "' class='unbound'></a>";
+ return "<a target=\"_blank\" href=\"" + linkUrl + "\" class=\"img-wrapper\" onclick=\"return imgClickHandler()\"><img src=\"" + linkUrl + "\" class=\"unbound\"></a>" + split.suffix;
+ } else if (type == 'video') {
+ LastMsgContainsImage = true;
+ var videoUrl = linkUrl.replace(/\.gifv([?#].*)?$/i, ".mp4$1");
+ return "<a target=\"_blank\" href=\"" + linkUrl + "\" class=\"video-wrapper\" onclick=\"return imgClickHandler()\"><video src=\"" + videoUrl + "\" autoplay loop muted controls playsinline></video></a>" + split.suffix;
} else if (type == 'youtube') {
Youtube.startAnimation();
- return "<a target='_blank' class='youtube' href='" + linkUrl + "'>" +
- "<img class='youtube-thumb' width='130' height='97' src='"+Youtube.nextThumbUrl(uri.queryKey.v)+"'>" +
- "<img class='youtube-controls' src='/static/img/youtube.controls.png'></a>"
+ return "<a target=\"_blank\" class=\"youtube\" href=\"" + linkUrl + "\">" +
+ "<img class=\"youtube-thumb\" width=\"130\" height=\"97\" src=\"" + Youtube.nextThumbUrl(uri.queryKey.v) + "\">" +
+ "<img class=\"youtube-controls\" src=\"/static/img/youtube.controls.png\"></a>" + split.suffix;
} else if (type == 'midi') {
- return '<embed src="'+linkUrl+'" autostart="false" loop="false" volume="80" width="150" height="20" style="vertical-align:bottom"> <a href="'+linkUrl+'">'+uri.file+'</a>'
+ return '<embed src="' + linkUrl + '" autostart="false" loop="false" volume="80" width="150" height="20" style="vertical-align:bottom"> <a href="' + linkUrl + '">' + uri.file + '</a>' + split.suffix;
} else if (type == 'wav') {
- return '<audio src="'+linkUrl+'" controls volume="80" width="150" height="20" style="vertical-align:bottom"></audio> <a href="'+linkUrl+'">'+uri.file+'</a>'
+ return '<audio src="' + linkUrl + '" controls volume="80" width="150" height="20" style="vertical-align:bottom"></audio> <a href="' + linkUrl + '">' + uri.file + '</a>' + split.suffix;
} else
- return "<a target='_blank' href='" + linkUrl + "'>" + url + "</a>";
+ return "<a target=\"_blank\" href=\"" + linkUrl + "\">" + split.url + "</a>" + split.suffix;
}
@@ -2230,6 +2301,9 @@ function linkReplace(url) {
function getUriType(uri){
if (PicRegex.test(uri.file.toLowerCase()))
return "image";
+
+ if (VideoRegex.test(uri.file.toLowerCase()))
+ return "video";
var domain = parseDomain(uri.host)
@@ -2443,7 +2517,7 @@ Youtube = {
var img = $(this);
// yt thumb url example: https://i.ytimg.com/vi/0123456789A/1.jpg
var src = img.attr("src") || ""
- var match = src.match(/\\/vi\\/([^/]{11})\\/(\\d)\\.jpg/i)
+ var match = src.match(/\/vi\/([^/]{11})\/(\d)\.jpg/i)
if (!match) return
var v = match[1]
var num = match[2]
diff --git a/static/js/src/text.js b/static/js/src/text.js
index 6fe5c3c..d99efbb 100755
--- a/static/js/src/text.js
+++ b/static/js/src/text.js
@@ -14,21 +14,74 @@ function escapeHtml(txt) {
URLRegex = /((\b(http\:\/\/|https\:\/\/|ftp\:\/\/)|(www\.))+(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi;
-PicRegex = /\.(jpg|jpeg|png|gif|bmp|svg|fid)$/i;
+PicRegex = /\.(jpg|jpeg|png|gif|bmp|svg|webp|fid)$/i;
+VideoRegex = /\.(mp4|webm|mov|m4v|gifv)$/i;
RecipRegex = /(^|\s)@\w+/g;
TopicRegex = /(^|\s)#\w+/g;
+function splitTrailingPunctuation(url) {
+ if (!url) return { "url": url, "suffix": "" };
+ // Common punctuation that follows pasted links in chat.
+ var match = url.match(/[)\]\}\.,!\?;:'"]+$/);
+ if (!match) return { "url": url, "suffix": "" };
+ return { "url": url.slice(0, -match[0].length), "suffix": match[0] };
+}
+
+function imgurIdFromUri(uri) {
+ var host = (uri.host || "").toLowerCase();
+ if (!host || !host.match(/(^|\.)imgur\.com$/i)) return null;
+ if (!uri.file) return null;
+
+ var fileLower = uri.file.toLowerCase();
+ if (PicRegex.test(fileLower) || VideoRegex.test(fileLower)) return null;
+
+ if (!uri.file.match(/^[A-Za-z0-9]+$/)) return null;
+ return uri.file;
+}
+
+function imgurCandidateUrls(id) {
+ var base = "https://i.imgur.com/" + id;
+ return [base + ".jpg", base + ".png", base + ".gif", base + ".jpeg"];
+}
+
+function imgurHotlinkFallback(img) {
+ try {
+ var candidates = (img.getAttribute("data-imgur-candidates") || "").split("|");
+ if (!candidates.length || !candidates[0]) return;
+
+ var idx = parseInt(img.getAttribute("data-imgur-idx") || "0", 10);
+ var next = idx + 1;
+ if (next >= candidates.length) {
+ var link = img.parentNode;
+ if (link && link.tagName == "A") {
+ var href = link.getAttribute("href") || "";
+ while (link.firstChild) link.removeChild(link.firstChild);
+ link.appendChild(document.createTextNode(href));
+ }
+ return;
+ }
+
+ img.setAttribute("data-imgur-idx", "" + next);
+ img.src = candidates[next];
+ } catch (e) {}
+}
+
function getImagesAsArray(text) {
var imgs = []
var urls = text.match(URLRegex)
if (urls === null) return imgs
for (var i = 0; i<urls.length; i++){
- var url = urls[i]
- var normalized = normalizeUrl(url);
- var urlWithoutParams = normalized.replace(/[?#].*$/i, "");
- if (PicRegex.test(urlWithoutParams))
- imgs.push(normalized)
+ var split = splitTrailingPunctuation(urls[i]);
+ var normalized = normalizeUrl(split.url);
+ var uri = parseUri(normalized);
+
+ // Try to turn common "page" links into direct image URLs (best-effort).
+ var imgurId = imgurIdFromUri(uri);
+ var candidate = imgurId ? imgurCandidateUrls(imgurId)[0] : normalized;
+
+ var urlWithoutParams = candidate.replace(/[?#].*$/i, "");
+ if (PicRegex.test(urlWithoutParams)) imgs.push(candidate)
}
return imgs
}
@@ -104,25 +157,43 @@ function imgClickHandler() {
// durty hack to use a global to check this... but otherwise i'd have to rewrite the String.replace function? :/
var LastMsgContainsImage = false
function linkReplace(url) {
- linkUrl = normalizeUrl(url);
+ var split = splitTrailingPunctuation(url);
+ var hrefUrl = normalizeUrl(split.url);
+ var uri = parseUri(hrefUrl);
+
+ // Best-effort support for Imgur page links (e.g. https://imgur.com/<id>).
+ var imgurId = imgurIdFromUri(uri);
+ if (imgurId) {
+ LastMsgContainsImage = true;
+ var candidates = imgurCandidateUrls(imgurId);
+ var first = candidates[0];
+ return "<a target=\"_blank\" href=\"" + hrefUrl + "\" class=\"img-wrapper\" onclick=\"return imgClickHandler()\">"
+ + "<img src=\"" + first + "\" class=\"unbound imgur-hotlink\" data-imgur-idx=\"0\" data-imgur-candidates=\"" + candidates.join("|") + "\" onerror=\"imgurHotlinkFallback(this)\">"
+ + "</a>" + split.suffix;
+ }
+
+ linkUrl = hrefUrl;
- var uri = parseUri(url)
var type = getUriType(uri)
if (type == 'image') {
LastMsgContainsImage = true;
- return "<a target='_blank' href='" + linkUrl + "' class='img-wrapper' onclick='return imgClickHandler()'><img src='" + linkUrl + "' class='unbound'></a>";
+ return "<a target=\"_blank\" href=\"" + linkUrl + "\" class=\"img-wrapper\" onclick=\"return imgClickHandler()\"><img src=\"" + linkUrl + "\" class=\"unbound\"></a>" + split.suffix;
+ } else if (type == 'video') {
+ LastMsgContainsImage = true;
+ var videoUrl = linkUrl.replace(/\.gifv([?#].*)?$/i, ".mp4$1");
+ return "<a target=\"_blank\" href=\"" + linkUrl + "\" class=\"video-wrapper\" onclick=\"return imgClickHandler()\"><video src=\"" + videoUrl + "\" autoplay loop muted controls playsinline></video></a>" + split.suffix;
} else if (type == 'youtube') {
Youtube.startAnimation();
- return "<a target='_blank' class='youtube' href='" + linkUrl + "'>" +
- "<img class='youtube-thumb' width='130' height='97' src='"+Youtube.nextThumbUrl(uri.queryKey.v)+"'>" +
- "<img class='youtube-controls' src='/static/img/youtube.controls.png'></a>"
+ return "<a target=\"_blank\" class=\"youtube\" href=\"" + linkUrl + "\">" +
+ "<img class=\"youtube-thumb\" width=\"130\" height=\"97\" src=\"" + Youtube.nextThumbUrl(uri.queryKey.v) + "\">" +
+ "<img class=\"youtube-controls\" src=\"/static/img/youtube.controls.png\"></a>" + split.suffix;
} else if (type == 'midi') {
- return '<embed src="'+linkUrl+'" autostart="false" loop="false" volume="80" width="150" height="20" style="vertical-align:bottom"> <a href="'+linkUrl+'">'+uri.file+'</a>'
+ return '<embed src="' + linkUrl + '" autostart="false" loop="false" volume="80" width="150" height="20" style="vertical-align:bottom"> <a href="' + linkUrl + '">' + uri.file + '</a>' + split.suffix;
} else if (type == 'wav') {
- return '<audio src="'+linkUrl+'" controls volume="80" width="150" height="20" style="vertical-align:bottom"></audio> <a href="'+linkUrl+'">'+uri.file+'</a>'
+ return '<audio src="' + linkUrl + '" controls volume="80" width="150" height="20" style="vertical-align:bottom"></audio> <a href="' + linkUrl + '">' + uri.file + '</a>' + split.suffix;
} else
- return "<a target='_blank' href='" + linkUrl + "'>" + url + "</a>";
+ return "<a target=\"_blank\" href=\"" + linkUrl + "\">" + split.url + "</a>" + split.suffix;
}
@@ -130,6 +201,9 @@ function linkReplace(url) {
function getUriType(uri){
if (PicRegex.test(uri.file.toLowerCase()))
return "image";
+
+ if (VideoRegex.test(uri.file.toLowerCase()))
+ return "video";
var domain = parseDomain(uri.host)
diff --git a/static/js/src/youtube.js b/static/js/src/youtube.js
index 2b6a977..ccb2cd4 100755
--- a/static/js/src/youtube.js
+++ b/static/js/src/youtube.js
@@ -19,7 +19,7 @@ Youtube = {
var img = $(this);
// yt thumb url example: https://i.ytimg.com/vi/0123456789A/1.jpg
var src = img.attr("src") || ""
- var match = src.match(/\\/vi\\/([^/]{11})\\/(\\d)\\.jpg/i)
+ var match = src.match(/\/vi\/([^/]{11})\/(\d)\.jpg/i)
if (!match) return
var v = match[1]
var num = match[2]