diff --git a/components.js b/components.js index 0a49d3c511..650111759f 100644 --- a/components.js +++ b/components.js @@ -587,7 +587,9 @@ var components = { }, "show-language": { "title": "Show Language", - "owner": "nauzilus" + "owner": "nauzilus", + "noCSS": true, + "require": "toolbar" }, "jsonp-highlight": { "title": "JSONP Highlight", @@ -659,6 +661,16 @@ var components = { "title": "Data-URI Highlight", "owner": "Golmote", "noCSS": true + }, + "toolbar": { + "title": "Toolbar", + "owner": "mAAdhaTTah" + }, + "copy-to-clipboard": { + "title": "Copy to Clipboard Button", + "owner": "mAAdhaTTah", + "require": "toolbar", + "noCSS": true } } }; diff --git a/package.json b/package.json index 4ae0326080..a51a2a96cc 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "author": "Lea Verou", "license": "MIT", "readmeFilename": "README.md", + "optionalDependencies": { + "clipboard": "^1.5.5" + }, "devDependencies": { "chai": "^2.3.0", "gulp": "^3.8.6", diff --git a/plugins/copy-to-clipboard/index.html b/plugins/copy-to-clipboard/index.html new file mode 100644 index 0000000000..abd5e57e4a --- /dev/null +++ b/plugins/copy-to-clipboard/index.html @@ -0,0 +1,48 @@ + + + + + + + Copy to Clipboard ▲ Prism plugins + + + + + + + + + + + +
+
+ +

Copy to Clipboard

+

Add a button that copies the code block to the clipboard when clicked.

+
+ +
+

How to use

+

In addition to including the plugin file with your PrismJS build, ensure Clipboard.js is loaded before the plugin.

+ +

The simplest way to include Clipboard.js is to use any of the + recommended CDNs. If you're using Browserify, Clipboard.js will be loaded auotmatically + if it's included in your package.json. + If you don't load Clipboard.js yourself, the plugin will load it from a CDN for you.

+ +

+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/copy-to-clipboard/prism-copy-to-clipboard.js b/plugins/copy-to-clipboard/prism-copy-to-clipboard.js new file mode 100644 index 0000000000..373976669b --- /dev/null +++ b/plugins/copy-to-clipboard/prism-copy-to-clipboard.js @@ -0,0 +1,75 @@ +(function(){ + if (typeof self === 'undefined' || !self.Prism || !self.document) { + return; + } + + if (!Prism.plugins.toolbar) { + console.warn('Copy to Clipboard plugin loaded before Toolbar plugin.'); + + return; + } + + var Clipboard = window.Clipboard || undefined; + + if (!Clipboard && typeof require === 'function') { + Clipboard = require('clipboard'); + } + + var callbacks = []; + + if (!Clipboard) { + var script = document.createElement('script'); + var head = document.querySelector('head'); + + script.onload = function() { + Clipboard = window.Clipboard; + + if (Clipboard) { + while (callbacks.length) { + callbacks.pop()(); + } + } + }; + + script.src = 'https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.8/clipboard.min.js'; + head.appendChild(script); + } + + Prism.plugins.toolbar.registerButton('copy-to-clipboard', function (env) { + var linkCopy = document.createElement('a'); + linkCopy.textContent = 'Copy'; + + if (!Clipboard) { + callbacks.push(registerClipboard); + } else { + registerClipboard(); + } + + return linkCopy; + + function registerClipboard() { + var clip = new Clipboard(linkCopy, { + 'text': function () { + return env.code; + } + }); + + clip.on('success', function() { + linkCopy.textContent = 'Copied!'; + + resetText(); + }); + clip.on('error', function () { + linkCopy.textContent = 'Press Ctrl+C to copy'; + + resetText(); + }); + } + + function resetText() { + setTimeout(function () { + linkCopy.textContent = 'Copy'; + }, 5000); + } + }); +})(); diff --git a/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js b/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js new file mode 100644 index 0000000000..5153ba68b1 --- /dev/null +++ b/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js @@ -0,0 +1 @@ +!function(){if("undefined"!=typeof self&&self.Prism&&self.document){if(!Prism.plugins.toolbar)return console.warn("Copy to Clipboard plugin loaded before Toolbar plugin."),void 0;var o=window.Clipboard||void 0;o||"function"!=typeof require||(o=require("clipboard"));var e=[];if(!o){var t=document.createElement("script"),n=document.querySelector("head");t.onload=function(){if(o=window.Clipboard)for(;e.length;)e.pop()()},t.src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.8/clipboard.min.js",n.appendChild(t)}Prism.plugins.toolbar.registerButton("copy-to-clipboard",function(t){function n(){var e=new o(i,{text:function(){return t.code}});e.on("success",function(){i.textContent="Copied!",r()}),e.on("error",function(){i.textContent="Press Ctrl+C to copy",r()})}function r(){setTimeout(function(){i.textContent="Copy"},5e3)}var i=document.createElement("a");return i.textContent="Copy",o?n():e.push(n),i})}}(); \ No newline at end of file diff --git a/plugins/show-language/index.html b/plugins/show-language/index.html index bbec50a8bf..95ae3a8cc0 100644 --- a/plugins/show-language/index.html +++ b/plugins/show-language/index.html @@ -8,7 +8,7 @@ - + @@ -25,7 +25,7 @@

Show Language

Examples

- +

JavaScript


 
@@ -43,6 +43,7 @@ 

SVG

+ diff --git a/plugins/show-language/prism-show-language.css b/plugins/show-language/prism-show-language.css deleted file mode 100644 index 3ada3eb98a..0000000000 --- a/plugins/show-language/prism-show-language.css +++ /dev/null @@ -1,27 +0,0 @@ -div.prism-show-language { - position: relative; -} - -div.prism-show-language > div.prism-show-language-label { - color: black; - background-color: #CFCFCF; - display: inline-block; - position: absolute; - bottom: auto; - left: auto; - top: 0; - right: 0; - width: auto; - height: auto; - font-size: 0.9em; - border-radius: 0 0 0 5px; - padding: 0 0.5em; - text-shadow: none; - z-index: 1; - box-shadow: none; - -webkit-transform: none; - -moz-transform: none; - -ms-transform: none; - -o-transform: none; - transform: none; -} diff --git a/plugins/show-language/prism-show-language.js b/plugins/show-language/prism-show-language.js index 10bdff15e2..4038ffb21b 100644 --- a/plugins/show-language/prism-show-language.js +++ b/plugins/show-language/prism-show-language.js @@ -4,35 +4,25 @@ if (typeof self === 'undefined' || !self.Prism || !self.document) { return; } +if (!Prism.plugins.toolbar) { + console.warn('Show Languages plugin loaded before Toolbar plugin.'); + + return; +} + // The languages map is built automatically with gulp var Languages = /*languages_placeholder[*/{"html":"HTML","xml":"XML","svg":"SVG","mathml":"MathML","css":"CSS","clike":"C-like","javascript":"JavaScript","abap":"ABAP","actionscript":"ActionScript","apacheconf":"Apache Configuration","apl":"APL","applescript":"AppleScript","asciidoc":"AsciiDoc","aspnet":"ASP.NET (C#)","autoit":"AutoIt","autohotkey":"AutoHotkey","basic":"BASIC","csharp":"C#","cpp":"C++","coffeescript":"CoffeeScript","css-extras":"CSS Extras","fsharp":"F#","glsl":"GLSL","graphql":"GraphQL","http":"HTTP","inform7":"Inform 7","json":"JSON","latex":"LaTeX","livescript":"LiveScript","lolcode":"LOLCODE","matlab":"MATLAB","mel":"MEL","nasm":"NASM","nginx":"nginx","nsis":"NSIS","objectivec":"Objective-C","ocaml":"OCaml","parigp":"PARI/GP","php":"PHP","php-extras":"PHP Extras","powershell":"PowerShell","properties":".properties","protobuf":"Protocol Buffers","jsx":"React JSX","rest":"reST (reStructuredText)","sas":"SAS","sass":"Sass (Sass)","scss":"Sass (Scss)","sql":"SQL","typescript":"TypeScript","vhdl":"VHDL","vim":"vim","wiki":"Wiki markup","xojo":"Xojo (REALbasic)","yaml":"YAML"}/*]*/; -Prism.hooks.add('before-highlight', function(env) { +Prism.plugins.toolbar.registerButton('show-language', function(env) { var pre = env.element.parentNode; if (!pre || !/pre/i.test(pre.nodeName)) { return; } var language = pre.getAttribute('data-language') || Languages[env.language] || (env.language.substring(0, 1).toUpperCase() + env.language.substring(1)); - /* check if the divs already exist */ - var sib = pre.previousSibling; - var div, div2; - if (sib && /\s*\bprism-show-language\b\s*/.test(sib.className) && - sib.firstChild && - /\s*\bprism-show-language-label\b\s*/.test(sib.firstChild.className)) { - div2 = sib.firstChild; - } else { - div = document.createElement('div'); - div2 = document.createElement('div'); + var element = document.createElement('span'); + element.innerHTML = language; - div2.className = 'prism-show-language-label'; - - div.className = 'prism-show-language'; - div.appendChild(div2); - - pre.parentNode.insertBefore(div, pre); - } - - div2.innerHTML = language; + return element; }); })(); diff --git a/plugins/show-language/prism-show-language.min.js b/plugins/show-language/prism-show-language.min.js index afa963ce48..14c744b77d 100644 --- a/plugins/show-language/prism-show-language.min.js +++ b/plugins/show-language/prism-show-language.min.js @@ -1 +1 @@ -!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var e={html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",css:"CSS",clike:"C-like",javascript:"JavaScript",abap:"ABAP",actionscript:"ActionScript",apacheconf:"Apache Configuration",apl:"APL",applescript:"AppleScript",asciidoc:"AsciiDoc",aspnet:"ASP.NET (C#)",autoit:"AutoIt",autohotkey:"AutoHotkey",basic:"BASIC",csharp:"C#",cpp:"C++",coffeescript:"CoffeeScript","css-extras":"CSS Extras",fsharp:"F#",glsl:"GLSL",graphql:"GraphQL",http:"HTTP",inform7:"Inform 7",json:"JSON",latex:"LaTeX",livescript:"LiveScript",lolcode:"LOLCODE",matlab:"MATLAB",mel:"MEL",nasm:"NASM",nginx:"nginx",nsis:"NSIS",objectivec:"Objective-C",ocaml:"OCaml",parigp:"PARI/GP",php:"PHP","php-extras":"PHP Extras",powershell:"PowerShell",properties:".properties",protobuf:"Protocol Buffers",jsx:"React JSX",rest:"reST (reStructuredText)",sas:"SAS",sass:"Sass (Sass)",scss:"Sass (Scss)",sql:"SQL",typescript:"TypeScript",vhdl:"VHDL",vim:"vim",wiki:"Wiki markup",xojo:"Xojo (REALbasic)",yaml:"YAML"};Prism.hooks.add("before-highlight",function(s){var a=s.element.parentNode;if(a&&/pre/i.test(a.nodeName)){var t,i,r=a.getAttribute("data-language")||e[s.language]||s.language.substring(0,1).toUpperCase()+s.language.substring(1),p=a.previousSibling;p&&/\s*\bprism-show-language\b\s*/.test(p.className)&&p.firstChild&&/\s*\bprism-show-language-label\b\s*/.test(p.firstChild.className)?i=p.firstChild:(t=document.createElement("div"),i=document.createElement("div"),i.className="prism-show-language-label",t.className="prism-show-language",t.appendChild(i),a.parentNode.insertBefore(t,a)),i.innerHTML=r}})}}(); \ No newline at end of file +!function(){if("undefined"!=typeof self&&self.Prism&&self.document){if(!Prism.plugins.toolbar)return console.warn("Show Languages plugin loaded before Toolbar plugin."),void 0;var e={html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",css:"CSS",clike:"C-like",javascript:"JavaScript",abap:"ABAP",actionscript:"ActionScript",apacheconf:"Apache Configuration",apl:"APL",applescript:"AppleScript",asciidoc:"AsciiDoc",aspnet:"ASP.NET (C#)",autoit:"AutoIt",autohotkey:"AutoHotkey",basic:"BASIC",csharp:"C#",cpp:"C++",coffeescript:"CoffeeScript","css-extras":"CSS Extras",fsharp:"F#",glsl:"GLSL",graphql:"GraphQL",http:"HTTP",inform7:"Inform 7",json:"JSON",latex:"LaTeX",livescript:"LiveScript",lolcode:"LOLCODE",matlab:"MATLAB",mel:"MEL",nasm:"NASM",nginx:"nginx",nsis:"NSIS",objectivec:"Objective-C",ocaml:"OCaml",parigp:"PARI/GP",php:"PHP","php-extras":"PHP Extras",powershell:"PowerShell",properties:".properties",protobuf:"Protocol Buffers",jsx:"React JSX",rest:"reST (reStructuredText)",sas:"SAS",sass:"Sass (Sass)",scss:"Sass (Scss)",sql:"SQL",typescript:"TypeScript",vhdl:"VHDL",vim:"vim",wiki:"Wiki markup",xojo:"Xojo (REALbasic)",yaml:"YAML"};Prism.plugins.toolbar.registerButton("show-language",function(t){var a=t.element.parentNode;if(a&&/pre/i.test(a.nodeName)){var s=a.getAttribute("data-language")||e[t.language]||t.language.substring(0,1).toUpperCase()+t.language.substring(1),r=document.createElement("span");return r.innerHTML=s,r}})}}(); \ No newline at end of file diff --git a/plugins/toolbar/index.html b/plugins/toolbar/index.html new file mode 100644 index 0000000000..64d8383098 --- /dev/null +++ b/plugins/toolbar/index.html @@ -0,0 +1,134 @@ + + + + + + + Toolbar ▲ Prism plugins + + + + + + + + + + + +
+
+ +

Toolbar

+

Attach a toolbar for plugins to easily register buttons on the top of a code block.

+
+ +
+

How to use

+

The Toolbar plugin allows for several methods to register your button, using the Prism.plugins.toolbar.registerButton function.

+ +

The simplest method is through the HTML API. Add a data-label attribute to the pre element, and the Toolbar + plugin will read the value of that attribute and append a label to the code snippet.

+ +
<pre data-src="plugins/toolbar/prism-toolbar.js" data-label="Hello World!"></pre>
+ +

If you want to provide arbitrary HTML to the label, create a template element with the HTML you want in the label, and provide the + template element's id to data-label. The Toolbar plugin will use the template's content for the button. + You can also use to declare your event handlers inline:

+ +
<pre data-src="plugins/toolbar/prism-toolbar.js" data-label="my-label-button"></pre>
+ +
<template id="my-label-button"><button onclick="console.log('This is an inline-handler');">My button</button></template>
+ +

For more flexibility, the Toolbar exposes a JavaScript function that can be used to register new buttons or labels to the Toolbar, + Prism.plugins.toolbar.registerButton.

+ +

The function accepts a key for the button and an object with a text property string and an optional + onClick function or url string. The onClick function will be called when the button is clicked, while the + url property will be set to the anchor tag's href.

+ +
Prism.plugins.toolbar.registerButton('hello-world', {
+	text: 'Hello World!', // required
+	onClick: function (env) { // optional
+		alert('This code snippet is written in ' + env.language + '.');
+	}
+});
+ +

See how the above code registers the Hello World! button? You can use this in your plugins to register your own buttons with the toolbar.

+ +

If you need more control, you can provide a function to registerButton that returns either a span, a, or + button element.

+ +
Prism.plugins.toolbar.registerButton('select-code', function() {
+	var button = document.createElement('button');
+	button.innerHTML = 'Select Code';
+
+	button.addEventListener('click', function () {
+		// Source: http://stackoverflow.com/a/11128179/2757940
+		if (document.body.createTextRange) { // ms
+			var range = document.body.createTextRange();
+			range.moveToElementText(env.element);
+			range.select();
+		} else if (window.getSelection) { // moz, opera, webkit
+			var selection = window.getSelection();
+			var range = document.createRange();
+			range.selectNodeContents(env.element);
+			selection.removeAllRanges();
+			selection.addRange(range);
+		}
+	});
+
+	return button;
+});
+ +

The above function creates the Select Code button you see, and when you click it, the code gets highlighted.

+ +

By default, the buttons will be added to the code snippet in the order they were registered. If more control over + the order is needed, an HTML attribute can be added to the body tag with a comma-separated string indicating the + order.

+ +
<body data-toolbar-order="select-code,hello-world,label">
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/toolbar/prism-toolbar.css b/plugins/toolbar/prism-toolbar.css new file mode 100644 index 0000000000..6d1afb9dfe --- /dev/null +++ b/plugins/toolbar/prism-toolbar.css @@ -0,0 +1,58 @@ +pre.code-toolbar { + position: relative; +} + +pre.code-toolbar > .toolbar { + position: absolute; + top: .3em; + right: .2em; + transition: opacity 0.3s ease-in-out; + opacity: 0; +} + +pre.code-toolbar:hover > .toolbar { + opacity: 1; +} + +pre.code-toolbar > .toolbar .toolbar-item { + display: inline-block; +} + +pre.code-toolbar > .toolbar a { + cursor: pointer; +} + +pre.code-toolbar > .toolbar button { + background: none; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + padding: 0; + -webkit-user-select: none; /* for button */ + -moz-user-select: none; + -ms-user-select: none; +} + +pre.code-toolbar > .toolbar a, +pre.code-toolbar > .toolbar button, +pre.code-toolbar > .toolbar span { + color: #bbb; + font-size: .8em; + padding: 0 .5em; + background: #f5f2f0; + background: rgba(224, 224, 224, 0.2); + box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); + border-radius: .5em; +} + +pre.code-toolbar > .toolbar a:hover, +pre.code-toolbar > .toolbar a:focus, +pre.code-toolbar > .toolbar button:hover, +pre.code-toolbar > .toolbar button:focus, +pre.code-toolbar > .toolbar span:hover, +pre.code-toolbar > .toolbar span:focus { + color: inherit; + text-decoration: none; +} diff --git a/plugins/toolbar/prism-toolbar.js b/plugins/toolbar/prism-toolbar.js new file mode 100644 index 0000000000..f4cd28ef08 --- /dev/null +++ b/plugins/toolbar/prism-toolbar.js @@ -0,0 +1,133 @@ +(function(){ + if (typeof self === 'undefined' || !self.Prism || !self.document) { + return; + } + + var callbacks = []; + var map = {}; + var noop = function() {}; + + Prism.plugins.toolbar = {}; + + /** + * Register a button callback with the toolbar. + * + * @param {string} key + * @param {Object|Function} opts + */ + var registerButton = Prism.plugins.toolbar.registerButton = function (key, opts) { + var callback; + + if (typeof opts === 'function') { + callback = opts; + } else { + callback = function (env) { + var element; + + if (typeof opts.onClick === 'function') { + element = document.createElement('button'); + element.type = 'button'; + element.addEventListener('click', function () { + opts.onClick.call(this, env); + }); + } else if (typeof opts.url === 'string') { + element = document.createElement('a'); + element.href = opts.url; + } else { + element = document.createElement('span'); + } + + element.textContent = opts.text; + + return element; + }; + } + + callbacks.push(map[key] = callback); + }; + + /** + * Post-highlight Prism hook callback. + * + * @param env + */ + var hook = Prism.plugins.toolbar.hook = function (env) { + // Check if inline or actual code block (credit to line-numbers plugin) + var pre = env.element.parentNode; + if (!pre || !/pre/i.test(pre.nodeName)) { + return; + } + + // Autoloader rehighlights, so only do this once. + if (pre.classList.contains('code-toolbar')) { + return; + } + + pre.classList.add('code-toolbar'); + + // Setup the toolbar + var toolbar = document.createElement('div'); + toolbar.classList.add('toolbar'); + + if (document.body.hasAttribute('data-toolbar-order')) { + callbacks = document.body.getAttribute('data-toolbar-order').split(',').map(function(key) { + return map[key] || noop; + }); + } + + callbacks.forEach(function(callback) { + var element = callback(env); + + if (!element) { + return; + } + + var item = document.createElement('div'); + item.classList.add('toolbar-item'); + + item.appendChild(element); + toolbar.appendChild(item); + }); + + // Add our toolbar to the
 tag
+		pre.appendChild(toolbar);
+	};
+
+	registerButton('label', function(env) {
+		var pre = env.element.parentNode;
+		if (!pre || !/pre/i.test(pre.nodeName)) {
+			return;
+		}
+
+		if (!pre.hasAttribute('data-label')) {
+			return;
+		}
+
+		var element, template;
+		var text = pre.getAttribute('data-label');
+		try {
+			// Any normal text will blow up this selector.
+			template = document.querySelector('template#' + text);
+		} catch (e) {}
+
+		if (template) {
+			element = template.content;
+		} else {
+			if (pre.hasAttribute('data-url')) {
+				element = document.createElement('a');
+				element.href = pre.getAttribute('data-url');
+			} else {
+				element = document.createElement('span');
+			}
+
+			element.innerHTML = text;
+		}
+
+		return element;
+	});
+
+	/**
+	 * Register the toolbar with Prism.
+	 */
+	Prism.hooks.add('complete', hook);
+})();
diff --git a/plugins/toolbar/prism-toolbar.min.js b/plugins/toolbar/prism-toolbar.min.js
new file mode 100644
index 0000000000..c069c1ebec
--- /dev/null
+++ b/plugins/toolbar/prism-toolbar.min.js
@@ -0,0 +1 @@
+!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var t=[],e={},n=function(){};Prism.plugins.toolbar={};var a=Prism.plugins.toolbar.registerButton=function(n,a){var o;o="function"==typeof a?a:function(t){var e;return"function"==typeof a.onClick?(e=document.createElement("button"),e.type="button",e.addEventListener("click",function(){a.onClick.call(this,t)})):"string"==typeof a.url?(e=document.createElement("a"),e.href=a.url):e=document.createElement("span"),e.textContent=a.text,e},t.push(e[n]=o)},o=Prism.plugins.toolbar.hook=function(a){var o=a.element.parentNode;if(o&&/pre/i.test(o.nodeName)){o.classList.add("code-toolbar");var r=document.createElement("div");r.classList.add("toolbar"),document.body.hasAttribute("data-toolbar-order")&&(t=document.body.getAttribute("data-toolbar-order").split(",").map(function(t){return e[t]||n})),t.forEach(function(t){var e=t(a);if(e){var n=document.createElement("div");n.classList.add("toolbar-item"),n.appendChild(e),r.appendChild(n)}}),o.appendChild(r)}};a("label",function(t){var e=t.element.parentNode;if(e&&/pre/i.test(e.nodeName)&&e.hasAttribute("data-label")){var n,a=e.getAttribute("data-label"),o=document.querySelector("template#"+a);return o?n=o.content:(e.hasAttribute("data-url")?(n=document.createElement("a"),n.href=e.getAttribute("data-url")):n=document.createElement("span"),n.innerHTML=a),n}}),Prism.hooks.add("complete",o)}}();
\ No newline at end of file