sinatra-zero_clipboard 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (c) 2013, Oliver Feldt <oliver.feldt@googlemail.com>
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ [![Gem Version](https://badge.fury.io/rb/sinatra-zero_clipboard.png)](http://badge.fury.io/rb/sinatra-zero_clipboard)
2
+ # Sinatra::ZeroClipboard
3
+
4
+ **Sinatra::ZeroClipboard** is a sinatra plugin to access [ZeroClipboard](https://github.com/jonrohan/ZeroClipboard),
5
+ a **Flash-based cross-browser clipboard library**. Accessing the clipboard from pure Javascript is still disabled
6
+ on most browser due to security concerns, but sometimes needed in a project to improve user experience.
7
+ This gem should mitigate this nuisance until better options are broadly available.
8
+
9
+ ## Requirements:
10
+
11
+ server-side:
12
+ * sinatra >= 1.4.2
13
+
14
+ client-side:
15
+ * Javascript & Flash-enabled Browser
16
+
17
+ ## Usage:
18
+
19
+ #### Add gem to your Gemfile
20
+ gem "sinatra-zero_clipboard"
21
+
22
+ #### Require the gem
23
+ ``` ruby
24
+ require 'sinatra/zero_clipboard'
25
+ ```
26
+
27
+ #### Register helper for asset routes
28
+ ``` ruby
29
+ class SampleApplication < Sinatra::Base
30
+ # ...
31
+ register Sinatra::ZeroClipboard::Assets
32
+ # ...
33
+ end
34
+ ```
35
+
36
+ #### Add asset links to HTML head
37
+ ``` haml
38
+ %html
39
+ %head
40
+ = zero_clipboard_assets
41
+ ```
42
+
43
+ #### Add a button
44
+ ``` haml
45
+ %button{ id: "clip_button", data-clipboard-text: "Default text", data-clipboard-target: "text_to_copy" }
46
+ %span Copy to Clipboard
47
+ ```
48
+
49
+ #### Add a target
50
+ ``` haml
51
+ %textarea{ id: "text_to_copy", rows: "3" cols: "40"} I'm getting copied, Yeah!
52
+ ```
53
+
54
+ #### Add ZeroClipboard Javascript binding
55
+ ``` javascript
56
+ var clip = new ZeroClipboard(document.getElementById("clip_button"), {
57
+ moviePath: "swf/ZeroClipboard.swf"
58
+ });
59
+ ```
60
+
61
+ ## More Information
62
+ For more ZeroClipboard Javascript options look [here](https://github.com/jonrohan/ZeroClipboard)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,365 @@
1
+ /*!
2
+ * zeroclipboard
3
+ * The Zero Clipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie, and a JavaScript interface.
4
+ * Copyright 2012 Jon Rohan, James M. Greene, .
5
+ * Released under the MIT license
6
+ * http://jonrohan.github.com/ZeroClipboard/
7
+ * v1.1.7
8
+ */(function() {
9
+ "use strict";
10
+ var _getStyle = function(el, prop) {
11
+ var y = el.style[prop];
12
+ if (el.currentStyle) y = el.currentStyle[prop]; else if (window.getComputedStyle) y = document.defaultView.getComputedStyle(el, null).getPropertyValue(prop);
13
+ if (y == "auto" && prop == "cursor") {
14
+ var possiblePointers = [ "a" ];
15
+ for (var i = 0; i < possiblePointers.length; i++) {
16
+ if (el.tagName.toLowerCase() == possiblePointers[i]) {
17
+ return "pointer";
18
+ }
19
+ }
20
+ }
21
+ return y;
22
+ };
23
+ var _elementMouseOver = function(event) {
24
+ if (!ZeroClipboard.prototype._singleton) return;
25
+ if (!event) {
26
+ event = window.event;
27
+ }
28
+ var target;
29
+ if (this !== window) {
30
+ target = this;
31
+ } else if (event.target) {
32
+ target = event.target;
33
+ } else if (event.srcElement) {
34
+ target = event.srcElement;
35
+ }
36
+ ZeroClipboard.prototype._singleton.setCurrent(target);
37
+ };
38
+ var _addEventHandler = function(element, method, func) {
39
+ if (element.addEventListener) {
40
+ element.addEventListener(method, func, false);
41
+ } else if (element.attachEvent) {
42
+ element.attachEvent("on" + method, func);
43
+ }
44
+ };
45
+ var _removeEventHandler = function(element, method, func) {
46
+ if (element.removeEventListener) {
47
+ element.removeEventListener(method, func, false);
48
+ } else if (element.detachEvent) {
49
+ element.detachEvent("on" + method, func);
50
+ }
51
+ };
52
+ var _addClass = function(element, value) {
53
+ if (element.addClass) {
54
+ element.addClass(value);
55
+ return element;
56
+ }
57
+ if (value && typeof value === "string") {
58
+ var classNames = (value || "").split(/\s+/);
59
+ if (element.nodeType === 1) {
60
+ if (!element.className) {
61
+ element.className = value;
62
+ } else {
63
+ var className = " " + element.className + " ", setClass = element.className;
64
+ for (var c = 0, cl = classNames.length; c < cl; c++) {
65
+ if (className.indexOf(" " + classNames[c] + " ") < 0) {
66
+ setClass += " " + classNames[c];
67
+ }
68
+ }
69
+ element.className = setClass.replace(/^\s+|\s+$/g, "");
70
+ }
71
+ }
72
+ }
73
+ return element;
74
+ };
75
+ var _removeClass = function(element, value) {
76
+ if (element.removeClass) {
77
+ element.removeClass(value);
78
+ return element;
79
+ }
80
+ if (value && typeof value === "string" || value === undefined) {
81
+ var classNames = (value || "").split(/\s+/);
82
+ if (element.nodeType === 1 && element.className) {
83
+ if (value) {
84
+ var className = (" " + element.className + " ").replace(/[\n\t]/g, " ");
85
+ for (var c = 0, cl = classNames.length; c < cl; c++) {
86
+ className = className.replace(" " + classNames[c] + " ", " ");
87
+ }
88
+ element.className = className.replace(/^\s+|\s+$/g, "");
89
+ } else {
90
+ element.className = "";
91
+ }
92
+ }
93
+ }
94
+ return element;
95
+ };
96
+ var _getDOMObjectPosition = function(obj) {
97
+ var info = {
98
+ left: 0,
99
+ top: 0,
100
+ width: obj.width || obj.offsetWidth || 0,
101
+ height: obj.height || obj.offsetHeight || 0,
102
+ zIndex: 9999
103
+ };
104
+ var zi = _getStyle(obj, "zIndex");
105
+ if (zi && zi != "auto") {
106
+ info.zIndex = parseInt(zi, 10);
107
+ }
108
+ while (obj) {
109
+ var borderLeftWidth = parseInt(_getStyle(obj, "borderLeftWidth"), 10);
110
+ var borderTopWidth = parseInt(_getStyle(obj, "borderTopWidth"), 10);
111
+ info.left += isNaN(obj.offsetLeft) ? 0 : obj.offsetLeft;
112
+ info.left += isNaN(borderLeftWidth) ? 0 : borderLeftWidth;
113
+ info.top += isNaN(obj.offsetTop) ? 0 : obj.offsetTop;
114
+ info.top += isNaN(borderTopWidth) ? 0 : borderTopWidth;
115
+ obj = obj.offsetParent;
116
+ }
117
+ return info;
118
+ };
119
+ var _noCache = function(path) {
120
+ var client = ZeroClipboard.prototype._singleton;
121
+ if (client.options.useNoCache) {
122
+ return (path.indexOf("?") >= 0 ? "&nocache=" : "?nocache=") + (new Date).getTime();
123
+ } else {
124
+ return "";
125
+ }
126
+ };
127
+ var _vars = function(options) {
128
+ var str = [];
129
+ if (options.trustedDomains) {
130
+ if (typeof options.trustedDomains === "string") {
131
+ str.push("trustedDomain=" + options.trustedDomains);
132
+ } else {
133
+ str.push("trustedDomain=" + options.trustedDomains.join(","));
134
+ }
135
+ }
136
+ return str.join("&");
137
+ };
138
+ var _inArray = function(elem, array) {
139
+ if (array.indexOf) {
140
+ return array.indexOf(elem);
141
+ }
142
+ for (var i = 0, length = array.length; i < length; i++) {
143
+ if (array[i] === elem) {
144
+ return i;
145
+ }
146
+ }
147
+ return -1;
148
+ };
149
+ var _prepGlue = function(elements) {
150
+ if (typeof elements === "string") throw new TypeError("ZeroClipboard doesn't accept query strings.");
151
+ if (!elements.length) return [ elements ];
152
+ return elements;
153
+ };
154
+ var ZeroClipboard = function(elements, options) {
155
+ if (elements) (ZeroClipboard.prototype._singleton || this).glue(elements);
156
+ if (ZeroClipboard.prototype._singleton) return ZeroClipboard.prototype._singleton;
157
+ ZeroClipboard.prototype._singleton = this;
158
+ this.options = {};
159
+ for (var kd in _defaults) this.options[kd] = _defaults[kd];
160
+ for (var ko in options) this.options[ko] = options[ko];
161
+ this.handlers = {};
162
+ if (ZeroClipboard.detectFlashSupport()) _bridge();
163
+ };
164
+ var currentElement, gluedElements = [];
165
+ ZeroClipboard.prototype.setCurrent = function(element) {
166
+ currentElement = element;
167
+ this.reposition();
168
+ if (element.getAttribute("title")) {
169
+ this.setTitle(element.getAttribute("title"));
170
+ }
171
+ this.setHandCursor(_getStyle(element, "cursor") == "pointer");
172
+ };
173
+ ZeroClipboard.prototype.setText = function(newText) {
174
+ if (newText && newText !== "") {
175
+ this.options.text = newText;
176
+ if (this.ready()) this.flashBridge.setText(newText);
177
+ }
178
+ };
179
+ ZeroClipboard.prototype.setTitle = function(newTitle) {
180
+ if (newTitle && newTitle !== "") this.htmlBridge.setAttribute("title", newTitle);
181
+ };
182
+ ZeroClipboard.prototype.setSize = function(width, height) {
183
+ if (this.ready()) this.flashBridge.setSize(width, height);
184
+ };
185
+ ZeroClipboard.prototype.setHandCursor = function(enabled) {
186
+ if (this.ready()) this.flashBridge.setHandCursor(enabled);
187
+ };
188
+ ZeroClipboard.version = "1.1.7";
189
+ var _defaults = {
190
+ moviePath: "ZeroClipboard.swf",
191
+ trustedDomains: null,
192
+ text: null,
193
+ hoverClass: "zeroclipboard-is-hover",
194
+ activeClass: "zeroclipboard-is-active",
195
+ allowScriptAccess: "sameDomain",
196
+ useNoCache: true
197
+ };
198
+ ZeroClipboard.setDefaults = function(options) {
199
+ for (var ko in options) _defaults[ko] = options[ko];
200
+ };
201
+ ZeroClipboard.destroy = function() {
202
+ ZeroClipboard.prototype._singleton.unglue(gluedElements);
203
+ var bridge = ZeroClipboard.prototype._singleton.htmlBridge;
204
+ bridge.parentNode.removeChild(bridge);
205
+ delete ZeroClipboard.prototype._singleton;
206
+ };
207
+ ZeroClipboard.detectFlashSupport = function() {
208
+ var hasFlash = false;
209
+ try {
210
+ if (new ActiveXObject("ShockwaveFlash.ShockwaveFlash")) {
211
+ hasFlash = true;
212
+ }
213
+ } catch (error) {
214
+ if (navigator.mimeTypes["application/x-shockwave-flash"]) {
215
+ hasFlash = true;
216
+ }
217
+ }
218
+ return hasFlash;
219
+ };
220
+ var _bridge = function() {
221
+ var client = ZeroClipboard.prototype._singleton;
222
+ var container = document.getElementById("global-zeroclipboard-html-bridge");
223
+ if (!container) {
224
+ var html = ' <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="global-zeroclipboard-flash-bridge" width="100%" height="100%"> <param name="movie" value="' + client.options.moviePath + _noCache(client.options.moviePath) + '"/> <param name="allowScriptAccess" value="' + client.options.allowScriptAccess + '"/> <param name="scale" value="exactfit"/> <param name="loop" value="false"/> <param name="menu" value="false"/> <param name="quality" value="best" /> <param name="bgcolor" value="#ffffff"/> <param name="wmode" value="transparent"/> <param name="flashvars" value="' + _vars(client.options) + '"/> <embed src="' + client.options.moviePath + _noCache(client.options.moviePath) + '" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="100%" height="100%" name="global-zeroclipboard-flash-bridge" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" wmode="transparent" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="' + _vars(client.options) + '" scale="exactfit"> </embed> </object>';
225
+ container = document.createElement("div");
226
+ container.id = "global-zeroclipboard-html-bridge";
227
+ container.setAttribute("class", "global-zeroclipboard-container");
228
+ container.setAttribute("data-clipboard-ready", false);
229
+ container.style.position = "absolute";
230
+ container.style.left = "-9999px";
231
+ container.style.top = "-9999px";
232
+ container.style.width = "15px";
233
+ container.style.height = "15px";
234
+ container.style.zIndex = "9999";
235
+ container.innerHTML = html;
236
+ document.body.appendChild(container);
237
+ }
238
+ client.htmlBridge = container;
239
+ client.flashBridge = document["global-zeroclipboard-flash-bridge"] || container.children[0].lastElementChild;
240
+ };
241
+ ZeroClipboard.prototype.resetBridge = function() {
242
+ this.htmlBridge.style.left = "-9999px";
243
+ this.htmlBridge.style.top = "-9999px";
244
+ this.htmlBridge.removeAttribute("title");
245
+ this.htmlBridge.removeAttribute("data-clipboard-text");
246
+ _removeClass(currentElement, this.options.activeClass);
247
+ currentElement = null;
248
+ this.options.text = null;
249
+ };
250
+ ZeroClipboard.prototype.ready = function() {
251
+ var ready = this.htmlBridge.getAttribute("data-clipboard-ready");
252
+ return ready === "true" || ready === true;
253
+ };
254
+ ZeroClipboard.prototype.reposition = function() {
255
+ if (!currentElement) return false;
256
+ var pos = _getDOMObjectPosition(currentElement);
257
+ this.htmlBridge.style.top = pos.top + "px";
258
+ this.htmlBridge.style.left = pos.left + "px";
259
+ this.htmlBridge.style.width = pos.width + "px";
260
+ this.htmlBridge.style.height = pos.height + "px";
261
+ this.htmlBridge.style.zIndex = pos.zIndex + 1;
262
+ this.setSize(pos.width, pos.height);
263
+ };
264
+ ZeroClipboard.dispatch = function(eventName, args) {
265
+ ZeroClipboard.prototype._singleton.receiveEvent(eventName, args);
266
+ };
267
+ ZeroClipboard.prototype.on = function(eventName, func) {
268
+ var events = eventName.toString().split(/\s/g);
269
+ for (var i = 0; i < events.length; i++) {
270
+ eventName = events[i].toLowerCase().replace(/^on/, "");
271
+ if (!this.handlers[eventName]) this.handlers[eventName] = func;
272
+ }
273
+ if (this.handlers.noflash && !ZeroClipboard.detectFlashSupport()) {
274
+ this.receiveEvent("onNoFlash", null);
275
+ }
276
+ };
277
+ ZeroClipboard.prototype.addEventListener = ZeroClipboard.prototype.on;
278
+ ZeroClipboard.prototype.off = function(eventName, func) {
279
+ var events = eventName.toString().split(/\s/g);
280
+ for (var i = 0; i < events.length; i++) {
281
+ eventName = events[i].toLowerCase().replace(/^on/, "");
282
+ for (var event in this.handlers) {
283
+ if (event === eventName && this.handlers[event] === func) {
284
+ delete this.handlers[event];
285
+ }
286
+ }
287
+ }
288
+ };
289
+ ZeroClipboard.prototype.removeEventListener = ZeroClipboard.prototype.off;
290
+ ZeroClipboard.prototype.receiveEvent = function(eventName, args) {
291
+ eventName = eventName.toString().toLowerCase().replace(/^on/, "");
292
+ var element = currentElement;
293
+ switch (eventName) {
294
+ case "load":
295
+ if (args && parseFloat(args.flashVersion.replace(",", ".").replace(/[^0-9\.]/gi, "")) < 10) {
296
+ this.receiveEvent("onWrongFlash", {
297
+ flashVersion: args.flashVersion
298
+ });
299
+ return;
300
+ }
301
+ this.htmlBridge.setAttribute("data-clipboard-ready", true);
302
+ break;
303
+ case "mouseover":
304
+ _addClass(element, this.options.hoverClass);
305
+ break;
306
+ case "mouseout":
307
+ _removeClass(element, this.options.hoverClass);
308
+ this.resetBridge();
309
+ break;
310
+ case "mousedown":
311
+ _addClass(element, this.options.activeClass);
312
+ break;
313
+ case "mouseup":
314
+ _removeClass(element, this.options.activeClass);
315
+ break;
316
+ case "datarequested":
317
+ var targetId = element.getAttribute("data-clipboard-target"), targetEl = !targetId ? null : document.getElementById(targetId);
318
+ if (targetEl) {
319
+ var textContent = targetEl.value || targetEl.textContent || targetEl.innerText;
320
+ if (textContent) this.setText(textContent);
321
+ } else {
322
+ var defaultText = element.getAttribute("data-clipboard-text");
323
+ if (defaultText) this.setText(defaultText);
324
+ }
325
+ break;
326
+ case "complete":
327
+ this.options.text = null;
328
+ break;
329
+ }
330
+ if (this.handlers[eventName]) {
331
+ var func = this.handlers[eventName];
332
+ if (typeof func == "function") {
333
+ func.call(element, this, args);
334
+ } else if (typeof func == "string") {
335
+ window[func].call(element, this, args);
336
+ }
337
+ }
338
+ };
339
+ ZeroClipboard.prototype.glue = function(elements) {
340
+ elements = _prepGlue(elements);
341
+ for (var i = 0; i < elements.length; i++) {
342
+ if (_inArray(elements[i], gluedElements) == -1) {
343
+ gluedElements.push(elements[i]);
344
+ _addEventHandler(elements[i], "mouseover", _elementMouseOver);
345
+ }
346
+ }
347
+ };
348
+ ZeroClipboard.prototype.unglue = function(elements) {
349
+ elements = _prepGlue(elements);
350
+ for (var i = 0; i < elements.length; i++) {
351
+ _removeEventHandler(elements[i], "mouseover", _elementMouseOver);
352
+ var arrayIndex = _inArray(elements[i], gluedElements);
353
+ if (arrayIndex != -1) gluedElements.splice(arrayIndex, 1);
354
+ }
355
+ };
356
+ if (typeof module !== "undefined") {
357
+ module.exports = ZeroClipboard;
358
+ } else if (typeof define === "function" && define.amd) {
359
+ define(function() {
360
+ return ZeroClipboard;
361
+ });
362
+ } else {
363
+ window.ZeroClipboard = ZeroClipboard;
364
+ }
365
+ })();
@@ -0,0 +1,8 @@
1
+ /*!
2
+ * zeroclipboard
3
+ * The Zero Clipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie, and a JavaScript interface.
4
+ * Copyright 2012 Jon Rohan, James M. Greene, .
5
+ * Released under the MIT license
6
+ * http://jonrohan.github.com/ZeroClipboard/
7
+ * v1.1.7
8
+ */(function(){"use strict";var a=function(a,b){var c=a.style[b];a.currentStyle?c=a.currentStyle[b]:window.getComputedStyle&&(c=document.defaultView.getComputedStyle(a,null).getPropertyValue(b));if(c=="auto"&&b=="cursor"){var d=["a"];for(var e=0;e<d.length;e++)if(a.tagName.toLowerCase()==d[e])return"pointer"}return c},b=function(a){if(!l.prototype._singleton)return;a||(a=window.event);var b;this!==window?b=this:a.target?b=a.target:a.srcElement&&(b=a.srcElement),l.prototype._singleton.setCurrent(b)},c=function(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c)},d=function(a,b,c){a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent&&a.detachEvent("on"+b,c)},e=function(a,b){if(a.addClass)return a.addClass(b),a;if(b&&typeof b=="string"){var c=(b||"").split(/\s+/);if(a.nodeType===1)if(!a.className)a.className=b;else{var d=" "+a.className+" ",e=a.className;for(var f=0,g=c.length;f<g;f++)d.indexOf(" "+c[f]+" ")<0&&(e+=" "+c[f]);a.className=e.replace(/^\s+|\s+$/g,"")}}return a},f=function(a,b){if(a.removeClass)return a.removeClass(b),a;if(b&&typeof b=="string"||b===undefined){var c=(b||"").split(/\s+/);if(a.nodeType===1&&a.className)if(b){var d=(" "+a.className+" ").replace(/[\n\t]/g," ");for(var e=0,f=c.length;e<f;e++)d=d.replace(" "+c[e]+" "," ");a.className=d.replace(/^\s+|\s+$/g,"")}else a.className=""}return a},g=function(b){var c={left:0,top:0,width:b.width||b.offsetWidth||0,height:b.height||b.offsetHeight||0,zIndex:9999},d=a(b,"zIndex");d&&d!="auto"&&(c.zIndex=parseInt(d,10));while(b){var e=parseInt(a(b,"borderLeftWidth"),10),f=parseInt(a(b,"borderTopWidth"),10);c.left+=isNaN(b.offsetLeft)?0:b.offsetLeft,c.left+=isNaN(e)?0:e,c.top+=isNaN(b.offsetTop)?0:b.offsetTop,c.top+=isNaN(f)?0:f,b=b.offsetParent}return c},h=function(a){var b=l.prototype._singleton;return b.options.useNoCache?(a.indexOf("?")>=0?"&nocache=":"?nocache=")+(new Date).getTime():""},i=function(a){var b=[];return a.trustedDomains&&(typeof a.trustedDomains=="string"?b.push("trustedDomain="+a.trustedDomains):b.push("trustedDomain="+a.trustedDomains.join(","))),b.join("&")},j=function(a,b){if(b.indexOf)return b.indexOf(a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},k=function(a){if(typeof a=="string")throw new TypeError("ZeroClipboard doesn't accept query strings.");return a.length?a:[a]},l=function(a,b){a&&(l.prototype._singleton||this).glue(a);if(l.prototype._singleton)return l.prototype._singleton;l.prototype._singleton=this,this.options={};for(var c in o)this.options[c]=o[c];for(var d in b)this.options[d]=b[d];this.handlers={},l.detectFlashSupport()&&p()},m,n=[];l.prototype.setCurrent=function(b){m=b,this.reposition(),b.getAttribute("title")&&this.setTitle(b.getAttribute("title")),this.setHandCursor(a(b,"cursor")=="pointer")},l.prototype.setText=function(a){a&&a!==""&&(this.options.text=a,this.ready()&&this.flashBridge.setText(a))},l.prototype.setTitle=function(a){a&&a!==""&&this.htmlBridge.setAttribute("title",a)},l.prototype.setSize=function(a,b){this.ready()&&this.flashBridge.setSize(a,b)},l.prototype.setHandCursor=function(a){this.ready()&&this.flashBridge.setHandCursor(a)},l.version="1.1.7";var o={moviePath:"ZeroClipboard.swf",trustedDomains:null,text:null,hoverClass:"zeroclipboard-is-hover",activeClass:"zeroclipboard-is-active",allowScriptAccess:"sameDomain",useNoCache:!0};l.setDefaults=function(a){for(var b in a)o[b]=a[b]},l.destroy=function(){l.prototype._singleton.unglue(n);var a=l.prototype._singleton.htmlBridge;a.parentNode.removeChild(a),delete l.prototype._singleton},l.detectFlashSupport=function(){var a=!1;try{new ActiveXObject("ShockwaveFlash.ShockwaveFlash")&&(a=!0)}catch(b){navigator.mimeTypes["application/x-shockwave-flash"]&&(a=!0)}return a};var p=function(){var a=l.prototype._singleton,b=document.getElementById("global-zeroclipboard-html-bridge");if(!b){var c=' <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="global-zeroclipboard-flash-bridge" width="100%" height="100%"> <param name="movie" value="'+a.options.moviePath+h(a.options.moviePath)+'"/> <param name="allowScriptAccess" value="'+a.options.allowScriptAccess+'"/> <param name="scale" value="exactfit"/> <param name="loop" value="false"/> <param name="menu" value="false"/> <param name="quality" value="best" /> <param name="bgcolor" value="#ffffff"/> <param name="wmode" value="transparent"/> <param name="flashvars" value="'+i(a.options)+'"/> <embed src="'+a.options.moviePath+h(a.options.moviePath)+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="100%" height="100%" name="global-zeroclipboard-flash-bridge" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" wmode="transparent" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+i(a.options)+'" scale="exactfit"> </embed> </object>';b=document.createElement("div"),b.id="global-zeroclipboard-html-bridge",b.setAttribute("class","global-zeroclipboard-container"),b.setAttribute("data-clipboard-ready",!1),b.style.position="absolute",b.style.left="-9999px",b.style.top="-9999px",b.style.width="15px",b.style.height="15px",b.style.zIndex="9999",b.innerHTML=c,document.body.appendChild(b)}a.htmlBridge=b,a.flashBridge=document["global-zeroclipboard-flash-bridge"]||b.children[0].lastElementChild};l.prototype.resetBridge=function(){this.htmlBridge.style.left="-9999px",this.htmlBridge.style.top="-9999px",this.htmlBridge.removeAttribute("title"),this.htmlBridge.removeAttribute("data-clipboard-text"),f(m,this.options.activeClass),m=null,this.options.text=null},l.prototype.ready=function(){var a=this.htmlBridge.getAttribute("data-clipboard-ready");return a==="true"||a===!0},l.prototype.reposition=function(){if(!m)return!1;var a=g(m);this.htmlBridge.style.top=a.top+"px",this.htmlBridge.style.left=a.left+"px",this.htmlBridge.style.width=a.width+"px",this.htmlBridge.style.height=a.height+"px",this.htmlBridge.style.zIndex=a.zIndex+1,this.setSize(a.width,a.height)},l.dispatch=function(a,b){l.prototype._singleton.receiveEvent(a,b)},l.prototype.on=function(a,b){var c=a.toString().split(/\s/g);for(var d=0;d<c.length;d++)a=c[d].toLowerCase().replace(/^on/,""),this.handlers[a]||(this.handlers[a]=b);this.handlers.noflash&&!l.detectFlashSupport()&&this.receiveEvent("onNoFlash",null)},l.prototype.addEventListener=l.prototype.on,l.prototype.off=function(a,b){var c=a.toString().split(/\s/g);for(var d=0;d<c.length;d++){a=c[d].toLowerCase().replace(/^on/,"");for(var e in this.handlers)e===a&&this.handlers[e]===b&&delete this.handlers[e]}},l.prototype.removeEventListener=l.prototype.off,l.prototype.receiveEvent=function(a,b){a=a.toString().toLowerCase().replace(/^on/,"");var c=m;switch(a){case"load":if(b&&parseFloat(b.flashVersion.replace(",",".").replace(/[^0-9\.]/gi,""))<10){this.receiveEvent("onWrongFlash",{flashVersion:b.flashVersion});return}this.htmlBridge.setAttribute("data-clipboard-ready",!0);break;case"mouseover":e(c,this.options.hoverClass);break;case"mouseout":f(c,this.options.hoverClass),this.resetBridge();break;case"mousedown":e(c,this.options.activeClass);break;case"mouseup":f(c,this.options.activeClass);break;case"datarequested":var d=c.getAttribute("data-clipboard-target"),g=d?document.getElementById(d):null;if(g){var h=g.value||g.textContent||g.innerText;h&&this.setText(h)}else{var i=c.getAttribute("data-clipboard-text");i&&this.setText(i)}break;case"complete":this.options.text=null}if(this.handlers[a]){var j=this.handlers[a];typeof j=="function"?j.call(c,this,b):typeof j=="string"&&window[j].call(c,this,b)}},l.prototype.glue=function(a){a=k(a);for(var d=0;d<a.length;d++)j(a[d],n)==-1&&(n.push(a[d]),c(a[d],"mouseover",b))},l.prototype.unglue=function(a){a=k(a);for(var c=0;c<a.length;c++){d(a[c],"mouseover",b);var e=j(a[c],n);e!=-1&&n.splice(e,1)}},typeof module!="undefined"?module.exports=l:typeof define=="function"&&define.amd?define(function(){return l}):window.ZeroClipboard=l})();
@@ -0,0 +1,79 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/zero_clipboard'
3
+ require 'slim'
4
+
5
+ class SampleApplication < Sinatra::Base
6
+ # register our helper to setup neccessary asset routes
7
+ register Sinatra::ZeroClipboard::Assets
8
+
9
+ use Rack::CommonLogger
10
+
11
+ enable :inline_templates
12
+
13
+ get '/' do
14
+ slim :index
15
+ end
16
+
17
+ end
18
+
19
+ SampleApplication.run!
20
+
21
+ __END__
22
+ @@ layout
23
+ doctype html
24
+ html
25
+ head
26
+ -# use helper to include links in our HTML
27
+ == zero_clipboard_assets
28
+ body
29
+ == yield
30
+
31
+ @@ index
32
+ -# Add a button
33
+ -#
34
+ -# date-clipboard-text is the default text which would be copied if the target was missing
35
+ -# data-clipboard-target is the target element from which we copy our text
36
+ -#
37
+ button id="clip_button" data-clipboard-text="Default clipboard text" data-clipboard-target="text_to_copy" title="Click me to copy to clipboard."
38
+ span Copy to Clipboard
39
+
40
+ .label
41
+ label for="text_to_copy" Change copy text here
42
+ -# Add a Target
43
+ -#
44
+ -# set the id
45
+ textarea id="text_to_copy" rows="3" cols="40" Copy me!
46
+
47
+ .label
48
+ label for="testarea" Paste text here for comparison
49
+ textarea id="testarea" rows="3" cols="40"
50
+
51
+ #debug_output
52
+
53
+ javascript:
54
+ // Add ZeroClipboard Javascript binding
55
+ //
56
+ // You'll find the needed flash movie under /swf/ZeroClipboard.swf
57
+ //
58
+ // For more ZeroClipboard documentation go to: https://github.com/jonrohan/ZeroClipboard
59
+ var clip = new ZeroClipboard(document.getElementById("clip_button"), {
60
+ moviePath: "swf/ZeroClipboard.swf"
61
+ });
62
+
63
+ clip.on('load', function (client) {
64
+ debug_output("Flash movie loaded and ready.");
65
+ });
66
+
67
+ clip.on('noflash', function (client) {
68
+ debug_output("Your browser has no flash.");
69
+ });
70
+
71
+ clip.on('complete', function (client, args) {
72
+ debug_output("Copied text to clipboard: " + args.text);
73
+ });
74
+
75
+ function debug_output(text) {
76
+ p = document.createElement("p")
77
+ p.innerHTML = text
78
+ document.getElementById("debug_output").appendChild(p)
79
+ }
@@ -0,0 +1,9 @@
1
+ require "sinatra/zero_clipboard/version"
2
+ require "sinatra/zero_clipboard/assets_helper"
3
+ require "sinatra/zero_clipboard/assets"
4
+
5
+ module Sinatra
6
+ module ZeroClipboard
7
+
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ module Sinatra
2
+ module ZeroClipboard
3
+
4
+ module Assets
5
+ ASSETS = {
6
+ development: {
7
+ swf: { "ZeroClipboard.swf" => "2fa578220e56ec9d382e26e2dbb82ad0b9550ac2" },
8
+ js: { "ZeroClipboard.js" => "e3f0c042f93fe9aeaa570ba09e774a308681f2ed" }
9
+ },
10
+ production: {
11
+ swf: { "ZeroClipboard.swf" => "2fa578220e56ec9d382e26e2dbb82ad0b9550ac2" },
12
+ js: { "ZeroClipboard.min.js" => "20699686261a143ba1972b18a3ef3f0b3dbcd95c" }
13
+ }
14
+ }
15
+ ASSETS.default = ASSETS[:production]
16
+
17
+ class << self
18
+ def generate_zero_clipboard_asset_routes(application)
19
+ ASSETS[application.settings.environment].each do |file_type, files|
20
+ files.each_pair do |file_name, sha1_checksum|
21
+ application.get "/#{file_type}/#{file_name}", :provides => file_type do
22
+ cache_control :public, :must_revalidate, :max_age => 3600
23
+ etag sha1_checksum
24
+
25
+ File.read(File.join(Gem.datadir("sinatra-zero_clipboard"), "assets", file_name))
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ def registered(application)
32
+ generate_zero_clipboard_asset_routes(application)
33
+ application.helpers AssetsHelper
34
+ end
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,16 @@
1
+ module Sinatra
2
+ module ZeroClipboard
3
+
4
+ module AssetsHelper
5
+ def zero_clipboard_assets
6
+ environment = Assets::ASSETS.has_key?(settings.environment) ? settings.environment : :production
7
+ html_fragments = Assets::ASSETS[environment][:js].keys.each_with_object([]) do |file_name, memo|
8
+ memo << "<script type=\"text/javascript\" src=\"#{url("/js/#{file_name}")}\"></script>"
9
+ end
10
+
11
+ html_fragments.join("\n")
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ module Sinatra
2
+ module ZeroClipboard
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "sinatra/zero_clipboard/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sinatra-zero_clipboard"
8
+ spec.version = Sinatra::ZeroClipboard::VERSION
9
+ spec.authors = ["Oliver Feldt"]
10
+ spec.email = ["oliver.feldt@googlemail.com"]
11
+ spec.description = "Sinatra integration for ZeroClipboard"
12
+ spec.summary = "Adds helpers to enable the use of the clipboard on websites"
13
+ spec.homepage = "https://github.com/ofeldt/sinatra-zero_clipboard"
14
+ spec.license = "ISC"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rack", "~> 1.5.2"
23
+ spec.add_development_dependency "rack-test", "~> 0.6.2"
24
+ spec.add_development_dependency "rspec", "~> 2.13.0"
25
+ spec.add_development_dependency "simplecov", "~> 0.7.1"
26
+ spec.add_development_dependency "slim", "~> 1.3.8"
27
+ spec.add_development_dependency "pry", "~> 0.9.12.1"
28
+
29
+ spec.add_runtime_dependency "sinatra", "~> 1.4.2"
30
+ end
@@ -0,0 +1,34 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+
4
+ Bundler.setup(:default, :test)
5
+
6
+ require 'simplecov'
7
+ SimpleCov.start do
8
+ add_filter "spec"
9
+ end
10
+
11
+ require "sinatra"
12
+ require "rspec"
13
+ require "rack"
14
+ require "rack/test"
15
+ require "pry"
16
+ require File.expand_path("../../lib/sinatra/zero_clipboard", __FILE__)
17
+
18
+ # setting up test environment
19
+ Sinatra::Base.set :environment, :test
20
+ Sinatra::Base.set :run, false
21
+ Sinatra::Base.set :raise_errors, true
22
+ Sinatra::Base.set :logging, false
23
+
24
+ module Sinatra::ZeroClipboard::RSpecHelper
25
+ def app(reset = false)
26
+ @test_app = Class.new(Sinatra::Application) if reset || @test_app.nil?
27
+ @test_app
28
+ end
29
+ end
30
+
31
+ RSpec.configure do |config|
32
+ config.include Rack::Test::Methods
33
+ config.include Sinatra::ZeroClipboard::RSpecHelper
34
+ end
@@ -0,0 +1,41 @@
1
+ require "spec_helper"
2
+
3
+ describe Sinatra::ZeroClipboard::AssetsHelper do
4
+ context "#zero_clipboard_assets" do
5
+ before(:each) do
6
+ app(:reset)
7
+ app.register Sinatra::ZeroClipboard::Assets
8
+ app.get("/") { zero_clipboard_assets }
9
+ end
10
+
11
+ it "should return html fragments" do
12
+ get("/")
13
+
14
+ expect(last_response.body).to eq('<script type="text/javascript" src="http://example.org/js/ZeroClipboard.min.js"></script>')
15
+ end
16
+
17
+ it "should return non-minified version in development environment" do
18
+ app.settings.environment = :development
19
+
20
+ get("/")
21
+
22
+ expect(last_response.body).to eq('<script type="text/javascript" src="http://example.org/js/ZeroClipboard.js"></script>')
23
+ end
24
+
25
+ it "should return minified version in test environment" do
26
+ app.settings.environment = :test
27
+
28
+ get("/")
29
+
30
+ expect(last_response.body).to eq('<script type="text/javascript" src="http://example.org/js/ZeroClipboard.min.js"></script>')
31
+ end
32
+
33
+ it "should return minified version in production environment" do
34
+ app.settings.environment = :production
35
+
36
+ get("/")
37
+
38
+ expect(last_response.body).to eq('<script type="text/javascript" src="http://example.org/js/ZeroClipboard.min.js"></script>')
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+
3
+ describe Sinatra::ZeroClipboard::Assets do
4
+ before(:each) { app(:reset) }
5
+
6
+ context "#registered" do
7
+ it "should register our asset module as helper" do
8
+ expect(app).to_not be_a(Sinatra::ZeroClipboard::Assets)
9
+
10
+ app.register(Sinatra::ZeroClipboard::Assets)
11
+
12
+ expect(app).to be_a(Sinatra::ZeroClipboard::Assets)
13
+ end
14
+ end
15
+
16
+ context "#generate_zero_clipboard_asset_routes" do
17
+ [:development, :test, :production].each do |environment|
18
+ context "in #{environment} mode" do
19
+ Sinatra::ZeroClipboard::Assets::ASSETS[environment].each_pair do |file_type, file_list|
20
+ file_list.each_pair do |file_name, sha1_checksum|
21
+ before(:each) do
22
+ app.settings.environment = environment
23
+ app.register Sinatra::ZeroClipboard::Assets
24
+ end
25
+
26
+ it "should register routes for #{file_type}/#{file_name}" do
27
+ get("/#{file_type}/#{file_name}")
28
+
29
+ expect(last_response.status).to eq(200)
30
+ end
31
+
32
+ it "should set an ETag for #{file_type}/#{file_name} in response headers for caching" do
33
+ get("/#{file_type}/#{file_name}")
34
+
35
+ expect(last_response.headers["ETag"]).to match(/#{sha1_checksum}/)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-zero_clipboard
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Oliver Feldt
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rack
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.5.2
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.5.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: rack-test
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.6.2
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.6.2
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 2.13.0
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 2.13.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: simplecov
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.7.1
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.7.1
94
+ - !ruby/object:Gem::Dependency
95
+ name: slim
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 1.3.8
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 1.3.8
110
+ - !ruby/object:Gem::Dependency
111
+ name: pry
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 0.9.12.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 0.9.12.1
126
+ - !ruby/object:Gem::Dependency
127
+ name: sinatra
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: 1.4.2
134
+ type: :runtime
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: 1.4.2
142
+ description: Sinatra integration for ZeroClipboard
143
+ email:
144
+ - oliver.feldt@googlemail.com
145
+ executables: []
146
+ extensions: []
147
+ extra_rdoc_files: []
148
+ files:
149
+ - .gitignore
150
+ - .rspec
151
+ - Gemfile
152
+ - LICENSE.txt
153
+ - README.md
154
+ - Rakefile
155
+ - data/sinatra-zero_clipboard/assets/ZeroClipboard.js
156
+ - data/sinatra-zero_clipboard/assets/ZeroClipboard.min.js
157
+ - data/sinatra-zero_clipboard/assets/ZeroClipboard.swf
158
+ - examples/sample_application.rb
159
+ - lib/sinatra/zero_clipboard.rb
160
+ - lib/sinatra/zero_clipboard/assets.rb
161
+ - lib/sinatra/zero_clipboard/assets_helper.rb
162
+ - lib/sinatra/zero_clipboard/version.rb
163
+ - sinatra-zero_clipboard.gemspec
164
+ - spec/spec_helper.rb
165
+ - spec/unit/sinatra/zero_clipboard/assets_helper_spec.rb
166
+ - spec/unit/sinatra/zero_clipboard/assets_spec.rb
167
+ homepage: https://github.com/ofeldt/sinatra-zero_clipboard
168
+ licenses:
169
+ - ISC
170
+ post_install_message:
171
+ rdoc_options: []
172
+ require_paths:
173
+ - lib
174
+ required_ruby_version: !ruby/object:Gem::Requirement
175
+ none: false
176
+ requirements:
177
+ - - '>='
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ required_rubygems_version: !ruby/object:Gem::Requirement
181
+ none: false
182
+ requirements:
183
+ - - '>='
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 1.8.25
189
+ signing_key:
190
+ specification_version: 3
191
+ summary: Adds helpers to enable the use of the clipboard on websites
192
+ test_files:
193
+ - spec/spec_helper.rb
194
+ - spec/unit/sinatra/zero_clipboard/assets_helper_spec.rb
195
+ - spec/unit/sinatra/zero_clipboard/assets_spec.rb