diff --git a/htmlpreview.js b/htmlpreview.js index 44629b0..a38e616 100644 --- a/htmlpreview.js +++ b/htmlpreview.js @@ -1,135 +1,114 @@ -var HTMLPreview = { +(function () { + + var previewForm = document.getElementById('previewform'); - content: '', + var url = location.search.substring(1).replace(/\/\/github\.com/, '//raw.githubusercontent.com').replace(/\/blob\//, '/'); //Get URL of the raw file - previewform: document.getElementById('previewform'), - - file: function() { - return location.search.substring(1); //Get everything after the ? - }, - - raw: function() { - return HTMLPreview.file().replace(/\/\/github\.com/, '//raw.githubusercontent.com').replace(/\/blob\//, '/'); //Get URL of the raw file - }, - - replaceAssets: function() { - var frame, a, link, script, i, href, src; + var replaceAssets = function () { + var frame, a, link, links = [], script, scripts = [], i, href, src; + //Framesets + if (document.querySelectorAll('frameset').length) + return; //Don't replace CSS/JS if it's a frameset, because it will be erased by document.write() + //Frames frame = document.querySelectorAll('iframe[src],frame[src]'); - for(i = 0; i < frame.length; ++i) { + for (i = 0; i < frame.length; ++i) { src = frame[i].src; //Get absolute URL - if(src.indexOf('//raw.githubusercontent.com') > 0 || src.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org - frame[i].src = '//' + location.hostname + location.pathname + '?' + src; //Then rewrite URL so it can be loaded using YQL + if (src.indexOf('//raw.githubusercontent.com') > 0 || src.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org + frame[i].src = '//' + location.hostname + location.pathname + '?' + src; //Then rewrite URL so it can be loaded using CORS proxy } } + //Links a = document.querySelectorAll('a[href]'); - for(i = 0; i < a.length; ++i) { + for (i = 0; i < a.length; ++i) { href = a[i].href; //Get absolute URL - if(href.indexOf('#') > 0) { //Check if it's an anchor + if (href.indexOf('#') > 0) { //Check if it's an anchor a[i].href = '//' + location.hostname + location.pathname + location.search + '#' + a[i].hash.substring(1); //Then rewrite URL with support for empty anchor - } - else if((href.indexOf('//raw.githubusercontent.com') > 0 || href.indexOf('//bitbucket.org') > 0) && (href.indexOf('.html') > 0 || href.indexOf('.htm') > 0)) { //Check if it's from raw.github.com or bitbucket.org and to HTML files - a[i].href = '//' + location.hostname + location.pathname + '?' + href; //Then rewrite URL so it can be loaded using YQL + } else if ((href.indexOf('//raw.githubusercontent.com') > 0 || href.indexOf('//bitbucket.org') > 0) && (href.indexOf('.html') > 0 || href.indexOf('.htm') > 0)) { //Check if it's from raw.github.com or bitbucket.org and to HTML files + a[i].href = '//' + location.hostname + location.pathname + '?' + href; //Then rewrite URL so it can be loaded using CORS proxy } } - if(document.querySelectorAll('frameset').length) - return; //Don't replace CSS/JS if it's a frameset, because it will be erased by document.write() + //Stylesheets link = document.querySelectorAll('link[rel=stylesheet]'); - for(i = 0; i < link.length; ++i) { + for (i = 0; i < link.length; ++i) { href = link[i].href; //Get absolute URL - if(href.indexOf('//raw.githubusercontent.com') > 0 || href.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org - HTMLPreview.send(href, 'loadCSS'); //Then load it using YQL + if (href.indexOf('//raw.githubusercontent.com') > 0 || href.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org + links.push(fetchProxy(href, null, 0)); //Then add it to links queue and fetch using CORS proxy } } + Promise.all(links).then(function (res) { + for (i = 0; i < res.length; ++i) { + loadCSS(res[i]); + } + }); + //Scripts script = document.querySelectorAll('script[type="text/htmlpreview"]'); - for(i = 0; i < script.length; ++i) { + for (i = 0; i < script.length; ++i) { src = script[i].src; //Get absolute URL - if(src.indexOf('//raw.githubusercontent.com') > 0 || src.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org - HTMLPreview.send(src, 'loadJS'); //Then load it using YQL - } - else { //Append all inline scripts + if (src.indexOf('//raw.githubusercontent.com') > 0 || src.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org + scripts.push(fetchProxy(src, null, 0)); //Then add it to scripts queue and fetch using CORS proxy + } else { script[i].removeAttribute('type'); - document.write(script[i].outerHTML); + scripts.push(script[i].innerHTML); //Add inline script to queue to eval in order } } - }, + Promise.all(scripts).then(function (res) { + for (i = 0; i < res.length; ++i) { + loadJS(res[i]); + } + document.dispatchEvent(new Event('DOMContentLoaded', {bubbles: true, cancelable: true})); //Dispatch DOMContentLoaded event after loading all scripts + }); + }; - loadHTML: function(data) { - if(data - && data.query - && data.query.diagnostics - && data.query.diagnostics.redirect) { - HTMLPreview.send(data.query.diagnostics.redirect.content, 'loadHTML'); - } - else if(data - && data.query - && data.query.results - && data.query.results.resources - && data.query.results.resources.content - && data.query.results.resources.status == 200) { - HTMLPreview.content = data.query.results.resources.content.replace(//i, '').replace(//i, '').replace(/<\/head>\s* just after and inject '); - } - }, - - send: function(file, callback) { - document.write(''); //Get content using YQL - }, + }; + + var fetchProxy = function (url, options, i) { + var proxy = [ + '', // try without proxy first + 'https://api.codetabs.com/v1/proxy/?quest=' + ]; + return fetch(proxy[i] + url, options).then(function (res) { + if (!res.ok) throw new Error('Cannot load ' + url + ': ' + res.status + ' ' + res.statusText); + return res.text(); + }).catch(function (error) { + if (i === proxy.length - 1) + throw error; + return fetchProxy(url, options, i + 1); + }) + }; - submitform: function() { - location.href = '/?' + document.getElementById('file').value; - return false; - }, + if (url && url.indexOf(location.hostname) < 0) + fetchProxy(url, null, 0).then(loadHTML).catch(function (error) { + console.error(error); + previewForm.style.display = 'block'; + previewForm.innerText = error; + }); + else + previewForm.style.display = 'block'; - init: function() { - HTMLPreview.previewform.onsubmit = HTMLPreview.submitform; - if(HTMLPreview.file()) { - HTMLPreview.previewform.innerHTML = '

Loading...

'; - HTMLPreview.send(HTMLPreview.raw(), 'loadHTML'); - } - } -} +})() diff --git a/htmlpreview.min.js b/htmlpreview.min.js deleted file mode 100644 index 63f4d20..0000000 --- a/htmlpreview.min.js +++ /dev/null @@ -1 +0,0 @@ -var HTMLPreview={content:"",previewform:document.getElementById("previewform"),file:function(){return location.search.substring(1)},raw:function(){return HTMLPreview.file().replace(/\/\/github\.com/,"//raw.githubusercontent.com").replace(/\/blob\//,"/")},replaceAssets:function(){var a,b,c;a=document.querySelectorAll("iframe[src],frame[src]");for(b=0;b/i,'').replace(//i,' - + diff --git a/readme.md b/readme.md index 3270148..3721989 100644 --- a/readme.md +++ b/readme.md @@ -1,18 +1,21 @@ GitHub & BitBucket HTML Preview ------------------------------- -Many GitHub repositories don't use GitHub Pages to host their HTML files. **GitHub & BitBucket HTML Preview** allows you to render those files without cloning or downloading whole repositories. It is a client-side solution and does not involve any third party hosting servers (except for Yahoo! Query Language to fetch assets). +Many GitHub repositories don't use GitHub Pages to host their HTML files. **GitHub & BitBucket HTML Preview** allows you to render those files without cloning or downloading whole repositories. It is a client-side solution using a CORS proxy to fetch assets. -If you try to open raw versions of any HTML, CSS or JS files in a web browser directly from GitHub, all you will see are sources. GitHub forces them to use the "text/plain" content-type, so they cannot be interpreted. This script overrides it by using Yahoo! Query Language. +If you try to open raw version of any HTML, CSS or JS file in a web browser directly from GitHub, all you will see is a source code. GitHub forces them to use the "text/plain" content-type, so they cannot be interpreted. This script overrides it by using a CORS proxy. -In order to use it, just prepend this fragment to the URL of any HTML file: **[http://htmlpreview.github.io/?](http://htmlpreview.github.io/?)** e.g.: +## Usage - - http://htmlpreview.github.io/?https://github.com/twbs/bootstrap/gh-pages/2.3.2/index.html - - http://htmlpreview.github.io/?https://github.com/documentcloud/backbone/blob/master/examples/todos/index.html +In order to use it, just prepend this fragment to the URL of any HTML file: **[https://htmlpreview.github.io/?](https://htmlpreview.github.io/?)** e.g.: -What it does is load HTML using YQL, then process all links, frames, scripts and styles, and load each of them using YQL, so they can be evaluted in the browser. Here is the workflow: -``` -HTMLPreview.init() -> HTMLPreview.send(HTML) -> YQL fetch HTML -> HTMLPreview.loadHTML(data) -> HTMLPreview.replaceAssets() -> HTMLPreview.send(CSS) -> YQL fetch CSS -> HTMLPreview.loadCSS(data) -> HTMLPreview.send(JS) -> YQL fetch JS -> HTMLPreview.loadJS(data) -``` + - https://htmlpreview.github.io/?https://github.com/twbs/bootstrap/gh-pages/2.3.2/index.html + - https://htmlpreview.github.io/?https://github.com/documentcloud/backbone/blob/master/examples/todos/index.html -**GitHub & BitBucket HTML Preview** was tested under Google Chrome, Apple Safari and Mozilla Firefox, and it should work with majority of websites, not only GitHub & BitBucket. +What it does is: load HTML using CORS proxy, then process all links, frames, scripts and styles, and load each of them using CORS proxy, so they can be evaluated by the browser. + +**GitHub & BitBucket HTML Preview** was tested under the latest Google Chrome and Mozilla Firefox. + +## License + +© 2019 Jerzy GÅ‚owacki under Apache License 2.0.