Skip to content

Commit f1780d5

Browse files
committed
Replaced defunct YQL with multiple CORS proxies
1 parent bd38288 commit f1780d5

File tree

4 files changed

+108
-125
lines changed

4 files changed

+108
-125
lines changed

htmlpreview.js

+85-107
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,115 @@
1-
var HTMLPreview = {
1+
(function () {
2+
3+
var previewForm = document.getElementById('previewform');
24

3-
content: '',
5+
var url = location.search.substring(1).replace(/\/\/github\.com/, '//raw.githubusercontent.com').replace(/\/blob\//, '/'); //Get URL of the raw file
46

5-
previewform: document.getElementById('previewform'),
6-
7-
file: function() {
8-
return location.search.substring(1); //Get everything after the ?
9-
},
10-
11-
raw: function() {
12-
return HTMLPreview.file().replace(/\/\/github\.com/, '//raw.githubusercontent.com').replace(/\/blob\//, '/'); //Get URL of the raw file
13-
},
14-
15-
replaceAssets: function() {
16-
var frame, a, link, script, i, href, src;
7+
var replaceAssets = function () {
8+
var frame, a, link, links = [], script, scripts = [], i, href, src;
9+
//Framesets
10+
if (document.querySelectorAll('frameset').length)
11+
return; //Don't replace CSS/JS if it's a frameset, because it will be erased by document.write()
12+
//Frames
1713
frame = document.querySelectorAll('iframe[src],frame[src]');
18-
for(i = 0; i < frame.length; ++i) {
14+
for (i = 0; i < frame.length; ++i) {
1915
src = frame[i].src; //Get absolute URL
20-
if(src.indexOf('//raw.githubusercontent.com') > 0 || src.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org
21-
frame[i].src = '//' + location.hostname + location.pathname + '?' + src; //Then rewrite URL so it can be loaded using YQL
16+
if (src.indexOf('//raw.githubusercontent.com') > 0 || src.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org
17+
frame[i].src = '//' + location.hostname + location.pathname + '?' + src; //Then rewrite URL so it can be loaded using CORS proxy
2218
}
2319
}
20+
//Links
2421
a = document.querySelectorAll('a[href]');
25-
for(i = 0; i < a.length; ++i) {
22+
for (i = 0; i < a.length; ++i) {
2623
href = a[i].href; //Get absolute URL
27-
if(href.indexOf('#') > 0) { //Check if it's an anchor
24+
if (href.indexOf('#') > 0) { //Check if it's an anchor
2825
a[i].href = '//' + location.hostname + location.pathname + location.search + '#' + a[i].hash.substring(1); //Then rewrite URL with support for empty anchor
29-
}
30-
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
31-
a[i].href = '//' + location.hostname + location.pathname + '?' + href; //Then rewrite URL so it can be loaded using YQL
26+
} 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
27+
a[i].href = '//' + location.hostname + location.pathname + '?' + href; //Then rewrite URL so it can be loaded using CORS proxy
3228
}
3329
}
34-
if(document.querySelectorAll('frameset').length)
35-
return; //Don't replace CSS/JS if it's a frameset, because it will be erased by document.write()
30+
//Stylesheets
3631
link = document.querySelectorAll('link[rel=stylesheet]');
37-
for(i = 0; i < link.length; ++i) {
32+
for (i = 0; i < link.length; ++i) {
3833
href = link[i].href; //Get absolute URL
39-
if(href.indexOf('//raw.githubusercontent.com') > 0 || href.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org
40-
HTMLPreview.send(href, 'loadCSS'); //Then load it using YQL
34+
if (href.indexOf('//raw.githubusercontent.com') > 0 || href.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org
35+
links.push(fetchProxy(href, null, 0)); //Then add it to links queue and fetch using CORS proxy
4136
}
4237
}
38+
Promise.all(links).then(function (res) {
39+
for (i = 0; i < res.length; ++i) {
40+
loadCSS(res[i]);
41+
}
42+
});
43+
//Scripts
4344
script = document.querySelectorAll('script[type="text/htmlpreview"]');
44-
for(i = 0; i < script.length; ++i) {
45+
for (i = 0; i < script.length; ++i) {
4546
src = script[i].src; //Get absolute URL
46-
if(src.indexOf('//raw.githubusercontent.com') > 0 || src.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org
47-
HTMLPreview.send(src, 'loadJS'); //Then load it using YQL
48-
}
49-
else { //Append all inline scripts
47+
if (src.indexOf('//raw.githubusercontent.com') > 0 || src.indexOf('//bitbucket.org') > 0) { //Check if it's from raw.github.com or bitbucket.org
48+
scripts.push(fetchProxy(src, null, 0)); //Then add it to scripts queue and fetch using CORS proxy
49+
} else {
5050
script[i].removeAttribute('type');
51-
document.write(script[i].outerHTML);
51+
scripts.push(script[i].innerHTML); //Add inline script to queue to eval in order
5252
}
5353
}
54-
},
54+
Promise.all(scripts).then(function (res) {
55+
for (i = 0; i < res.length; ++i) {
56+
loadJS(res[i]);
57+
}
58+
});
59+
};
5560

56-
loadHTML: function(data) {
57-
/* if(data
58-
&& data.query
59-
&& data.query.diagnostics
60-
&& data.query.diagnostics.redirect) {
61-
HTMLPreview.send(data.query.diagnostics.redirect.content, 'loadHTML');
62-
}
63-
else */
64-
if(data
65-
&& data.query
66-
&& data.query.results
67-
&& data.query.results.resources
68-
&& data.query.results.resources.content
69-
&& data.query.results.resources.status == 200) {
70-
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>
71-
setTimeout(function() {
61+
var loadHTML = function (data) {
62+
if (data) {
63+
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">
64+
setTimeout(function () {
7265
document.open();
73-
document.write(HTMLPreview.content);
66+
document.write(data);
7467
document.close();
75-
}, 50); //Delay updating document to have it cleared before
76-
}
77-
else if(data
78-
&& data.error
79-
&& data.error.description) {
80-
HTMLPreview.previewform.innerHTML = data.error.description;
68+
replaceAssets();
69+
}, 10); //Delay updating document to have it cleared before
8170
}
82-
else
83-
HTMLPreview.previewform.innerHTML = 'Error: Cannot load file ' + HTMLPreview.raw();
84-
},
71+
};
8572

86-
loadCSS: function(data) {
87-
/* if(data
88-
&& data.query
89-
&& data.query.diagnostics
90-
&& data.query.diagnostics.redirect) {
91-
HTMLPreview.send(data.query.diagnostics.redirect.content, 'loadCSS');
73+
var loadCSS = function (data) {
74+
if (data) {
75+
var style = document.createElement('style');
76+
style.innerHTML = data;
77+
document.head.appendChild(style);
9278
}
93-
else */
94-
if(data
95-
&& data.query
96-
&& data.query.results
97-
&& data.query.results.resources
98-
&& data.query.results.resources.content
99-
&& data.query.results.resources.status == 200) {
100-
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
101-
}
102-
},
79+
};
10380

104-
loadJS: function(data) {
105-
/* if(data
106-
&& data.query
107-
&& data.query.diagnostics
108-
&& data.query.diagnostics.redirect) {
109-
HTMLPreview.send(data.query.diagnostics.redirect.content, 'loadJS');
110-
} else */
111-
if(data
112-
&& data.query
113-
&& data.query.results
114-
&& data.query.results.resources
115-
&& data.query.results.resources.content
116-
&& data.query.results.resources.status == 200) {
117-
document.write('<script>' + data.query.results.resources.content + '</script>');
81+
var loadJS = function (data) {
82+
if (data) {
83+
var script = document.createElement('script');
84+
script.innerHTML = data;
85+
document.body.appendChild(script);
11886
}
119-
},
120-
121-
send: function(file, callback) {
122-
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
123-
},
87+
};
88+
89+
var fetchProxy = function (url, options, i) {
90+
var proxy = [
91+
'https://cors.io/?',
92+
'https://jsonp.afeld.me/?url=',
93+
'https://cors-anywhere.herokuapp.com/'
94+
];
95+
return fetch(proxy[i] + url, options).then(function (res) {
96+
if (!res.ok)
97+
throw new Error('Cannot load ' + url + ': ' + res.status + ' ' + res.statusText);
98+
return res.text();
99+
}).catch(function (error) {
100+
if (i === proxy.length - 1)
101+
throw error;
102+
return fetchProxy(url, options, i + 1);
103+
})
104+
};
124105

125-
submitform: function() {
126-
location.href = '/?' + document.getElementById('file').value;
127-
return false;
128-
},
106+
if (url && url.indexOf(location.href) < 0)
107+
fetchProxy(url, null, 0).then(loadHTML).catch(function (error) {
108+
console.error(error);
109+
previewForm.style.display = 'block';
110+
previewForm.innerText = error;
111+
});
112+
else
113+
previewForm.style.display = 'block';
129114

130-
init: function() {
131-
HTMLPreview.previewform.onsubmit = HTMLPreview.submitform;
132-
if(HTMLPreview.file()) {
133-
HTMLPreview.previewform.innerHTML = '<p>Loading...</p>';
134-
HTMLPreview.send(HTMLPreview.raw(), 'loadHTML');
135-
}
136-
}
137-
}
115+
})()

htmlpreview.min.js

-1
This file was deleted.

index.html

+10-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
a {
1515
color: #666;
1616
}
17-
form {
17+
#previewform {
18+
display: none;
1819
padding: 20px;
1920
text-align: center;
2021
}
@@ -31,14 +32,16 @@
3132
</style>
3233
</head>
3334
<body>
34-
<form id="previewform" action="">
35+
<form id="previewform" onsubmit="location.href='/?'+this.file.value;return false">
3536
<h1>GitHub &amp; BitBucket HTML Preview</h1>
36-
<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>
37-
<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>
38-
<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>
37+
<p>
38+
Enter URL of the HTML file to preview:
39+
<input type="url" id="file" value="" placeholder="e.g. https://github.com/user/repo/blob/master/index.html" size="60" autofocus>
40+
<input type="submit" value="Preview">
41+
</p>
42+
<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>
3943
<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>
4044
</form>
41-
<script src="/htmlpreview.min.js"></script>
42-
<script>HTMLPreview.init();</script>
45+
<script src="/htmlpreview.js"></script>
4346
</body>
4447
</html>

readme.md

+13-10
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
GitHub & BitBucket HTML Preview
22
-------------------------------
33

4-
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).
4+
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.
55

6-
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.
6+
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.
77

8-
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.:
8+
## Usage
99

10-
- http://htmlpreview.github.io/?https://github.com/twbs/bootstrap/gh-pages/2.3.2/index.html
11-
- http://htmlpreview.github.io/?https://github.com/documentcloud/backbone/blob/master/examples/todos/index.html
10+
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.:
1211

13-
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:
14-
```
15-
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)
16-
```
12+
- https://htmlpreview.github.io/?https://github.com/twbs/bootstrap/gh-pages/2.3.2/index.html
13+
- https://htmlpreview.github.io/?https://github.com/documentcloud/backbone/blob/master/examples/todos/index.html
1714

18-
**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.
15+
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.
16+
17+
**GitHub & BitBucket HTML Preview** was tested under the latest Google Chrome and Mozilla Firefox.
18+
19+
## License
20+
21+
&copy; 2019 Jerzy Głowacki under Apache License 2.0.

0 commit comments

Comments
 (0)