From d059971dcc9dd690224d6e88988f1aec8ac1266f Mon Sep 17 00:00:00 2001 From: ZiRo Date: Wed, 27 Apr 2016 14:21:12 +0200 Subject: [PATCH 1/3] make js more modular --- build-js.sh | 19 +++ contrib/js/api.js | 41 ++++++ contrib/js/backlink.js | 78 +++++++++++ contrib/js/banner.js | 13 ++ contrib/js/livechan.js | 66 ++++++++++ contrib/js/local_storage.js | 10 ++ contrib/js/theme.js | 15 +++ contrib/static/nntpchan.js | 253 ++++-------------------------------- 8 files changed, 268 insertions(+), 227 deletions(-) create mode 100755 build-js.sh create mode 100644 contrib/js/api.js create mode 100644 contrib/js/backlink.js create mode 100644 contrib/js/banner.js create mode 100644 contrib/js/livechan.js create mode 100644 contrib/js/local_storage.js create mode 100644 contrib/js/theme.js diff --git a/build-js.sh b/build-js.sh new file mode 100755 index 0000000..c244037 --- /dev/null +++ b/build-js.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -e +root=$(readlink -e $(dirname $0)) + +cd $root +if [ -z "$GOPATH" ]; then + export GOPATH=$PWD/go + mkdir -p $GOPATH +fi + +if [ ! -f $GOPATH/bin/minify ]; then + go get github.com/tdewolff/minify/cmd/minify +fi + +echo -e "//For source code and license information please check https://github.com/majestrate/nntpchan \n" > ./contrib/static/nntpchan.js + +for f in ./contrib/js/*.js ; do + cat $f | $GOPATH/bin/minify --mime=text/javascript >> ./contrib/static/nntpchan.js +done diff --git a/contrib/js/api.js b/contrib/js/api.js new file mode 100644 index 0000000..7186f27 --- /dev/null +++ b/contrib/js/api.js @@ -0,0 +1,41 @@ +// call an api method +// handler(json_object) on success +// handler(null) on fail +function nntpchan_apicall(url, handler, err_handler) { + var ajax = new XMLHttpRequest(); + ajax.onreadystatechange = function() { + if (ajax.readyState == XMLHttpRequest.DONE ) { + var status = ajax.status; + var j = null; + if (status == 200) { + // found + try { + j = JSON.parse(ajax.responseText); + } catch (e) {} // ignore parse error + } else if (status == 410) { + if (err_handler) {err_handler("cannot fetch post: api disabled");} + return; + } + handler(j); + } + }; + ajax.open("GET", url); + ajax.send(); +} + +// build post from json +// inject into parent +// if j is null then inject "not found" post +function nntpchan_buildpost(parent, j) { + var post = document.createElement("div"); + if (j) { + // huehuehue + post.innerHTML = j.PostMarkup; + inject_hover_for_element(post); + } else { + post.setAttribute("class", "notfound post"); + post.appendChild(document.createTextNode("post not found")); + } + parent.appendChild(post); +} + diff --git a/contrib/js/backlink.js b/contrib/js/backlink.js new file mode 100644 index 0000000..4f89ba8 --- /dev/null +++ b/contrib/js/backlink.js @@ -0,0 +1,78 @@ +// insert a backlink for a post given its short hash +function nntpchan_backlink(shorthash) { + var elem = document.getElementById("postform_message"); + if ( elem ) + { + elem.value += ">>" + shorthash.substr(0,10) + "\n"; + } +} + +// inject post hover behavior +function inject_hover(prefix, el, parent) { + if (!prefix) { throw "prefix is not defined"; } + var linkhash = el.getAttribute("backlinkhash"); + if (!linkhash) { throw "linkhash undefined"; } + console.log("rewrite linkhash "+linkhash); + + var elem = document.createElement("span"); + elem.setAttribute("class", "backlink_rewritten"); + elem.appendChild(document.createTextNode(">>"+linkhash.substr(0,10))); + if (!parent) { + parent = el.parentNode; + } + parent.removeChild(el); + parent.appendChild(elem); + + elem.onclick = function(ev) { + if(parent.backlink) { + nntpchan_apicall(prefix+"api/find?hash="+linkhash, function(j) { + var wrapper = document.createElement("div"); + wrapper.setAttribute("class", "hover "+linkhash); + if (j == null) { + // not found? + wrapper.setAttribute("class", "hover notfound-hover "+linkhash); + wrapper.appendChild(document.createTextNode("not found")); + } else { + // wrap backlink + nntpchan_buildpost(wrapper, j); + } + parent.appendChild(wrapper); + parent.backlink = false; + }, function(msg) { + var wrapper = document.createElement("div"); + wrapper.setAttribute("class", "hover "+linkhash); + wrapper.appendChild(document.createTextNode(msg)); + parent.appendChild(wrapper); + parent.backlink = false; + }); + } else { + var elems = document.getElementsByClassName(linkhash); + if (!elems) throw "bad state, no backlinks open?"; + for (var idx = 0 ; idx < elems.length; idx ++ ) { + elems[idx].parentNode.removeChild(elems[idx]); + } + parent.backlink = true; + } + }; + parent.backlink = true; +} + +// inject post hover for all backlinks in an element +function inject_hover_for_element(elem) { + var elems = elem.getElementsByClassName("backlink"); + var ls = []; + var l = elems.length; + for ( var idx = 0 ; idx < l ; idx ++ ) { + var e = elems[idx]; + ls.push(e); + } + for( var elem in ls ) { + inject_hover(prefix, ls[elem]); + } +} + +function init(prefix) { + // inject posthover ... + inject_hover_for_element(document); +} + diff --git a/contrib/js/banner.js b/contrib/js/banner.js new file mode 100644 index 0000000..e6e9c62 --- /dev/null +++ b/contrib/js/banner.js @@ -0,0 +1,13 @@ +var banner_count = 3; + +// inject a banner into an element +function nntpchan_inject_banners(elem, prefix) { + var n = Math.floor(Math.random() * banner_count); + var banner = prefix + "static/banner_"+n+".jpg"; + var e = document.createElement("img"); + e.src = banner; + e.id = "nntpchan_banner"; + elem.appendChild(e); +} + + diff --git a/contrib/js/livechan.js b/contrib/js/livechan.js new file mode 100644 index 0000000..c94168f --- /dev/null +++ b/contrib/js/livechan.js @@ -0,0 +1,66 @@ +function livechan_got_post(widget, j) { + // do scroll + while (widget.children.length > 5) { + // remove top element + widget.removeChild(widget.children[0]); + } + nntpchan_buildpost(widget, j); + // scroll to bottom + widget.scrollTop = widget.scrollHeight; +} + +// inject post form into an element +function inject_postform(prefix, parent) { + +} + +// inject livechan widget into parent +function inject_livechan_widget(prefix, parent) { + if ( "WebSocket" in window ) { + var url = "ws://"+document.location.host+prefix+"live"; + if ( document.location.protocol == "https:" ) { + url = "wss://"+document.location.host+prefix+"live"; + } + var socket = new WebSocket(url); + var progress = function(str) { + parent.innerHTML = "
livechan: "+str+"
"; + }; + progress("initialize"); + socket.onopen = function () { + progress("streaming (read only)"); + } + socket.onmessage = function(ev) { + var j = null; + try { + j = JSON.parse(ev.data); + } catch(e) { + // ignore + } + if (j) { + console.log(j); + livechan_got_post(parent, j); + } + } + socket.onclose = function(ev) { + progress("connection closed"); + setTimeout(function() { + inject_livechan_widget(prefix, parent); + }, 1000); + } + } else { + parent.innerHTML = "
livechan mode requires websocket support
"; + setTimeout(function() { + parent.innerHTML = ""; + }, 5000); + } +} + +function ukko_livechan(prefix) { + var ukko = document.getElementById("ukko_threads"); + if (ukko) { + // remove children + ukko.innerHTML = ""; + inject_livechan_widget(prefix, ukko); + } +} + diff --git a/contrib/js/local_storage.js b/contrib/js/local_storage.js new file mode 100644 index 0000000..fe64b91 --- /dev/null +++ b/contrib/js/local_storage.js @@ -0,0 +1,10 @@ +function get_storage() { + var st = null; + if (window.localStorage) { + st = window.localStorage; + } else if (localStorage) { + st = localStorage; + } + return st; +} + diff --git a/contrib/js/theme.js b/contrib/js/theme.js new file mode 100644 index 0000000..3a0ec1a --- /dev/null +++ b/contrib/js/theme.js @@ -0,0 +1,15 @@ +function enable_theme(prefix, name) { + if (prefix && name) { + var theme = document.getElementById("current_theme"); + if (theme) { + theme.href = prefix + "static/"+ name + ".css"; + var st = get_storage(); + st.nntpchan_prefix = prefix; + st.nntpchan_theme = name; + } + } +} + +// apply themes +var st = get_storage(); +enable_theme(st.nntpchan_prefix, st.nntpchan_theme); diff --git a/contrib/static/nntpchan.js b/contrib/static/nntpchan.js index 827d8af..fcb4812 100644 --- a/contrib/static/nntpchan.js +++ b/contrib/static/nntpchan.js @@ -1,228 +1,27 @@ -// -// nntpchan.js -- frontend ui niceness -// +//For source code and license information please check https://github.com/majestrate/nntpchan - -// insert a backlink for a post given its short hash -function nntpchan_backlink(shorthash) { - var elem = document.getElementById("postform_message"); - if ( elem ) - { - elem.value += ">>" + shorthash.substr(0,10) + "\n"; - } -} - -function get_storage() { - var st = null; - if (window.localStorage) { - st = window.localStorage; - } else if (localStorage) { - st = localStorage; - } - return st; -} - -function enable_theme(prefix, name) { - if (prefix && name) { - var theme = document.getElementById("current_theme"); - if (theme) { - theme.href = prefix + "static/"+ name + ".css"; - var st = get_storage(); - st.nntpchan_prefix = prefix; - st.nntpchan_theme = name; - } - } -} - - -// call an api method -// handler(json_object) on success -// handler(null) on fail -function nntpchan_apicall(url, handler, err_handler) { - var ajax = new XMLHttpRequest(); - ajax.onreadystatechange = function() { - if (ajax.readyState == XMLHttpRequest.DONE ) { - var status = ajax.status; - var j = null; - if (status == 200) { - // found - try { - j = JSON.parse(ajax.responseText); - } catch (e) {} // ignore parse error - } else if (status == 410) { - if (err_handler) {err_handler("cannot fetch post: api disabled");} - return; - } - handler(j); - } - }; - ajax.open("GET", url); - ajax.send(); -} - -// build post from json -// inject into parent -// if j is null then inject "not found" post -function nntpchan_buildpost(parent, j) { - var post = document.createElement("div"); - if (j) { - // huehuehue - post.innerHTML = j.PostMarkup; - inject_hover_for_element(post); - } else { - post.setAttribute("class", "notfound post"); - post.appendChild(document.createTextNode("post not found")); - } - parent.appendChild(post); -} - -// inject post hover behavior -function inject_hover(prefix, el, parent) { - if (!prefix) { throw "prefix is not defined"; } - var linkhash = el.getAttribute("backlinkhash"); - if (!linkhash) { throw "linkhash undefined"; } - console.log("rewrite linkhash "+linkhash); - - var elem = document.createElement("span"); - elem.setAttribute("class", "backlink_rewritten"); - elem.appendChild(document.createTextNode(">>"+linkhash.substr(0,10))); - if (!parent) { - parent = el.parentNode; - } - parent.removeChild(el); - parent.appendChild(elem); - - elem.onclick = function(ev) { - if(parent.backlink) { - nntpchan_apicall(prefix+"api/find?hash="+linkhash, function(j) { - var wrapper = document.createElement("div"); - wrapper.setAttribute("class", "hover "+linkhash); - if (j == null) { - // not found? - wrapper.setAttribute("class", "hover notfound-hover "+linkhash); - wrapper.appendChild(document.createTextNode("not found")); - } else { - // wrap backlink - nntpchan_buildpost(wrapper, j); - } - parent.appendChild(wrapper); - parent.backlink = false; - }, function(msg) { - var wrapper = document.createElement("div"); - wrapper.setAttribute("class", "hover "+linkhash); - wrapper.appendChild(document.createTextNode(msg)); - parent.appendChild(wrapper); - parent.backlink = false; - }); - } else { - var elems = document.getElementsByClassName(linkhash); - if (!elems) throw "bad state, no backlinks open?"; - for (var idx = 0 ; idx < elems.length; idx ++ ) { - elems[idx].parentNode.removeChild(elems[idx]); - } - parent.backlink = true; - } - }; - parent.backlink = true; -} - -function livechan_got_post(widget, j) { - // do scroll - while (widget.children.length > 5) { - // remove top element - widget.removeChild(widget.children[0]); - } - nntpchan_buildpost(widget, j); - // scroll to bottom - widget.scrollTop = widget.scrollHeight; -} - -// inject post form into an element -function inject_postform(prefix, parent) { - -} - -// inject livechan widget into parent -function inject_livechan_widget(prefix, parent) { - if ( "WebSocket" in window ) { - var url = "ws://"+document.location.host+prefix+"live"; - if ( document.location.protocol == "https:" ) { - url = "wss://"+document.location.host+prefix+"live"; - } - var socket = new WebSocket(url); - var progress = function(str) { - parent.innerHTML = "
livechan: "+str+"
"; - }; - progress("initialize"); - socket.onopen = function () { - progress("streaming (read only)"); - } - socket.onmessage = function(ev) { - var j = null; - try { - j = JSON.parse(ev.data); - } catch(e) { - // ignore - } - if (j) { - console.log(j); - livechan_got_post(parent, j); - } - } - socket.onclose = function(ev) { - progress("connection closed"); - setTimeout(function() { - inject_livechan_widget(prefix, parent); - }, 1000); - } - } else { - parent.innerHTML = "
livechan mode requires websocket support
"; - setTimeout(function() { - parent.innerHTML = ""; - }, 5000); - } -} - -function ukko_livechan(prefix) { - var ukko = document.getElementById("ukko_threads"); - if (ukko) { - // remove children - ukko.innerHTML = ""; - inject_livechan_widget(prefix, ukko); - } -} - -var banner_count = 3; - -// inject a banner into an element -function nntpchan_inject_banners(elem, prefix) { - var n = Math.floor(Math.random() * banner_count); - var banner = prefix + "static/banner_"+n+".jpg"; - var e = document.createElement("img"); - e.src = banner; - e.id = "nntpchan_banner"; - elem.appendChild(e); -} - -// inject post hover for all backlinks in an element -function inject_hover_for_element(elem) { - var elems = elem.getElementsByClassName("backlink"); - var ls = []; - var l = elems.length; - for ( var idx = 0 ; idx < l ; idx ++ ) { - var e = elems[idx]; - ls.push(e); - } - for( var elem in ls ) { - inject_hover(prefix, ls[elem]); - } -} - -function init(prefix) { - // inject posthover ... - inject_hover_for_element(document); -} - -// apply themes -var st = get_storage(); -enable_theme(st.nntpchan_prefix, st.nntpchan_theme); +function nntpchan_apicall(url,handler,err_handler){var ajax=new XMLHttpRequest();ajax.onreadystatechange=function(){if(ajax.readyState==XMLHttpRequest.DONE){var status=ajax.status;var j=null;if(status==200){try{j=JSON.parse(ajax.responseText);}catch(e){}}else if(status==410){if(err_handler){err_handler("cannot fetch post: api disabled");} +return;} +handler(j);}};ajax.open("GET",url);ajax.send();} +function nntpchan_buildpost(parent,j){var post=document.createElement("div");if(j){post.innerHTML=j.PostMarkup;inject_hover_for_element(post);}else{post.setAttribute("class","notfound post");post.appendChild(document.createTextNode("post not found"));} +parent.appendChild(post);}function nntpchan_backlink(shorthash){var elem=document.getElementById("postform_message");if(elem) +{elem.value+=">>"+shorthash.substr(0,10)+"\n";}} +function inject_hover(prefix,el,parent){if(!prefix){throw"prefix is not defined";} +var linkhash=el.getAttribute("backlinkhash");if(!linkhash){throw"linkhash undefined";} +console.log("rewrite linkhash "+linkhash);var elem=document.createElement("span");elem.setAttribute("class","backlink_rewritten");elem.appendChild(document.createTextNode(">>"+linkhash.substr(0,10)));if(!parent){parent=el.parentNode;} +parent.removeChild(el);parent.appendChild(elem);elem.onclick=function(ev){if(parent.backlink){nntpchan_apicall(prefix+"api/find?hash="+linkhash,function(j){var wrapper=document.createElement("div");wrapper.setAttribute("class","hover "+linkhash);if(j==null){wrapper.setAttribute("class","hover notfound-hover "+linkhash);wrapper.appendChild(document.createTextNode("not found"));}else{nntpchan_buildpost(wrapper,j);} +parent.appendChild(wrapper);parent.backlink=false;},function(msg){var wrapper=document.createElement("div");wrapper.setAttribute("class","hover "+linkhash);wrapper.appendChild(document.createTextNode(msg));parent.appendChild(wrapper);parent.backlink=false;});}else{var elems=document.getElementsByClassName(linkhash);if(!elems)throw"bad state, no backlinks open?";for(var idx=0;idx5){widget.removeChild(widget.children[0]);} +nntpchan_buildpost(widget,j);widget.scrollTop=widget.scrollHeight;} +function inject_postform(prefix,parent){} +function inject_livechan_widget(prefix,parent){if("WebSocket"in window){var url="ws://"+document.location.host+prefix+"live";if(document.location.protocol=="https:"){url="wss://"+document.location.host+prefix+"live";} +var socket=new WebSocket(url);var progress=function(str){parent.innerHTML="
livechan: "+str+"
";};progress("initialize");socket.onopen=function(){progress("streaming (read only)");} +socket.onmessage=function(ev){var j=null;try{j=JSON.parse(ev.data);}catch(e){} +if(j){console.log(j);livechan_got_post(parent,j);}} +socket.onclose=function(ev){progress("connection closed");setTimeout(function(){inject_livechan_widget(prefix,parent);},1000);}}else{parent.innerHTML="
livechan mode requires websocket support
";setTimeout(function(){parent.innerHTML="";},5000);}} +function ukko_livechan(prefix){var ukko=document.getElementById("ukko_threads");if(ukko){ukko.innerHTML="";inject_livechan_widget(prefix,ukko);}}function get_storage(){var st=null;if(window.localStorage){st=window.localStorage;}else if(localStorage){st=localStorage;} +return st;}function enable_theme(prefix,name){if(prefix&&name){var theme=document.getElementById("current_theme");if(theme){theme.href=prefix+"static/"+name+".css";var st=get_storage();st.nntpchan_prefix=prefix;st.nntpchan_theme=name;}}} +var st=get_storage();enable_theme(st.nntpchan_prefix,st.nntpchan_theme); \ No newline at end of file From 2dea0917d11b4a0a5bd036d56efe20cc3e31dd2d Mon Sep 17 00:00:00 2001 From: ZiRo Date: Wed, 27 Apr 2016 15:36:57 +0200 Subject: [PATCH 2/3] Add inline webm playback --- build-js.sh | 2 + contrib/js/expand-video.js | 246 ++++++++++++++++++++++ contrib/js/main.js_ | 11 + contrib/static/collapse.gif | Bin 0 -> 60 bytes contrib/static/nntpchan.js | 17 +- contrib/templates/default/board.mustache | 2 + contrib/templates/default/post.mustache | 2 +- contrib/templates/default/thread.mustache | 1 + contrib/templates/default/ukko.mustache | 1 + 9 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 contrib/js/expand-video.js create mode 100644 contrib/js/main.js_ create mode 100644 contrib/static/collapse.gif diff --git a/build-js.sh b/build-js.sh index c244037..9fc0385 100755 --- a/build-js.sh +++ b/build-js.sh @@ -14,6 +14,8 @@ fi echo -e "//For source code and license information please check https://github.com/majestrate/nntpchan \n" > ./contrib/static/nntpchan.js + +cat ./contrib/js/main.js_ | $GOPATH/bin/minify --mime=text/javascript >> ./contrib/static/nntpchan.js for f in ./contrib/js/*.js ; do cat $f | $GOPATH/bin/minify --mime=text/javascript >> ./contrib/static/nntpchan.js done diff --git a/contrib/js/expand-video.js b/contrib/js/expand-video.js new file mode 100644 index 0000000..5ef2c2b --- /dev/null +++ b/contrib/js/expand-video.js @@ -0,0 +1,246 @@ +/* This file is dedicated to the public domain; you may do as you wish with it. */ +/* Note: This code expects the global variable configRoot to be set. */ +var configRoot = ""; + + +if (typeof _ == 'undefined') { + var _ = function(a) { return a; }; +} + +function setupVideo(thumb, url) { + if (thumb.videoAlreadySetUp) return; + thumb.videoAlreadySetUp = true; + + var video = null; + var videoContainer, videoHide; + var expanded = false; + var hovering = false; + //var loop = setting("videoloop"); + var loop = true; + var loopControls = [document.createElement("span"), document.createElement("span")]; + var fileInfo = thumb.parentNode.querySelector(".fileinfo"); + var mouseDown = false; + + function unexpand() { + if (expanded) { + expanded = false; + if (video.pause) video.pause(); + videoContainer.style.display = "none"; + thumb.style.display = "inline"; + video.style.maxWidth = "inherit"; + video.style.maxHeight = "inherit"; + } + } + + function unhover() { + if (hovering) { + hovering = false; + if (video.pause) video.pause(); + videoContainer.style.display = "none"; + video.style.maxWidth = "inherit"; + video.style.maxHeight = "inherit"; + } + } + + // Create video element if does not exist yet + function getVideo() { + if (video == null) { + video = document.createElement("video"); + video.src = url; + video.loop = loop; + video.innerText = _("Your browser does not support HTML5 video."); + + videoHide = document.createElement("img"); + videoHide.src = configRoot + "static/collapse.gif"; + videoHide.alt = "[ - ]"; + videoHide.title = "Collapse video"; + videoHide.style.marginLeft = "-15px"; + videoHide.style.cssFloat = "left"; + videoHide.addEventListener("click", unexpand, false); + + videoContainer = document.createElement("div"); + videoContainer.style.paddingLeft = "15px"; + videoContainer.style.display = "none"; + videoContainer.appendChild(videoHide); + videoContainer.appendChild(video); + thumb.parentNode.insertBefore(videoContainer, thumb.nextSibling); + + // Dragging to the left collapses the video + video.addEventListener("mousedown", function(e) { + if (e.button == 0) mouseDown = true; + }, false); + video.addEventListener("mouseup", function(e) { + if (e.button == 0) mouseDown = false; + }, false); + video.addEventListener("mouseenter", function(e) { + mouseDown = false; + }, false); + video.addEventListener("mouseout", function(e) { + if (mouseDown && e.clientX - video.getBoundingClientRect().left <= 0) { + unexpand(); + } + mouseDown = false; + }, false); + } + } + + // Clicking on thumbnail expands video + thumb.addEventListener("click", function(e) { + if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) { + getVideo(); + expanded = true; + hovering = false; + + video.style.position = "static"; + video.style.pointerEvents = "inherit"; + video.style.display = "inline"; + videoHide.style.display = "inline"; + videoContainer.style.display = "block"; + videoContainer.style.position = "static"; + video.parentNode.parentNode.removeAttribute('style'); + thumb.style.display = "none"; + + //video.muted = (setting("videovolume") == 0); + //video.volume = setting("videovolume"); + video.controls = true; + if (video.readyState == 0) { + video.addEventListener("loadedmetadata", expand2, false); + } else { + setTimeout(expand2, 0); + } + video.play(); + e.preventDefault(); + } + }, false); + + function expand2() { + video.style.maxWidth = "100%"; + video.style.maxHeight = window.innerHeight + "px"; + var bottom = video.getBoundingClientRect().bottom; + if (bottom > window.innerHeight) { + window.scrollBy(0, bottom - window.innerHeight); + } + // work around Firefox volume control bug + //video.volume = Math.max(setting("videovolume") - 0.001, 0); + //video.volume = setting("videovolume"); + } + + // Hovering over thumbnail displays video + thumb.addEventListener("mouseover", function(e) { + //if (setting("videohover")) { + if (false) { // DOESN'T WORK YET + getVideo(); + expanded = false; + hovering = true; + + var docRight = document.documentElement.getBoundingClientRect().right; + var thumbRight = thumb.querySelector("img, video").getBoundingClientRect().right; + var maxWidth = docRight - thumbRight - 20; + if (maxWidth < 250) maxWidth = 250; + + video.style.position = "fixed"; + video.style.right = "0px"; + video.style.top = "0px"; + var docRight = document.documentElement.getBoundingClientRect().right; + var thumbRight = thumb.querySelector("img, video").getBoundingClientRect().right; + video.style.maxWidth = maxWidth + "px"; + video.style.maxHeight = "100%"; + video.style.pointerEvents = "none"; + + video.style.display = "inline"; + videoHide.style.display = "none"; + videoContainer.style.display = "inline"; + videoContainer.style.position = "fixed"; + + //video.muted = (setting("videovolume") == 0); + //video.volume = setting("videovolume"); + video.controls = false; + video.play(); + } + }, false); + + thumb.addEventListener("mouseout", unhover, false); + + // Scroll wheel on thumbnail adjusts default volume + thumb.addEventListener("wheel", function(e) { + //if (setting("videohover")) { + if (true) { + //var volume = setting("videovolume"); + if (e.deltaY > 0) volume -= 0.1; + if (e.deltaY < 0) volume += 0.1; + if (volume < 0) volume = 0; + if (volume > 1) volume = 1; + if (video != null) { + video.muted = (volume == 0); + video.volume = volume; + } + //changeSetting("videovolume", volume); + e.preventDefault(); + } + }, false); + + // [play once] vs [loop] controls + /*function setupLoopControl(i) { + loopControls[i].addEventListener("click", function(e) { + loop = (i != 0); + thumb.href = thumb.href.replace(/([\?&])loop=\d+/, "$1loop=" + i); + if (video != null) { + video.loop = loop; + if (loop && video.currentTime >= video.duration) { + video.currentTime = 0; + } + } + loopControls[i].style.fontWeight = "bold"; + loopControls[1-i].style.fontWeight = "inherit"; + }, false); + } + + loopControls[0].textContent = _("[play once]"); + loopControls[1].textContent = _("[loop]"); + loopControls[(setting("videoloop") ? 1 : 0)].style.fontWeight = "bold"; + for (var i = 0; i < 2; i++) { + setupLoopControl(i); + loopControls[i].style.whiteSpace = "nowrap"; + fileInfo.appendChild(document.createTextNode(" ")); + fileInfo.appendChild(loopControls[i]); + }*/ +} + +function setupVideosIn(element) { + var thumbs = element.querySelectorAll("a.file"); + for (var i = 0; i < thumbs.length; i++) { + if (/(\.webm)|(\.mp4)$/.test(thumbs[i].pathname)) { + setupVideo(thumbs[i], thumbs[i].href); + } else { + var url = thumbs[i].href + if (/(\.webm)|(\.mp4)$/.test(url)) setupVideo(thumbs[i], url); + } + } +} + +onready(function(){ + // Insert menu from settings.js + if (typeof settingsMenu != "undefined" && typeof Options == "undefined") + document.body.insertBefore(settingsMenu, document.getElementsByTagName("hr")[0]); + + // Setup Javascript events for videos in document now + setupVideosIn(document); + + // Setup Javascript events for videos added by updater + if (window.MutationObserver) { + var observer = new MutationObserver(function(mutations) { + for (var i = 0; i < mutations.length; i++) { + var additions = mutations[i].addedNodes; + if (additions == null) continue; + for (var j = 0; j < additions.length; j++) { + var node = additions[j]; + if (node.nodeType == 1) { + setupVideosIn(node); + } + } + } + }); + observer.observe(document.body, {childList: true, subtree: true}); + } +}); + diff --git a/contrib/js/main.js_ b/contrib/js/main.js_ new file mode 100644 index 0000000..46f9b5d --- /dev/null +++ b/contrib/js/main.js_ @@ -0,0 +1,11 @@ +onready_callbacks = []; +function onready(fnc) { + onready_callbacks.push(fnc); +} + +function ready() { + for (var i = 0; i < onready_callbacks.length; i++) { + onready_callbacks[i](); + } +} + diff --git a/contrib/static/collapse.gif b/contrib/static/collapse.gif new file mode 100644 index 0000000000000000000000000000000000000000..470815ac073f22a0e8607a6438b444aa3d798e33 GIT binary patch literal 60 zcmZ?wbhEHb5){widget.removeChild(widget.children[0]);} +function init(prefix){inject_hover_for_element(document);}var banner_count=3;function nntpchan_inject_banners(elem,prefix){var n=Math.floor(Math.random()*banner_count);var banner=prefix+"static/banner_"+n+".jpg";var e=document.createElement("img");e.src=banner;e.id="nntpchan_banner";elem.appendChild(e);}var configRoot="";if(typeof _=='undefined'){var _=function(a){return a;};} +function setupVideo(thumb,url){if(thumb.videoAlreadySetUp)return;thumb.videoAlreadySetUp=true;var video=null;var videoContainer,videoHide;var expanded=false;var hovering=false;var loop=true;var loopControls=[document.createElement("span"),document.createElement("span")];var fileInfo=thumb.parentNode.querySelector(".fileinfo");var mouseDown=false;function unexpand(){if(expanded){expanded=false;if(video.pause)video.pause();videoContainer.style.display="none";thumb.style.display="inline";video.style.maxWidth="inherit";video.style.maxHeight="inherit";}} +function unhover(){if(hovering){hovering=false;if(video.pause)video.pause();videoContainer.style.display="none";video.style.maxWidth="inherit";video.style.maxHeight="inherit";}} +function getVideo(){if(video==null){video=document.createElement("video");video.src=url;video.loop=loop;video.innerText=_("Your browser does not support HTML5 video.");videoHide=document.createElement("img");videoHide.src=configRoot+"static/collapse.gif";videoHide.alt="[ - ]";videoHide.title="Collapse video";videoHide.style.marginLeft="-15px";videoHide.style.cssFloat="left";videoHide.addEventListener("click",unexpand,false);videoContainer=document.createElement("div");videoContainer.style.paddingLeft="15px";videoContainer.style.display="none";videoContainer.appendChild(videoHide);videoContainer.appendChild(video);thumb.parentNode.insertBefore(videoContainer,thumb.nextSibling);video.addEventListener("mousedown",function(e){if(e.button==0)mouseDown=true;},false);video.addEventListener("mouseup",function(e){if(e.button==0)mouseDown=false;},false);video.addEventListener("mouseenter",function(e){mouseDown=false;},false);video.addEventListener("mouseout",function(e){if(mouseDown&&e.clientX-video.getBoundingClientRect().left<=0){unexpand();} +mouseDown=false;},false);}} +thumb.addEventListener("click",function(e){if(!e.shiftKey&&!e.ctrlKey&&!e.altKey&&!e.metaKey){getVideo();expanded=true;hovering=false;video.style.position="static";video.style.pointerEvents="inherit";video.style.display="inline";videoHide.style.display="inline";videoContainer.style.display="block";videoContainer.style.position="static";video.parentNode.parentNode.removeAttribute('style');thumb.style.display="none";video.controls=true;if(video.readyState==0){video.addEventListener("loadedmetadata",expand2,false);}else{setTimeout(expand2,0);} +video.play();e.preventDefault();}},false);function expand2(){video.style.maxWidth="100%";video.style.maxHeight=window.innerHeight+"px";var bottom=video.getBoundingClientRect().bottom;if(bottom>window.innerHeight){window.scrollBy(0,bottom-window.innerHeight);}} +thumb.addEventListener("mouseover",function(e){if(false){getVideo();expanded=false;hovering=true;var docRight=document.documentElement.getBoundingClientRect().right;var thumbRight=thumb.querySelector("img, video").getBoundingClientRect().right;var maxWidth=docRight-thumbRight-20;if(maxWidth<250)maxWidth=250;video.style.position="fixed";video.style.right="0px";video.style.top="0px";var docRight=document.documentElement.getBoundingClientRect().right;var thumbRight=thumb.querySelector("img, video").getBoundingClientRect().right;video.style.maxWidth=maxWidth+"px";video.style.maxHeight="100%";video.style.pointerEvents="none";video.style.display="inline";videoHide.style.display="none";videoContainer.style.display="inline";videoContainer.style.position="fixed";video.controls=false;video.play();}},false);thumb.addEventListener("mouseout",unhover,false);thumb.addEventListener("wheel",function(e){if(true){if(e.deltaY>0)volume-=0.1;if(e.deltaY<0)volume+=0.1;if(volume<0)volume=0;if(volume>1)volume=1;if(video!=null){video.muted=(volume==0);video.volume=volume;} +e.preventDefault();}},false);} +function setupVideosIn(element){var thumbs=element.querySelectorAll("a.file");for(var i=0;i5){widget.removeChild(widget.children[0]);} nntpchan_buildpost(widget,j);widget.scrollTop=widget.scrollHeight;} function inject_postform(prefix,parent){} function inject_livechan_widget(prefix,parent){if("WebSocket"in window){var url="ws://"+document.location.host+prefix+"live";if(document.location.protocol=="https:"){url="wss://"+document.location.host+prefix+"live";} diff --git a/contrib/templates/default/board.mustache b/contrib/templates/default/board.mustache index acc482f..cbf9540 100644 --- a/contrib/templates/default/board.mustache +++ b/contrib/templates/default/board.mustache @@ -59,6 +59,8 @@ var e = document.getElementById("nntpchan_banner"); nntpchan_inject_banners(e, prefix); init(prefix); + var onready_callbacks = new Array(); + ready();