Commit b2f14d961e6e1c355f040a7a0932f58b06198863

Golmote 2015-09-04T08:19:21

Merge branch 'plugin-jsonp' of https://github.com/nauzilus/prism into gh-pages Conflicts: components/prism-core.min.js

diff --git a/components.js b/components.js
index 01fd5f9..43f1536 100644
--- a/components.js
+++ b/components.js
@@ -375,6 +375,11 @@ var components = {
 			"title": "Show Language",
 			"owner": "nauzilus"
 		},
+		"jsonp-highlight": {
+			"title": "JSONP Highlight",
+			"noCSS": true,
+			"owner": "nauzilus"
+		},
 		"highlight-keywords": {
 			"title": "Highlight Keywords",
 			"owner": "vkbansal",
diff --git a/components/prism-core.js b/components/prism-core.js
index 8ee4050..3ab918d 100644
--- a/components/prism-core.js
+++ b/components/prism-core.js
@@ -140,7 +140,8 @@ var _ = _self.Prism = {
 			}
 		}
 	},
-
+	plugins: {},
+	
 	highlightAll: function(async, callback) {
 		var elements = document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');
 
diff --git a/components/prism-core.min.js b/components/prism-core.min.js
index d97d828..842973b 100644
--- a/components/prism-core.min.js
+++ b/components/prism-core.min.js
@@ -1 +1 @@
-var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=t.util.clone(e[r]));return a;case"Array":return e.map&&e.map(function(e){return t.util.clone(e)})}return e}},languages:{extend:function(e,n){var a=t.util.clone(t.languages[e]);for(var r in n)a[r]=n[r];return a},insertBefore:function(e,n,a,r){r=r||t.languages;var i=r[e];if(2==arguments.length){a=arguments[1];for(var l in a)a.hasOwnProperty(l)&&(i[l]=a[l]);return i}var o={};for(var s in i)if(i.hasOwnProperty(s)){if(s==n)for(var l in a)a.hasOwnProperty(l)&&(o[l]=a[l]);o[s]=i[s]}return t.languages.DFS(t.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,n,a){for(var r in e)e.hasOwnProperty(r)&&(n.call(e,r,e[r],a||r),"Object"===t.util.type(e[r])?t.languages.DFS(e[r],n):"Array"===t.util.type(e[r])&&t.languages.DFS(e[r],n,r))}},highlightAll:function(e,n){for(var a,r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'),i=0;a=r[i++];)t.highlightElement(a,e===!0,n)},highlightElement:function(a,r,i){for(var l,o,s=a;s&&!e.test(s.className);)s=s.parentNode;s&&(l=(s.className.match(e)||[,""])[1],o=t.languages[l]),a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+l,s=a.parentNode,/pre/i.test(s.nodeName)&&(s.className=s.className.replace(e,"").replace(/\s+/g," ")+" language-"+l);var u=a.textContent,g={element:a,language:l,grammar:o,code:u};if(!u||!o)return t.hooks.run("complete",g),void 0;if(t.hooks.run("before-highlight",g),r&&_self.Worker){var c=new Worker(t.filename);c.onmessage=function(e){g.highlightedCode=n.stringify(JSON.parse(e.data),l),t.hooks.run("before-insert",g),g.element.innerHTML=g.highlightedCode,i&&i.call(g.element),t.hooks.run("after-highlight",g),t.hooks.run("complete",g)},c.postMessage(JSON.stringify({language:g.language,code:g.code,immediateClose:!0}))}else g.highlightedCode=t.highlight(g.code,g.grammar,g.language),t.hooks.run("before-insert",g),g.element.innerHTML=g.highlightedCode,i&&i.call(a),t.hooks.run("after-highlight",g),t.hooks.run("complete",g)},highlight:function(e,a,r){var i=t.tokenize(e,a);return n.stringify(t.util.encode(i),r)},tokenize:function(e,n){var a=t.Token,r=[e],i=n.rest;if(i){for(var l in i)n[l]=i[l];delete n.rest}e:for(var l in n)if(n.hasOwnProperty(l)&&n[l]){var o=n[l];o="Array"===t.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var u=o[s],g=u.inside,c=!!u.lookbehind,f=0,h=u.alias;u=u.pattern||u;for(var p=0;p<r.length;p++){var d=r[p];if(r.length>e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=(o?" ":"")+s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+o+">"+i.content+"</"+i.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code,i=n.immediateClose;_self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),i&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
\ No newline at end of file
+var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=t.util.clone(e[r]));return a;case"Array":return e.map&&e.map(function(e){return t.util.clone(e)})}return e}},languages:{extend:function(e,n){var a=t.util.clone(t.languages[e]);for(var r in n)a[r]=n[r];return a},insertBefore:function(e,n,a,r){r=r||t.languages;var i=r[e];if(2==arguments.length){a=arguments[1];for(var l in a)a.hasOwnProperty(l)&&(i[l]=a[l]);return i}var o={};for(var s in i)if(i.hasOwnProperty(s)){if(s==n)for(var l in a)a.hasOwnProperty(l)&&(o[l]=a[l]);o[s]=i[s]}return t.languages.DFS(t.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,n,a){for(var r in e)e.hasOwnProperty(r)&&(n.call(e,r,e[r],a||r),"Object"===t.util.type(e[r])?t.languages.DFS(e[r],n):"Array"===t.util.type(e[r])&&t.languages.DFS(e[r],n,r))}},plugins:{},highlightAll:function(e,n){for(var a,r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'),i=0;a=r[i++];)t.highlightElement(a,e===!0,n)},highlightElement:function(a,r,i){for(var l,o,s=a;s&&!e.test(s.className);)s=s.parentNode;s&&(l=(s.className.match(e)||[,""])[1],o=t.languages[l]),a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+l,s=a.parentNode,/pre/i.test(s.nodeName)&&(s.className=s.className.replace(e,"").replace(/\s+/g," ")+" language-"+l);var u=a.textContent,g={element:a,language:l,grammar:o,code:u};if(!u||!o)return t.hooks.run("complete",g),void 0;if(t.hooks.run("before-highlight",g),r&&_self.Worker){var c=new Worker(t.filename);c.onmessage=function(e){g.highlightedCode=n.stringify(JSON.parse(e.data),l),t.hooks.run("before-insert",g),g.element.innerHTML=g.highlightedCode,i&&i.call(g.element),t.hooks.run("after-highlight",g),t.hooks.run("complete",g)},c.postMessage(JSON.stringify({language:g.language,code:g.code,immediateClose:!0}))}else g.highlightedCode=t.highlight(g.code,g.grammar,g.language),t.hooks.run("before-insert",g),g.element.innerHTML=g.highlightedCode,i&&i.call(a),t.hooks.run("after-highlight",g),t.hooks.run("complete",g)},highlight:function(e,a,r){var i=t.tokenize(e,a);return n.stringify(t.util.encode(i),r)},tokenize:function(e,n){var a=t.Token,r=[e],i=n.rest;if(i){for(var l in i)n[l]=i[l];delete n.rest}e:for(var l in n)if(n.hasOwnProperty(l)&&n[l]){var o=n[l];o="Array"===t.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var u=o[s],g=u.inside,c=!!u.lookbehind,f=0,h=u.alias;u=u.pattern||u;for(var p=0;p<r.length;p++){var d=r[p];if(r.length>e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=(o?" ":"")+s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+o+">"+i.content+"</"+i.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code,i=n.immediateClose;_self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),i&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
\ No newline at end of file
diff --git a/plugins/jsonp-highlight/index.html b/plugins/jsonp-highlight/index.html
new file mode 100644
index 0000000..d70d375
--- /dev/null
+++ b/plugins/jsonp-highlight/index.html
@@ -0,0 +1,174 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+
+<meta charset="utf-8" />
+<link rel="shortcut icon" href="favicon.png" />
+<title>JSONP Highlight ▲ Prism plugins</title>
+<base href="../.." />
+<link rel="stylesheet" href="style.css" />
+<link rel="stylesheet" href="themes/prism.css" data-noprefix />
+<script src="prefixfree.min.js"></script>
+
+<script>var _gaq = [['_setAccount', 'UA-33746269-1'], ['_trackPageview']];</script>
+<script src="http://www.google-analytics.com/ga.js" async></script>
+</head>
+<body>
+
+<header>
+	<div class="intro" data-src="templates/header-plugins.html" data-type="text/html"></div>
+
+	<h2>JSONP Highlight</h2>
+	<p>Fetch content with JSONP and highlight some interesting content (e.g. GitHub/Gists or Bitbucket API).</p>
+</header>
+
+<section class="language-markup">
+	<h1>How to use</h1>
+	
+	<p>Use the <code>data-jsonp</code> attribute on <code>&lt;pre></code> elements, like so:</p>
+	
+	<pre><code>&lt;pre
+  class="language-javascript"
+  data-jsonp="https://api.github.com/repos/leaverou/prism/contents/prism.js">
+&lt;/pre></code></pre>
+	
+	<p>
+		Don't specifiy the <code>callback</code> query parameter in the URL; this will be added
+		automatically. If the API expects a different callback parameter name however, use the
+		<code>data-callback</code> parameter to specify the name:
+	</p>
+	
+	<pre><code>&lt;pre class="&hellip;" data-jsonp="&hellip;" data-callback="cb">&lt;/pre></code></pre>
+
+	<p>
+		The next trick is of course actually extracting something from the JSONP response worth
+		highlighting, which means processing the response to extract the interesting data.
+	</p>	
+
+	<p>The following JSONP APIs are automatically detected and parsed:</p>
+
+	<ul>
+		<li><a href="https://developer.github.com/v3/repos/contents/#get-contents">GitHub</a></li>
+		<li><a href="https://developer.github.com/v3/gists/#get-a-single-gist">GitHub Gists</a></li>
+		<li><a href="https://confluence.atlassian.com/display/BITBUCKET/src+Resources#srcResources-GETrawcontentofanindividualfile">Bitbucket</a></li>
+	</ul>
+
+	<p>If you need to do your own parsing, you can hook your your own data adapters in two ways:</p>
+	<ol>
+		<li>
+			Supply the <code>data-adapter</code> parameter on the <code>&lt;pre></code> element.
+			This must be the name of a globally defined function.
+			The plugin will use <em>only</em> this adapter to parse the response.
+		</li>
+		<li>
+			Register your adapter function by calling
+			<code>Prism.plugins.jsonphighlight.registerAdapter(function(rsp) { &hellip; })</code>.
+			It will be added to the list of inbuilt adapters and used if no other registered
+			adapater (e.g. GitHub/Bitbucket) can parse the response.
+		</li>
+	</ol>
+	
+	<p>
+		In either case, the function must accept at least a single parameter (the JSONP response) and
+		returns a string of the content to highlight. If your adapter cannot parse the response, you
+		must return <code>null</code>. The DOM node that will contain the highlighted code will also
+		be passed in as the second argument, incase you need to use it to query any extra information
+		(maybe you wish to inspect the <code>class</code> or <code>data-jsonp</code> attributes to
+		assist in parsing the response).
+	</p>
+
+	<p>
+		The following example demonstrates both methods of using a custom adapter, to simply return
+		the stringyfied JSONP response (i.e highlight the entire JSONP data):
+	</p>
+
+	<pre><code>&lt;!-- perhaps this is in a .js file elsewhere -->
+&lt;script>
+	function dump_json(rsp) {
+		return "using dump_json: " + JSON.stringify(rsp,null,2);
+	}
+&lt;/script>
+
+&lt;!-- &hellip; include prism.js &hellip; -->
+&lt;script>
+	Prism.plugins.jsonphighlight.registerAdapter(function(rsp) {
+		return "using registerAdapter: " + JSON.stringify(rsp,null,2);
+	})
+&lt;/script>
+</code></pre>
+
+	<p>And later in your HTML:</p>
+
+	<pre><code>&lt;!-- using the data-adapter attribute -->
+&lt;pre class="language-javascript" data-jsonp="&hellip;" data-adapter="dump_json">&lt;/pre>
+
+&lt;!-- using whatever data adapters are available -->
+&lt;pre class="language-javascript" data-jsonp="&hellip;">&lt;/pre>
+</code></pre>
+
+	<p>
+		Finally, unlike like the <a href="/plugins/file-highlight/index.html">File Highlight</a>
+		plugin, you <em>do</em> need to supply the appropriate <code>class</code> with the language
+		to highlight. This could have been auto-detected, but since you're not actually linking to
+		a file it's not always possible (see below in the example using GitHub status).
+		Furthermore, if you're linking to files with a <code>.xaml</code> extension for example,
+		this plugin then needs to somehow map that to highlight as <code>markup</code>, which just
+		means more bloat. You know what you're trying to highlight, just say so :)
+	</p>
+
+	<h2>Caveat for Gists</h2>
+
+	<p>
+		There's a bit of a catch with gists, as they can actually contain multiple files.
+		There are two options to handle this:
+	</p>
+
+	<ol>
+		<li>
+			If your gist only contains one file, you don't need to to anything; the one and only
+			file will automatically be chosen and highlighted
+		</li>
+		<li>
+			If your file contains multiple files, the first one will be chosen by default.
+			However, you can supply the filename in the <code>data-filename</code> attribute, and
+			this file will be highlighted instead:
+			<pre><code>&lt;pre class="&hellip;" data-jsonp="&hellip;" data-filename="mydemo.js">&lt;/pre></code></pre>
+		</li>
+	</ol>
+</section>
+
+<section>
+	<h1>Examples</h1>
+	
+	<p>The plugin’s JS code (from GitHub):</p>
+ 	<pre class="lang-javascript" data-jsonp="https://api.github.com/repos/PrismJS/prism/contents/plugins/jsonp-highlight/prism-jsonp-highlight.js"></pre>
+
+	<p>GitHub Gist (gist contains a single file, automatically selected):</p>
+ 	<pre class="lang-javascript" data-jsonp="https://api.github.com/gists/599a04c05a22f48a292d"></pre>
+
+	<p>GitHub Gist (gist contains a multiple files, file to load specified):</p>
+ 	<pre class="lang-markup" data-jsonp="https://api.github.com/gists/599a04c05a22f48a292d" data-filename="dabblet.html"></pre>
+
+ 	<p>Bitbucket API:</p>
+	<pre class="lang-css" data-jsonp="https://bitbucket.org/!api/1.0/repositories/nauzilus/stylish/src/master/whirlpool/style.css"></pre>
+ 	
+ 	<p>Custom adapter (JSON.stringify showing GitHub <a href="https://status.github.com/api/status.json">status</a>):</p>
+	<pre class="lang-javascript" data-jsonp="https://status.github.com/api/status.json" data-adapter="dump_json"></pre>
+
+	<p>Registered adapter (as above, but without explicitly declaring the <code>data-adapter</code> attribute):</p>
+	<pre class="lang-javascript" data-jsonp="https://status.github.com/api/status.json"></pre>
+</section>
+
+<footer data-src="templates/footer.html" data-type="text/html"></footer>
+
+<script>function dump_json(x) { return "using dump_json: " + JSON.stringify(x,null,2); }</script>
+<script src="prism.js"></script>
+<script src="plugins/jsonp-highlight/prism-jsonp-highlight.js"></script>
+<script>Prism.plugins.jsonphighlight.registerAdapter(function (x) { return "using registerAdapter: " + JSON.stringify(x,null,2); })</script>
+<script src="utopia.js"></script>
+<script src="components.js"></script>
+<script src="code.js"></script>
+
+
+</body>
+</html>
\ No newline at end of file
diff --git a/plugins/jsonp-highlight/prism-jsonp-highlight.js b/plugins/jsonp-highlight/prism-jsonp-highlight.js
new file mode 100644
index 0000000..06a9e68
--- /dev/null
+++ b/plugins/jsonp-highlight/prism-jsonp-highlight.js
@@ -0,0 +1,151 @@
+(function() {
+	if ( !self.Prism || !self.document || !document.querySelectorAll ) return;
+
+	var adapters = [];
+	function registerAdapter(adapter) {
+		if (typeof adapter === "function" && !getAdapter(adapter)) {
+			adapters.push(adapter);
+		}
+	}
+	function getAdapter(adapter) {
+		if (typeof adapter === "function") {
+			return adapters.filter(function(fn) { return fn.valueOf() === adapter.valueOf()})[0];
+		}
+		else if (typeof adapter === "string" && adapter.length > 0) {
+			return adapters.filter(function(fn) { return fn.name === adapter})[0];
+		}
+		return null;
+	}
+	function removeAdapter(adapter) {
+		if (typeof adapter === "string")
+			adapter = getAdapter(adapter);
+		if (typeof adapter === "function") {
+			var index = adapters.indexOf(adapter);
+			if (index >=0) {
+				adapters.splice(index,1);
+			}
+		}
+	}
+
+	Prism.plugins.jsonphighlight = {
+		registerAdapter: registerAdapter,
+		removeAdapter: removeAdapter,
+		highlight: highlight
+	};
+	registerAdapter(function github(rsp, el) {
+		if ( rsp && rsp.meta && rsp.data ) {
+			if ( rsp.meta.status && rsp.meta.status >= 400 ) {
+				return "Error: " + ( rsp.data.message || rsp.meta.status );
+			}
+			else if ( typeof(rsp.data.content) === "string" ) {
+				return typeof(atob) === "function"
+					? atob(rsp.data.content.replace(/\s/g, ""))
+					: "Your browser cannot decode base64";
+			}
+		}
+		return null;
+	});
+	registerAdapter(function gist(rsp, el) {
+		if ( rsp && rsp.meta && rsp.data && rsp.data.files ) {
+			if ( rsp.meta.status && rsp.meta.status >= 400 ) {
+				return "Error: " + ( rsp.data.message || rsp.meta.status );
+			}
+			else {
+				var filename = el.getAttribute("data-filename");
+				if (filename == null) {
+					// Maybe in the future we can somehow render all files
+					// But the standard <script> include for gists does that nicely already,
+					// so that might be getting beyond the scope of this plugin
+					for (var key in rsp.data.files) {
+						if (rsp.data.files.hasOwnProperty(key)) {
+							filename = key;
+							break;
+						}
+					}
+				}
+				if (rsp.data.files[filename] !== undefined) {
+					return rsp.data.files[filename].content;
+				}
+				else {
+					return "Error: unknown or missing gist file " + filename;
+				}
+			}
+		}
+		return null;
+	});
+	registerAdapter(function bitbucket(rsp, el) {
+		return rsp && rsp.node && typeof(rsp.data) === "string"
+			? rsp.data
+			: null;
+	});
+
+	var jsonpcb = 0,
+	    loadstr = "Loading…";
+
+	function highlight() {
+		Array.prototype.slice.call(document.querySelectorAll("pre[data-jsonp]")).forEach(function(pre) {
+			pre.textContent = "";
+
+			var code = document.createElement("code");
+			code.textContent = loadstr;
+			pre.appendChild(code);
+
+			var adapterfn = pre.getAttribute("data-adapter");
+			var adapter = null;
+			if ( adapterfn ) {
+				if ( typeof(window[adapterfn]) === "function" ) {
+					adapter = window[adapterfn];
+				}
+				else {
+					code.textContent = "JSONP adapter function '" + adapterfn + "' doesn't exist";
+					return;
+				}
+			}
+
+			var cb = "prismjsonp" + ( jsonpcb++ );
+			
+			var uri = document.createElement("a");
+			var src = uri.href = pre.getAttribute("data-jsonp");
+			uri.href += ( uri.search ? "&" : "?" ) + ( pre.getAttribute("data-callback") || "callback" ) + "=" + cb;
+
+			var timeout = setTimeout(function() {
+				// we could clean up window[cb], but if the request finally succeeds, keeping it around is a good thing
+				if ( code.textContent === loadstr )
+					code.textContent = "Timeout loading '" + src + "'";
+			}, 5000);
+			
+			var script = document.createElement("script");
+			script.src = uri.href;
+
+			window[cb] = function(rsp) {
+				document.head.removeChild(script);
+				clearTimeout(timeout);
+				delete window[cb];
+
+				var data = "";
+				
+				if ( adapter ) {
+					data = adapter(rsp, pre);
+				}
+				else {
+					for ( var p in adapters ) {
+						data = adapters[p](rsp, pre);
+						if ( data !== null ) break;
+					}
+				}
+
+				if (data === null) {
+					code.textContent = "Cannot parse response (perhaps you need an adapter function?)";
+				}
+				else {
+					code.textContent = data;
+					Prism.highlightElement(code);
+				}
+			};
+
+			document.head.appendChild(script);
+		});
+	}
+
+	highlight();
+})();
\ No newline at end of file
diff --git a/plugins/jsonp-highlight/prism-jsonp-highlight.min.js b/plugins/jsonp-highlight/prism-jsonp-highlight.min.js
new file mode 100644
index 0000000..3954db5
--- /dev/null
+++ b/plugins/jsonp-highlight/prism-jsonp-highlight.min.js
@@ -0,0 +1 @@
+!function(){function t(t){"function"!=typeof t||e(t)||r.push(t)}function e(t){return"function"==typeof t?r.filter(function(e){return e.valueOf()===t.valueOf()})[0]:"string"==typeof t&&t.length>0?r.filter(function(e){return e.name===t})[0]:null}function n(t){if("string"==typeof t&&(t=e(t)),"function"==typeof t){var n=r.indexOf(t);n>=0&&r.splice(n,1)}}function a(){Array.prototype.slice.call(document.querySelectorAll("pre[data-jsonp]")).forEach(function(t){t.textContent="";var e=document.createElement("code");e.textContent=i,t.appendChild(e);var n=t.getAttribute("data-adapter"),a=null;if(n){if("function"!=typeof window[n])return e.textContent="JSONP adapter function '"+n+"' doesn't exist",void 0;a=window[n]}var u="prismjsonp"+o++,f=document.createElement("a"),l=f.href=t.getAttribute("data-jsonp");f.href+=(f.search?"&":"?")+(t.getAttribute("data-callback")||"callback")+"="+u;var s=setTimeout(function(){e.textContent===i&&(e.textContent="Timeout loading '"+l+"'")},5e3),d=document.createElement("script");d.src=f.href,window[u]=function(n){document.head.removeChild(d),clearTimeout(s),delete window[u];var o="";if(a)o=a(n,t);else for(var i in r)if(o=r[i](n,t),null!==o)break;null===o?e.textContent="Cannot parse response (perhaps you need an adapter function?)":(e.textContent=o,Prism.highlightElement(e))},document.head.appendChild(d)})}if(self.Prism&&self.document&&document.querySelectorAll){var r=[];Prism.plugins.jsonphighlight={registerAdapter:t,removeAdapter:n,highlight:a},t(function(t){if(t&&t.meta&&t.data){if(t.meta.status&&t.meta.status>=400)return"Error: "+(t.data.message||t.meta.status);if("string"==typeof t.data.content)return"function"==typeof atob?atob(t.data.content.replace(/\s/g,"")):"Your browser cannot decode base64"}return null}),t(function(t,e){if(t&&t.meta&&t.data&&t.data.files){if(t.meta.status&&t.meta.status>=400)return"Error: "+(t.data.message||t.meta.status);var n=e.getAttribute("data-filename");if(null==n)for(var a in t.data.files)if(t.data.files.hasOwnProperty(a)){n=a;break}return void 0!==t.data.files[n]?t.data.files[n].content:"Error: unknown or missing gist file "+n}return null}),t(function(t){return t&&t.node&&"string"==typeof t.data?t.data:null});var o=0,i="Loading…";a()}}();
\ No newline at end of file
diff --git a/prism.js b/prism.js
index c5b3de6..90e7df2 100644
--- a/prism.js
+++ b/prism.js
@@ -145,7 +145,8 @@ var _ = _self.Prism = {
 			}
 		}
 	},
-
+	plugins: {},
+	
 	highlightAll: function(async, callback) {
 		var elements = document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');