Skip to content

Commit

Permalink
Replaced defunct YQL with multiple CORS proxies
Browse files Browse the repository at this point in the history
  • Loading branch information
niutech committed Jan 10, 2019
1 parent bd38288 commit 102c584
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 125 deletions.
192 changes: 85 additions & 107 deletions htmlpreview.js
Original file line number Diff line number Diff line change
@@ -1,137 +1,115 @@
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]);
}
});
};

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(/<head>/i, '<head><base href="' + HTMLPreview.raw() + '">').replace(/<script( type=["'](text|application)\/javascript["'])?/gi, '<script type="text/htmlpreview"').replace(/<\/body>/i, '<script src="//' + location.hostname + '/htmlpreview.min.js"></script><script>HTMLPreview.replaceAssets();</script></body>').replace(/<\/head>\s*<frameset/gi, '<script src="//' + location.hostname + '/htmlpreview.min.js"></script><script>document.addEventListener("DOMContentLoaded",HTMLPreview.replaceAssets,false);</script></head><frameset'); //Add <base> just after <head> and inject <script> just before </body> or </head> if <frameset>
setTimeout(function() {
var loadHTML = function (data) {
if (data) {
data = data.replace(/<head([^>]*)>/i, '<head$1><base href="' + url + '">').replace(/<script(\s*src=["'][^"']*["'])?(\s*type=["'](text|application)\/javascript["'])?/gi, '<script type="text/htmlpreview"$1'); //Add <base> just after <head> and replace <script type="text/javascript"> with <script type="text/htmlpreview">
setTimeout(function () {
document.open();
document.write(HTMLPreview.content);
document.write(data);
document.close();
}, 50); //Delay updating document to have it cleared before
}
else if(data
&& data.error
&& data.error.description) {
HTMLPreview.previewform.innerHTML = data.error.description;
replaceAssets();
}, 10); //Delay updating document to have it cleared before
}
else
HTMLPreview.previewform.innerHTML = 'Error: Cannot load file ' + HTMLPreview.raw();
},
};

loadCSS: function(data) {
/* if(data
&& data.query
&& data.query.diagnostics
&& data.query.diagnostics.redirect) {
HTMLPreview.send(data.query.diagnostics.redirect.content, 'loadCSS');
var loadCSS = function (data) {
if (data) {
var style = document.createElement('style');
style.innerHTML = data;
document.head.appendChild(style);
}
else */
if(data
&& data.query
&& data.query.results
&& data.query.results.resources
&& data.query.results.resources.content
&& data.query.results.resources.status == 200) {
document.write('<style>' + data.query.results.resources.content.replace(/url\((?:'|")?([^\/][^:'"\)]+)(?:'|")?\)/gi, 'url(' + data.query.results.resources.url.replace(/[^\/]+\.css.*$/gi, '') + '$1)') + '</style>'); //If relative URL in CSS background-image property, then concatenate URL to CSS directory
}
},
};

loadJS: function(data) {
/* if(data
&& data.query
&& data.query.diagnostics
&& data.query.diagnostics.redirect) {
HTMLPreview.send(data.query.diagnostics.redirect.content, 'loadJS');
} else */
if(data
&& data.query
&& data.query.results
&& data.query.results.resources
&& data.query.results.resources.content
&& data.query.results.resources.status == 200) {
document.write('<script>' + data.query.results.resources.content + '</script>');
var loadJS = function (data) {
if (data) {
var script = document.createElement('script');
script.innerHTML = data;
document.body.appendChild(script);
}
},

send: function(file, callback) {
document.write('<script src="//query.yahooapis.com/v1/public/yql?q=select%20*%20from%20data.headers%20where%20url%3D%22' + encodeURIComponent(file) + '%22&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&diagnostics=true&callback=HTMLPreview.' + callback + '"></script>'); //Get content using YQL
},
};

var fetchProxy = function (url, options, i) {
var proxy = [
'https://cors.io/?',
'https://jsonp.afeld.me/?url=',
'https://cors-anywhere.herokuapp.com/'
];
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 = '<p>Loading...</p>';
HTMLPreview.send(HTMLPreview.raw(), 'loadHTML');
}
}
}
})()
1 change: 0 additions & 1 deletion htmlpreview.min.js

This file was deleted.

17 changes: 10 additions & 7 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
a {
color: #666;
}
form {
#previewform {
display: none;
padding: 20px;
text-align: center;
}
Expand All @@ -31,14 +32,16 @@
</style>
</head>
<body>
<form id="previewform" action="">
<form id="previewform" onsubmit="location.href='/?'+this.file.value;return false">
<h1>GitHub &amp; BitBucket HTML Preview</h1>
<p>Enter URL of the HTML file to preview: <input type="url" id="file" value="" placeholder="e.g. https://github.com/user/repo/blob/master/index.html" size="60" autofocus> <input type="submit" value="&raquo;"></p>
<p>or prepend to the URL: <strong>http://htmlpreview.github.io/?</strong>https://github.com/twbs/bootstrap/blob/gh-pages/2.3.2/index.html</p>
<p>or use this bookmarklet while browsing GitHub or BitBucket: <a href="javascript:void('http://htmlpreview.github.io/'==window.location?alert('Drag me to your bookmarks bar!'):window.location='http://htmlpreview.github.io/?'+window.location)"><strong>HTMLPreview</strong></a></p>
<p>
Enter URL of the HTML file to preview:
<input type="url" id="file" value="" placeholder="e.g. https://github.com/user/repo/blob/master/index.html" size="60" autofocus>
<input type="submit" value="Preview">
</p>
<p>or prepend to the URL: <code><strong>http://htmlpreview.github.io/?</strong>https://github.com/twbs/bootstrap/blob/gh-pages/2.3.2/index.html</code></p>
<p id="footer">Developed by <a href="https://github.com/niutech">niu tech</a> | Contribute on <a href="https://github.com/htmlpreview/htmlpreview.github.com">GitHub</a></p>
</form>
<script src="/htmlpreview.min.js"></script>
<script>HTMLPreview.init();</script>
<script src="/htmlpreview.js"></script>
</body>
</html>
23 changes: 13 additions & 10 deletions readme.md
Original file line number Diff line number Diff line change
@@ -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 evaluted by the browser.

**GitHub & BitBucket HTML Preview** was tested under the latest Google Chrome and Mozilla Firefox.

## License

&copy; 2019 Jerzy Głowacki under Apache License 2.0.

0 comments on commit 102c584

Please sign in to comment.