├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── dist ├── .jshintrc ├── ZeroClipboard.swf ├── jquery.zeroclipboard.js └── jquery.zeroclipboard.min.js ├── libs ├── jquery-loader.js └── jquery │ └── jquery.js ├── package.json ├── src ├── end.js ├── jquery.zeroclipboard.js └── start.js ├── test ├── demo.html ├── jquery.zeroclipboard.html └── jquery.zeroclipboard_tests.js └── zeroclipboard.jquery.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | /* Enforcing options */ 3 | "bitwise": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "es3": true, 8 | "es5": false, 9 | "forin": true, 10 | "freeze": true, 11 | "immed": true, 12 | "indent": 2, 13 | "latedef": true, 14 | "newcap": true, 15 | "noarg": true, 16 | "noempty": true, 17 | "nonbsp": true, 18 | "nonew": true, 19 | "plusplus": false, 20 | "quotmark": "double", 21 | "undef": true, 22 | "unused": true, 23 | "strict": true, 24 | "trailing": true, 25 | "maxparams": 4, 26 | "maxdepth": 5, 27 | "maxstatements": 25, 28 | "maxlen": false, /* IDEAL: 120? */ 29 | 30 | 31 | /* Relaxing options */ 32 | "asi": false, 33 | "boss": false, 34 | "debug": false, 35 | "eqnull": true, 36 | "esnext": false, 37 | "evil": false, 38 | "expr": false, 39 | "funcscope": false, 40 | "gcl": false, 41 | "globalstrict": false, 42 | "iterator": false, 43 | "lastsemic": false, 44 | "laxbreak": false, 45 | "laxcomma": false, 46 | "loopfunc": false, 47 | "maxerr": 50, 48 | "moz": false, 49 | "multistr": false, 50 | "notypeof": false, 51 | "proto": false, 52 | "scripturl": false, 53 | "smarttabs": false, 54 | "shadow": false, 55 | "sub": false, 56 | "supernew": false, 57 | "validthis": false, 58 | "noyield": false, 59 | 60 | /* Environments */ 61 | "browser": true, 62 | "jquery": true 63 | 64 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_install: 5 | - npm update -g npm 6 | - npm install -g grunt-cli 7 | 8 | sudo: false 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Important notes 4 | Please don't edit files in the `dist` subdirectory as they are generated via Grunt. You'll find source code in the `src` subdirectory! 5 | 6 | ### Code style 7 | Regarding code style like indentation and whitespace, **follow the conventions you see used in the source already.** 8 | 9 | ### PhantomJS 10 | While Grunt can run the included unit tests via [PhantomJS](http://phantomjs.org/), this shouldn't be considered a substitute for the real thing. Please be sure to test the `test/*.html` unit test file(s) in _actual_ browsers. 11 | 12 | ## Modifying the code 13 | First, ensure that you have the latest [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/) installed. 14 | 15 | Test that Grunt's CLI is installed by running `grunt --version`. If the command isn't found, run `npm install -g grunt-cli`. For more information about installing Grunt, see the [getting started guide](http://gruntjs.com/getting-started). 16 | 17 | 1. Fork and clone the repo. 18 | 1. Run `npm install` to install all dependencies (including Grunt). 19 | 1. Run `grunt` to grunt this project. 20 | 21 | Assuming that you don't see any red, you're ready to go. Just be sure to run `grunt` after making any changes, to ensure that nothing is broken. 22 | 23 | ## Submitting pull requests 24 | 25 | 1. Create a new branch, please don't work in your `master` branch directly. 26 | 1. Add failing tests for the change you want to make. Run `grunt` to see the tests fail. 27 | 1. Fix stuff. 28 | 1. Run `grunt` to see if the tests pass. Repeat steps 2-4 until done. 29 | 1. Open `test/*.html` unit test file(s) in actual browser to ensure tests pass everywhere. 30 | 1. Update the documentation to reflect any changes. 31 | 1. Push to your fork and submit a pull request. 32 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*jshint -W106 */ 2 | /*jshint node:true, browser:false */ 3 | 4 | "use strict"; 5 | 6 | var path = require("path"); 7 | var findup = require("findup-sync"); 8 | 9 | 10 | module.exports = function(grunt) { 11 | 12 | // Project configuration. 13 | grunt.initConfig({ 14 | // Metadata. 15 | pkg: grunt.file.readJSON("zeroclipboard.jquery.json"), 16 | 17 | banner: 18 | "/*!\n" + 19 | " * <%= pkg.title || pkg.name %>\n" + 20 | " * <%= pkg.description %>\n" + 21 | " * Copyright (c) <%= grunt.template.today('yyyy') %> <%= _.pluck(pkg.contributors, 'name').join(', ') %>\n" + 22 | " * Licensed <%= _.pluck(pkg.licenses, 'type').join(', ') %>\n" + 23 | " * <%= pkg.homepage %>\n" + 24 | " * v<%= pkg.version %>\n" + 25 | " */\n", 26 | 27 | zeroclipboardPath: path.dirname(findup("package.json", { cwd: path.dirname(require.resolve("zeroclipboard")) })), 28 | 29 | 30 | // Task configuration. 31 | jshint: { 32 | options: { 33 | jshintrc: true 34 | }, 35 | prebuild: { 36 | src: [ 37 | "Gruntfile.js", 38 | "src/**/*.js", 39 | "!src/start.js", 40 | "!src/end.js", 41 | "test/**/*.js" 42 | ] 43 | }, 44 | postbuild: { 45 | src: ["<%= concat.dist.dest %>"] 46 | } 47 | }, 48 | clean: { 49 | files: ["dist/**/*", "!dist/**/.jshintrc"] 50 | }, 51 | concat: { 52 | options: { 53 | stripBanners: true, 54 | process: function(src, filepath) { 55 | return filepath === "src/start.js" ? 56 | src : 57 | src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, "$1"); 58 | } 59 | }, 60 | dist: { 61 | src: [ 62 | "src/start.js", 63 | "<%= zeroclipboardPath %>/dist/ZeroClipboard.Core.js", 64 | "src/jquery.<%= pkg.name %>.js", 65 | "src/end.js" 66 | ], 67 | dest: "dist/jquery.<%= pkg.name %>.js" 68 | } 69 | }, 70 | copy: { 71 | swf: { 72 | src: ["<%= zeroclipboardPath %>/dist/ZeroClipboard.swf"], 73 | dest: "dist/ZeroClipboard.swf" 74 | } 75 | }, 76 | uglify: { 77 | options: { 78 | report: "min" 79 | }, 80 | js: { 81 | options: { 82 | banner: "<%= banner %>", 83 | preserveComments: function(node, comment) { 84 | return comment && 85 | comment.type === "comment2" && 86 | /^(!|\*|\*!)\r?\n/.test(comment.value); 87 | }, 88 | beautify: { 89 | beautify: true, 90 | // `indent_level` requires jshint -W106 91 | indent_level: 2 92 | }, 93 | mangle: false, 94 | compress: false 95 | }, 96 | src: "<%= concat.dist.dest %>", 97 | dest: "<%= concat.dist.dest %>" 98 | }, 99 | minjs: { 100 | options: { 101 | preserveComments: function(node, comment) { 102 | return comment && 103 | comment.type === "comment2" && 104 | /^(!|\*!)\r?\n/.test(comment.value); 105 | } 106 | }, 107 | src: "<%= concat.dist.dest %>", 108 | dest: "dist/jquery.<%= pkg.name %>.min.js" 109 | } 110 | }, 111 | connect: { 112 | server: { 113 | options: { 114 | port: 7320 // "ZERO" 115 | } 116 | } 117 | }, 118 | qunit: { 119 | file: ["test/jquery.<%= pkg.name %>.html"], 120 | http: { 121 | options: { 122 | urls: [ 123 | /*"1.0", "1.0.1", "1.0.2", "1.0.3", "1.0.4",*/ 124 | /*"1.1", "1.1.1", "1.1.2", "1.1.3", "1.1.4",*/ 125 | /*"1.2", "1.2.1", "1.2.2", "1.2.3", "1.2.4", "1.2.5", "1.2.6",*/ 126 | /*"1.3", "1.3.1", "1.3.2",*/ 127 | /*"1.4", "1.4.1", "1.4.2", "1.4.3", "1.4.4",*/ 128 | /*"1.5", "1.5.1", "1.5.2",*/ 129 | /*"1.6", "1.6.1", "1.6.2", "1.6.3", "1.6.4",*/ 130 | "1.7", "1.7.1", "1.7.2", 131 | "1.8.0", "1.8.1", "1.8.2", "1.8.3", 132 | "1.9.0", "1.9.1", 133 | "1.10.0", "1.10.1", "1.10.2", 134 | "1.11.0", "1.11.1", 135 | "git1", 136 | "2.0.0", "2.0.1", "2.0.2", "2.0.3", 137 | "2.1.0", "2.1.1", 138 | "git2" 139 | ].map(function(jqVersion) { 140 | return "http://localhost:<%= connect.server.options.port %>/test/jquery.<%= pkg.name %>.html?jquery=" + jqVersion; 141 | }) 142 | } 143 | } 144 | } 145 | }); 146 | 147 | // These plugins provide necessary tasks. 148 | grunt.loadNpmTasks("grunt-contrib-clean"); 149 | grunt.loadNpmTasks("grunt-contrib-concat"); 150 | grunt.loadNpmTasks("grunt-contrib-connect"); 151 | grunt.loadNpmTasks("grunt-contrib-copy"); 152 | grunt.loadNpmTasks("grunt-contrib-jshint"); 153 | grunt.loadNpmTasks("grunt-contrib-qunit"); 154 | grunt.loadNpmTasks("grunt-contrib-uglify"); 155 | 156 | // Custom task chains. 157 | grunt.registerTask("build", ["jshint:prebuild", "clean", "concat", "jshint:postbuild", "copy", "uglify"]); 158 | grunt.registerTask("travis", ["jshint:prebuild", "clean", "concat", "jshint:postbuild", "connect", "qunit"]); 159 | 160 | // Default task. 161 | grunt.registerTask("default", ["build", "connect", "qunit"]); 162 | 163 | }; 164 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 James M. Greene 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/zeroclipboard/jquery.zeroclipboard.png?branch=master)](https://travis-ci.org/zeroclipboard/jquery.zeroclipboard) 2 | 3 | # jquery.zeroclipboard 4 | 5 | Bind to the `beforecopy`, `copy`, `aftercopy`, and `copy-error` events, custom DOM-like events for clipboard injection generated using jQuery's Special Events API and [ZeroClipboard](http://zeroclipboard.org/)'s Core module. 6 | 7 | The `beforecopy` and `copy` events trigger when the user clicks on a bound element. 8 | 9 | The `aftercopy` event triggers after the clipboard injection has been attempted, regardless of whether or not the injection succeeded. 10 | 11 | The `copy-error` event triggers if any of the underlying ZeroClipboard error events occur. 12 | 13 | The `click` event will also be bubbled after the `aftercopy` event handlers have all been triggered or stopped. 14 | 15 | 16 | ## Prerequisites 17 | 18 | ZeroClipboard requires the use of Flash Player 11.0.0 or higher. See [ZeroClipboard](https://github.com/zeroclipboard/zeroclipboard) for more details about the underlying mechanism. 19 | 20 | This plugin's functionality is made possible by the smart default configuration values made in ZeroClipboard `v2.x`, plus internally overriding a few configuration options. 21 | 22 | 23 | ## Getting Started 24 | Check the [jQuery Plugins Registry](http://plugins.jquery.com/zeroclipboard/) for the latest published version of this plugin! 25 | 26 | You can also download the [production version][min] or the [development version][max] from GitHub. You will also need a [ZeroClipboard v2.x SWF][swf]. 27 | 28 | [min]: https://raw.github.com/zeroclipboard/jquery.zeroclipboard/master/dist/jquery.zeroclipboard.min.js 29 | [max]: https://raw.github.com/zeroclipboard/jquery.zeroclipboard/master/dist/jquery.zeroclipboard.js 30 | [swf]: https://raw.github.com/zeroclipboard/jquery.zeroclipboard/master/dist/ZeroClipboard.swf 31 | 32 | In your web page: 33 | 34 | ```html 35 | 36 | 37 | 47 | 48 | ``` 49 | 50 | 51 | ## Options 52 | 53 | There are a handful of options that can be configured to customize the use of this jQuery Special Event: 54 | 55 | ```js 56 | $.event.special.copy.options = { 57 | 58 | // The default action for the W3C Clipboard API spec (as it stands today) is to 59 | // copy the currently selected text [and specificially ignore any pending data] 60 | // unless `e.preventDefault();` is called. 61 | requirePreventDefault: true, 62 | 63 | // If HTML has been added to the pending data, this plugin can automatically 64 | // convert the HTML into RTF (RichText) for use in non-HTML-capable editors. 65 | autoConvertHtmlToRtf: true, 66 | 67 | // SWF inbound scripting policy: page domains that the SWF should trust. 68 | // (single string, or array of strings) 69 | trustedDomains: ZeroClipboard.config("trustedDomains"), 70 | 71 | // The CSS class name used to mimic the `:hover` pseudo-class 72 | hoverClass: "hover", 73 | 74 | // The CSS class name used to mimic the `:active` pseudo-class 75 | activeClass: "active" 76 | 77 | }; 78 | ``` 79 | 80 | 81 | ## Examples 82 | 83 | Offers an API similar to the HTML5 Clipboard API. 84 | _NOTE:_ Some of these examples will also be leveraging the [jQuery.Range plugin](http://jquerypp.com/#range) where noted. 85 | 86 | ### Example 1: Using `beforecopy` 87 | 88 | The following example uses the `beforecopy` event to change the selected text before it is copied. The modified selection is what will be copied into the clipboard if the action is not prevented. 89 | 90 | ```js 91 | jQuery(document).ready(function($) { 92 | $("body").on("beforecopy", ".zclip", function() { 93 | // Select the text of this element; this will be copied by default 94 | $("#textToCopy").range().select(); // ** Using the jQuery.Range plugin 95 | }); 96 | }); 97 | ``` 98 | 99 | 100 | ### Example 2: Using `copy` 101 | 102 | The following example uses the `copy` event to set data into several different clipboard sectors. 103 | 104 | ```js 105 | jQuery(document).ready(function($) { 106 | $("body").on("copy", ".zclip", function(/* ClipboardEvent */ e) { 107 | // Get the currently selected text 108 | var textToCopy = $.Range.current().toString(); // ** Using the jQuery.Range plugin 109 | 110 | // If there isn't any currently selected text, just ignore this event 111 | if (!textToCopy) { 112 | return; 113 | } 114 | 115 | // Clear out any existing data in the pending clipboard transaction 116 | e.clipboardData.clearData(); 117 | 118 | // Set your own data into the pending clipboard transaction 119 | e.clipboardData.setData("text/plain", textToCopy); 120 | e.clipboardData.setData("text/html", "" + textToCopy + ""); 121 | e.clipboardData.setData("application/rtf", "{\\rtf1\\ansi\n{\\b " + textToCopy + "}}"); 122 | 123 | // Prevent the default action of copying the currently selected text into the clipboard 124 | e.preventDefault(); 125 | }); 126 | }); 127 | ``` 128 | 129 | ### Example 3: Using `aftercopy` 130 | 131 | This is the same as [Example #1](#example-1-using-beforecopy), except that it also uses the `aftercopy` event to "celebrate" and the `copy-error` event to watch for errors. 132 | 133 | ```js 134 | jQuery(document).ready(function($) { 135 | var eventsMap = { 136 | "beforecopy": function() { 137 | // Select the text of this element; this will be copied by default 138 | $("#textToCopy").range().select(); // ** Using the jQuery.Range plugin 139 | }, 140 | "aftercopy": function(/* aftercopyEvent */ e) { 141 | // NOTE: The `aftercopyEvent` event interface is not based on any existing DOM event, so the event model 142 | // is still just a draft version. If you have any suggestions, please submit a new issue in this repo! 143 | if (e.status["text/plain"] === true) { 144 | alert("Copy succeeded. Yay! Text: " + e.data["text/plain"]); 145 | } 146 | else { 147 | alert("Copy failed... BOOOOOO!!!"); 148 | } 149 | }, 150 | "copy-error": function(errorEvent) { 151 | alert("ERROR! " + errorEvent); 152 | } 153 | }; 154 | $("body").on(eventsMap, ".zclip"); 155 | }); 156 | ``` 157 | 158 | 159 | ## `file://` Protocol Limitations 160 | 161 | If you want to host a page locally on the `file://` protocol, you must specifically configure 162 | ZeroClipboard to trust ALL domains for SWF interaction via a wildcard. This configuration must be 163 | set _before_ attaching any event handlers for the `beforecopy`, `copy`, `aftercopy`, or `copy-error` 164 | events: 165 | 166 | ```js 167 | $.event.special.copy.options.trustedDomains = ["*"]; 168 | ``` 169 | 170 | This wildcard configuration should _**NOT**_ be used in environments hosted over HTTP/HTTPS. 171 | 172 | 173 | ## Compatibility 174 | **Works 100% with jQuery versions:** 175 | - 1.7.x and above 176 | 177 | **Untested jQuery versions:** 178 | - Anything below 1.7.x (incompatible jQuery Special Events API) 179 | -------------------------------------------------------------------------------- /dist/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | /* Enforcing options */ 3 | "bitwise": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "es3": true, 8 | "es5": false, 9 | "forin": true, 10 | "freeze": true, 11 | "immed": false, 12 | "indent": 2, 13 | "latedef": true, 14 | "newcap": true, 15 | "noarg": true, 16 | "noempty": true, 17 | "nonbsp": true, 18 | "nonew": true, 19 | "plusplus": false, 20 | "quotmark": false, 21 | "undef": true, 22 | "unused": true, 23 | "strict": false, 24 | "trailing": true, 25 | "maxparams": 4, 26 | "maxdepth": 6, 27 | "maxstatements": false, 28 | "maxlen": false, /* IDEAL: 120? */ 29 | 30 | 31 | /* Relaxing options */ 32 | "asi": false, 33 | "boss": false, 34 | "debug": false, 35 | "eqnull": true, 36 | "esnext": false, 37 | "evil": false, 38 | "expr": false, 39 | "funcscope": false, 40 | "gcl": false, 41 | "globalstrict": false, 42 | "iterator": false, 43 | "lastsemic": false, 44 | "laxbreak": false, 45 | "laxcomma": false, 46 | "loopfunc": false, 47 | "maxerr": 50, 48 | "moz": false, 49 | "multistr": false, 50 | "notypeof": false, 51 | "proto": false, 52 | "scripturl": false, 53 | "smarttabs": false, 54 | "shadow": false, 55 | "sub": false, 56 | "supernew": false, 57 | "validthis": false, 58 | "noyield": false, 59 | 60 | /* Environments */ 61 | "browser": true, 62 | "jquery": true, 63 | 64 | /* Global Variables */ 65 | "globals": { 66 | "define": false, 67 | "module": false, 68 | "exports": false 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /dist/ZeroClipboard.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeroclipboard/jquery.zeroclipboard/68bcae7c62c503bbdd7456e94d9899efde86cd0d/dist/ZeroClipboard.swf -------------------------------------------------------------------------------- /dist/jquery.zeroclipboard.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jquery.zeroclipboard 3 | * Bind to the `beforecopy`, `copy`, `aftercopy`, and `copy-error` events, custom DOM-like events for clipboard injection generated using jQuery's Special Events API and ZeroClipboard's Core module. 4 | * Copyright (c) 2014 5 | * Licensed MIT 6 | * https://github.com/zeroclipboard/jquery.zeroclipboard 7 | * v0.2.0 8 | */ 9 | (function($, window, undefined) { 10 | "use strict"; 11 | var require, module, exports; 12 | var zcExistsAlready = !!window.ZeroClipboard; 13 | /*! 14 | * ZeroClipboard 15 | * The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface. 16 | * Copyright (c) 2014 Jon Rohan, James M. Greene 17 | * Licensed MIT 18 | * http://zeroclipboard.org/ 19 | * v2.1.2 20 | */ 21 | (function(window, undefined) { 22 | /** 23 | * Store references to critically important global functions that may be 24 | * overridden on certain web pages. 25 | */ 26 | var _window = window, _document = _window.document, _navigator = _window.navigator, _setTimeout = _window.setTimeout, _encodeURIComponent = _window.encodeURIComponent, _ActiveXObject = _window.ActiveXObject, _parseInt = _window.Number.parseInt || _window.parseInt, _parseFloat = _window.Number.parseFloat || _window.parseFloat, _isNaN = _window.Number.isNaN || _window.isNaN, _round = _window.Math.round, _now = _window.Date.now, _keys = _window.Object.keys, _defineProperty = _window.Object.defineProperty, _hasOwn = _window.Object.prototype.hasOwnProperty, _slice = _window.Array.prototype.slice; 27 | /** 28 | * Convert an `arguments` object into an Array. 29 | * 30 | * @returns The arguments as an Array 31 | * @private 32 | */ 33 | var _args = function(argumentsObj) { 34 | return _slice.call(argumentsObj, 0); 35 | }; 36 | /** 37 | * Shallow-copy the owned, enumerable properties of one object over to another, similar to jQuery's `$.extend`. 38 | * 39 | * @returns The target object, augmented 40 | * @private 41 | */ 42 | var _extend = function() { 43 | var i, len, arg, prop, src, copy, args = _args(arguments), target = args[0] || {}; 44 | for (i = 1, len = args.length; i < len; i++) { 45 | if ((arg = args[i]) != null) { 46 | for (prop in arg) { 47 | if (_hasOwn.call(arg, prop)) { 48 | src = target[prop]; 49 | copy = arg[prop]; 50 | if (target !== copy && copy !== undefined) { 51 | target[prop] = copy; 52 | } 53 | } 54 | } 55 | } 56 | } 57 | return target; 58 | }; 59 | /** 60 | * Return a deep copy of the source object or array. 61 | * 62 | * @returns Object or Array 63 | * @private 64 | */ 65 | var _deepCopy = function(source) { 66 | var copy, i, len, prop; 67 | if (typeof source !== "object" || source == null) { 68 | copy = source; 69 | } else if (typeof source.length === "number") { 70 | copy = []; 71 | for (i = 0, len = source.length; i < len; i++) { 72 | if (_hasOwn.call(source, i)) { 73 | copy[i] = _deepCopy(source[i]); 74 | } 75 | } 76 | } else { 77 | copy = {}; 78 | for (prop in source) { 79 | if (_hasOwn.call(source, prop)) { 80 | copy[prop] = _deepCopy(source[prop]); 81 | } 82 | } 83 | } 84 | return copy; 85 | }; 86 | /** 87 | * Makes a shallow copy of `obj` (like `_extend`) but filters its properties based on a list of `keys` to keep. 88 | * The inverse of `_omit`, mostly. The big difference is that these properties do NOT need to be enumerable to 89 | * be kept. 90 | * 91 | * @returns A new filtered object. 92 | * @private 93 | */ 94 | var _pick = function(obj, keys) { 95 | var newObj = {}; 96 | for (var i = 0, len = keys.length; i < len; i++) { 97 | if (keys[i] in obj) { 98 | newObj[keys[i]] = obj[keys[i]]; 99 | } 100 | } 101 | return newObj; 102 | }; 103 | /** 104 | * Makes a shallow copy of `obj` (like `_extend`) but filters its properties based on a list of `keys` to omit. 105 | * The inverse of `_pick`. 106 | * 107 | * @returns A new filtered object. 108 | * @private 109 | */ 110 | var _omit = function(obj, keys) { 111 | var newObj = {}; 112 | for (var prop in obj) { 113 | if (keys.indexOf(prop) === -1) { 114 | newObj[prop] = obj[prop]; 115 | } 116 | } 117 | return newObj; 118 | }; 119 | /** 120 | * Remove all owned, enumerable properties from an object. 121 | * 122 | * @returns The original object without its owned, enumerable properties. 123 | * @private 124 | */ 125 | var _deleteOwnProperties = function(obj) { 126 | if (obj) { 127 | for (var prop in obj) { 128 | if (_hasOwn.call(obj, prop)) { 129 | delete obj[prop]; 130 | } 131 | } 132 | } 133 | return obj; 134 | }; 135 | /** 136 | * Determine if an element is contained within another element. 137 | * 138 | * @returns Boolean 139 | * @private 140 | */ 141 | var _containedBy = function(el, ancestorEl) { 142 | if (el && el.nodeType === 1 && el.ownerDocument && ancestorEl && (ancestorEl.nodeType === 1 && ancestorEl.ownerDocument && ancestorEl.ownerDocument === el.ownerDocument || ancestorEl.nodeType === 9 && !ancestorEl.ownerDocument && ancestorEl === el.ownerDocument)) { 143 | do { 144 | if (el === ancestorEl) { 145 | return true; 146 | } 147 | el = el.parentNode; 148 | } while (el); 149 | } 150 | return false; 151 | }; 152 | /** 153 | * Keep track of the state of the Flash object. 154 | * @private 155 | */ 156 | var _flashState = { 157 | bridge: null, 158 | version: "0.0.0", 159 | pluginType: "unknown", 160 | disabled: null, 161 | outdated: null, 162 | unavailable: null, 163 | deactivated: null, 164 | overdue: null, 165 | ready: null 166 | }; 167 | /** 168 | * The minimum Flash Player version required to use ZeroClipboard completely. 169 | * @readonly 170 | * @private 171 | */ 172 | var _minimumFlashVersion = "11.0.0"; 173 | /** 174 | * Keep track of all event listener registrations. 175 | * @private 176 | */ 177 | var _handlers = {}; 178 | /** 179 | * Keep track of the currently activated element. 180 | * @private 181 | */ 182 | var _currentElement; 183 | /** 184 | * Keep track of data for the pending clipboard transaction. 185 | * @private 186 | */ 187 | var _clipData = {}; 188 | /** 189 | * Keep track of data formats for the pending clipboard transaction. 190 | * @private 191 | */ 192 | var _clipDataFormatMap = null; 193 | /** 194 | * The `message` store for events 195 | * @private 196 | */ 197 | var _eventMessages = { 198 | ready: "Flash communication is established", 199 | error: { 200 | "flash-disabled": "Flash is disabled or not installed", 201 | "flash-outdated": "Flash is too outdated to support ZeroClipboard", 202 | "flash-unavailable": "Flash is unable to communicate bidirectionally with JavaScript", 203 | "flash-deactivated": "Flash is too outdated for your browser and/or is configured as click-to-activate", 204 | "flash-overdue": "Flash communication was established but NOT within the acceptable time limit" 205 | } 206 | }; 207 | /** 208 | * The presumed location of the "ZeroClipboard.swf" file, based on the location 209 | * of the executing JavaScript file (e.g. "ZeroClipboard.js", etc.). 210 | * @private 211 | */ 212 | var _swfPath = function() { 213 | var i, jsDir, tmpJsPath, jsPath, swfPath = "ZeroClipboard.swf"; 214 | if (!(_document.currentScript && (jsPath = _document.currentScript.src))) { 215 | var scripts = _document.getElementsByTagName("script"); 216 | if ("readyState" in scripts[0]) { 217 | for (i = scripts.length; i--; ) { 218 | if (scripts[i].readyState === "interactive" && (jsPath = scripts[i].src)) { 219 | break; 220 | } 221 | } 222 | } else if (_document.readyState === "loading") { 223 | jsPath = scripts[scripts.length - 1].src; 224 | } else { 225 | for (i = scripts.length; i--; ) { 226 | tmpJsPath = scripts[i].src; 227 | if (!tmpJsPath) { 228 | jsDir = null; 229 | break; 230 | } 231 | tmpJsPath = tmpJsPath.split("#")[0].split("?")[0]; 232 | tmpJsPath = tmpJsPath.slice(0, tmpJsPath.lastIndexOf("/") + 1); 233 | if (jsDir == null) { 234 | jsDir = tmpJsPath; 235 | } else if (jsDir !== tmpJsPath) { 236 | jsDir = null; 237 | break; 238 | } 239 | } 240 | if (jsDir !== null) { 241 | jsPath = jsDir; 242 | } 243 | } 244 | } 245 | if (jsPath) { 246 | jsPath = jsPath.split("#")[0].split("?")[0]; 247 | swfPath = jsPath.slice(0, jsPath.lastIndexOf("/") + 1) + swfPath; 248 | } 249 | return swfPath; 250 | }(); 251 | /** 252 | * ZeroClipboard configuration defaults for the Core module. 253 | * @private 254 | */ 255 | var _globalConfig = { 256 | swfPath: _swfPath, 257 | trustedDomains: window.location.host ? [ window.location.host ] : [], 258 | cacheBust: true, 259 | forceEnhancedClipboard: false, 260 | flashLoadTimeout: 3e4, 261 | autoActivate: true, 262 | bubbleEvents: true, 263 | containerId: "global-zeroclipboard-html-bridge", 264 | containerClass: "global-zeroclipboard-container", 265 | swfObjectId: "global-zeroclipboard-flash-bridge", 266 | hoverClass: "zeroclipboard-is-hover", 267 | activeClass: "zeroclipboard-is-active", 268 | forceHandCursor: false, 269 | title: null, 270 | zIndex: 999999999 271 | }; 272 | /** 273 | * The underlying implementation of `ZeroClipboard.config`. 274 | * @private 275 | */ 276 | var _config = function(options) { 277 | if (typeof options === "object" && options !== null) { 278 | for (var prop in options) { 279 | if (_hasOwn.call(options, prop)) { 280 | if (/^(?:forceHandCursor|title|zIndex|bubbleEvents)$/.test(prop)) { 281 | _globalConfig[prop] = options[prop]; 282 | } else if (_flashState.bridge == null) { 283 | if (prop === "containerId" || prop === "swfObjectId") { 284 | if (_isValidHtml4Id(options[prop])) { 285 | _globalConfig[prop] = options[prop]; 286 | } else { 287 | throw new Error("The specified `" + prop + "` value is not valid as an HTML4 Element ID"); 288 | } 289 | } else { 290 | _globalConfig[prop] = options[prop]; 291 | } 292 | } 293 | } 294 | } 295 | } 296 | if (typeof options === "string" && options) { 297 | if (_hasOwn.call(_globalConfig, options)) { 298 | return _globalConfig[options]; 299 | } 300 | return; 301 | } 302 | return _deepCopy(_globalConfig); 303 | }; 304 | /** 305 | * The underlying implementation of `ZeroClipboard.state`. 306 | * @private 307 | */ 308 | var _state = function() { 309 | return { 310 | browser: _pick(_navigator, [ "userAgent", "platform", "appName" ]), 311 | flash: _omit(_flashState, [ "bridge" ]), 312 | zeroclipboard: { 313 | version: ZeroClipboard.version, 314 | config: ZeroClipboard.config() 315 | } 316 | }; 317 | }; 318 | /** 319 | * The underlying implementation of `ZeroClipboard.isFlashUnusable`. 320 | * @private 321 | */ 322 | var _isFlashUnusable = function() { 323 | return !!(_flashState.disabled || _flashState.outdated || _flashState.unavailable || _flashState.deactivated); 324 | }; 325 | /** 326 | * The underlying implementation of `ZeroClipboard.on`. 327 | * @private 328 | */ 329 | var _on = function(eventType, listener) { 330 | var i, len, events, added = {}; 331 | if (typeof eventType === "string" && eventType) { 332 | events = eventType.toLowerCase().split(/\s+/); 333 | } else if (typeof eventType === "object" && eventType && typeof listener === "undefined") { 334 | for (i in eventType) { 335 | if (_hasOwn.call(eventType, i) && typeof i === "string" && i && typeof eventType[i] === "function") { 336 | ZeroClipboard.on(i, eventType[i]); 337 | } 338 | } 339 | } 340 | if (events && events.length) { 341 | for (i = 0, len = events.length; i < len; i++) { 342 | eventType = events[i].replace(/^on/, ""); 343 | added[eventType] = true; 344 | if (!_handlers[eventType]) { 345 | _handlers[eventType] = []; 346 | } 347 | _handlers[eventType].push(listener); 348 | } 349 | if (added.ready && _flashState.ready) { 350 | ZeroClipboard.emit({ 351 | type: "ready" 352 | }); 353 | } 354 | if (added.error) { 355 | var errorTypes = [ "disabled", "outdated", "unavailable", "deactivated", "overdue" ]; 356 | for (i = 0, len = errorTypes.length; i < len; i++) { 357 | if (_flashState[errorTypes[i]] === true) { 358 | ZeroClipboard.emit({ 359 | type: "error", 360 | name: "flash-" + errorTypes[i] 361 | }); 362 | break; 363 | } 364 | } 365 | } 366 | } 367 | return ZeroClipboard; 368 | }; 369 | /** 370 | * The underlying implementation of `ZeroClipboard.off`. 371 | * @private 372 | */ 373 | var _off = function(eventType, listener) { 374 | var i, len, foundIndex, events, perEventHandlers; 375 | if (arguments.length === 0) { 376 | events = _keys(_handlers); 377 | } else if (typeof eventType === "string" && eventType) { 378 | events = eventType.split(/\s+/); 379 | } else if (typeof eventType === "object" && eventType && typeof listener === "undefined") { 380 | for (i in eventType) { 381 | if (_hasOwn.call(eventType, i) && typeof i === "string" && i && typeof eventType[i] === "function") { 382 | ZeroClipboard.off(i, eventType[i]); 383 | } 384 | } 385 | } 386 | if (events && events.length) { 387 | for (i = 0, len = events.length; i < len; i++) { 388 | eventType = events[i].toLowerCase().replace(/^on/, ""); 389 | perEventHandlers = _handlers[eventType]; 390 | if (perEventHandlers && perEventHandlers.length) { 391 | if (listener) { 392 | foundIndex = perEventHandlers.indexOf(listener); 393 | while (foundIndex !== -1) { 394 | perEventHandlers.splice(foundIndex, 1); 395 | foundIndex = perEventHandlers.indexOf(listener, foundIndex); 396 | } 397 | } else { 398 | perEventHandlers.length = 0; 399 | } 400 | } 401 | } 402 | } 403 | return ZeroClipboard; 404 | }; 405 | /** 406 | * The underlying implementation of `ZeroClipboard.handlers`. 407 | * @private 408 | */ 409 | var _listeners = function(eventType) { 410 | var copy; 411 | if (typeof eventType === "string" && eventType) { 412 | copy = _deepCopy(_handlers[eventType]) || null; 413 | } else { 414 | copy = _deepCopy(_handlers); 415 | } 416 | return copy; 417 | }; 418 | /** 419 | * The underlying implementation of `ZeroClipboard.emit`. 420 | * @private 421 | */ 422 | var _emit = function(event) { 423 | var eventCopy, returnVal, tmp; 424 | event = _createEvent(event); 425 | if (!event) { 426 | return; 427 | } 428 | if (_preprocessEvent(event)) { 429 | return; 430 | } 431 | if (event.type === "ready" && _flashState.overdue === true) { 432 | return ZeroClipboard.emit({ 433 | type: "error", 434 | name: "flash-overdue" 435 | }); 436 | } 437 | eventCopy = _extend({}, event); 438 | _dispatchCallbacks.call(this, eventCopy); 439 | if (event.type === "copy") { 440 | tmp = _mapClipDataToFlash(_clipData); 441 | returnVal = tmp.data; 442 | _clipDataFormatMap = tmp.formatMap; 443 | } 444 | return returnVal; 445 | }; 446 | /** 447 | * The underlying implementation of `ZeroClipboard.create`. 448 | * @private 449 | */ 450 | var _create = function() { 451 | if (typeof _flashState.ready !== "boolean") { 452 | _flashState.ready = false; 453 | } 454 | if (!ZeroClipboard.isFlashUnusable() && _flashState.bridge === null) { 455 | var maxWait = _globalConfig.flashLoadTimeout; 456 | if (typeof maxWait === "number" && maxWait >= 0) { 457 | _setTimeout(function() { 458 | if (typeof _flashState.deactivated !== "boolean") { 459 | _flashState.deactivated = true; 460 | } 461 | if (_flashState.deactivated === true) { 462 | ZeroClipboard.emit({ 463 | type: "error", 464 | name: "flash-deactivated" 465 | }); 466 | } 467 | }, maxWait); 468 | } 469 | _flashState.overdue = false; 470 | _embedSwf(); 471 | } 472 | }; 473 | /** 474 | * The underlying implementation of `ZeroClipboard.destroy`. 475 | * @private 476 | */ 477 | var _destroy = function() { 478 | ZeroClipboard.clearData(); 479 | ZeroClipboard.blur(); 480 | ZeroClipboard.emit("destroy"); 481 | _unembedSwf(); 482 | ZeroClipboard.off(); 483 | }; 484 | /** 485 | * The underlying implementation of `ZeroClipboard.setData`. 486 | * @private 487 | */ 488 | var _setData = function(format, data) { 489 | var dataObj; 490 | if (typeof format === "object" && format && typeof data === "undefined") { 491 | dataObj = format; 492 | ZeroClipboard.clearData(); 493 | } else if (typeof format === "string" && format) { 494 | dataObj = {}; 495 | dataObj[format] = data; 496 | } else { 497 | return; 498 | } 499 | for (var dataFormat in dataObj) { 500 | if (typeof dataFormat === "string" && dataFormat && _hasOwn.call(dataObj, dataFormat) && typeof dataObj[dataFormat] === "string" && dataObj[dataFormat]) { 501 | _clipData[dataFormat] = dataObj[dataFormat]; 502 | } 503 | } 504 | }; 505 | /** 506 | * The underlying implementation of `ZeroClipboard.clearData`. 507 | * @private 508 | */ 509 | var _clearData = function(format) { 510 | if (typeof format === "undefined") { 511 | _deleteOwnProperties(_clipData); 512 | _clipDataFormatMap = null; 513 | } else if (typeof format === "string" && _hasOwn.call(_clipData, format)) { 514 | delete _clipData[format]; 515 | } 516 | }; 517 | /** 518 | * The underlying implementation of `ZeroClipboard.getData`. 519 | * @private 520 | */ 521 | var _getData = function(format) { 522 | if (typeof format === "undefined") { 523 | return _deepCopy(_clipData); 524 | } else if (typeof format === "string" && _hasOwn.call(_clipData, format)) { 525 | return _clipData[format]; 526 | } 527 | }; 528 | /** 529 | * The underlying implementation of `ZeroClipboard.focus`/`ZeroClipboard.activate`. 530 | * @private 531 | */ 532 | var _focus = function(element) { 533 | if (!(element && element.nodeType === 1)) { 534 | return; 535 | } 536 | if (_currentElement) { 537 | _removeClass(_currentElement, _globalConfig.activeClass); 538 | if (_currentElement !== element) { 539 | _removeClass(_currentElement, _globalConfig.hoverClass); 540 | } 541 | } 542 | _currentElement = element; 543 | _addClass(element, _globalConfig.hoverClass); 544 | var newTitle = element.getAttribute("title") || _globalConfig.title; 545 | if (typeof newTitle === "string" && newTitle) { 546 | var htmlBridge = _getHtmlBridge(_flashState.bridge); 547 | if (htmlBridge) { 548 | htmlBridge.setAttribute("title", newTitle); 549 | } 550 | } 551 | var useHandCursor = _globalConfig.forceHandCursor === true || _getStyle(element, "cursor") === "pointer"; 552 | _setHandCursor(useHandCursor); 553 | _reposition(); 554 | }; 555 | /** 556 | * The underlying implementation of `ZeroClipboard.blur`/`ZeroClipboard.deactivate`. 557 | * @private 558 | */ 559 | var _blur = function() { 560 | var htmlBridge = _getHtmlBridge(_flashState.bridge); 561 | if (htmlBridge) { 562 | htmlBridge.removeAttribute("title"); 563 | htmlBridge.style.left = "0px"; 564 | htmlBridge.style.top = "-9999px"; 565 | htmlBridge.style.width = "1px"; 566 | htmlBridge.style.top = "1px"; 567 | } 568 | if (_currentElement) { 569 | _removeClass(_currentElement, _globalConfig.hoverClass); 570 | _removeClass(_currentElement, _globalConfig.activeClass); 571 | _currentElement = null; 572 | } 573 | }; 574 | /** 575 | * The underlying implementation of `ZeroClipboard.activeElement`. 576 | * @private 577 | */ 578 | var _activeElement = function() { 579 | return _currentElement || null; 580 | }; 581 | /** 582 | * Check if a value is a valid HTML4 `ID` or `Name` token. 583 | * @private 584 | */ 585 | var _isValidHtml4Id = function(id) { 586 | return typeof id === "string" && id && /^[A-Za-z][A-Za-z0-9_:\-\.]*$/.test(id); 587 | }; 588 | /** 589 | * Create or update an `event` object, based on the `eventType`. 590 | * @private 591 | */ 592 | var _createEvent = function(event) { 593 | var eventType; 594 | if (typeof event === "string" && event) { 595 | eventType = event; 596 | event = {}; 597 | } else if (typeof event === "object" && event && typeof event.type === "string" && event.type) { 598 | eventType = event.type; 599 | } 600 | if (!eventType) { 601 | return; 602 | } 603 | _extend(event, { 604 | type: eventType.toLowerCase(), 605 | target: event.target || _currentElement || null, 606 | relatedTarget: event.relatedTarget || null, 607 | currentTarget: _flashState && _flashState.bridge || null, 608 | timeStamp: event.timeStamp || _now() || null 609 | }); 610 | var msg = _eventMessages[event.type]; 611 | if (event.type === "error" && event.name && msg) { 612 | msg = msg[event.name]; 613 | } 614 | if (msg) { 615 | event.message = msg; 616 | } 617 | if (event.type === "ready") { 618 | _extend(event, { 619 | target: null, 620 | version: _flashState.version 621 | }); 622 | } 623 | if (event.type === "error") { 624 | if (/^flash-(disabled|outdated|unavailable|deactivated|overdue)$/.test(event.name)) { 625 | _extend(event, { 626 | target: null, 627 | minimumVersion: _minimumFlashVersion 628 | }); 629 | } 630 | if (/^flash-(outdated|unavailable|deactivated|overdue)$/.test(event.name)) { 631 | _extend(event, { 632 | version: _flashState.version 633 | }); 634 | } 635 | } 636 | if (event.type === "copy") { 637 | event.clipboardData = { 638 | setData: ZeroClipboard.setData, 639 | clearData: ZeroClipboard.clearData 640 | }; 641 | } 642 | if (event.type === "aftercopy") { 643 | event = _mapClipResultsFromFlash(event, _clipDataFormatMap); 644 | } 645 | if (event.target && !event.relatedTarget) { 646 | event.relatedTarget = _getRelatedTarget(event.target); 647 | } 648 | event = _addMouseData(event); 649 | return event; 650 | }; 651 | /** 652 | * Get a relatedTarget from the target's `data-clipboard-target` attribute 653 | * @private 654 | */ 655 | var _getRelatedTarget = function(targetEl) { 656 | var relatedTargetId = targetEl && targetEl.getAttribute && targetEl.getAttribute("data-clipboard-target"); 657 | return relatedTargetId ? _document.getElementById(relatedTargetId) : null; 658 | }; 659 | /** 660 | * Add element and position data to `MouseEvent` instances 661 | * @private 662 | */ 663 | var _addMouseData = function(event) { 664 | if (event && /^_(?:click|mouse(?:over|out|down|up|move))$/.test(event.type)) { 665 | var srcElement = event.target; 666 | var fromElement = event.type === "_mouseover" && event.relatedTarget ? event.relatedTarget : undefined; 667 | var toElement = event.type === "_mouseout" && event.relatedTarget ? event.relatedTarget : undefined; 668 | var pos = _getDOMObjectPosition(srcElement); 669 | var screenLeft = _window.screenLeft || _window.screenX || 0; 670 | var screenTop = _window.screenTop || _window.screenY || 0; 671 | var scrollLeft = _document.body.scrollLeft + _document.documentElement.scrollLeft; 672 | var scrollTop = _document.body.scrollTop + _document.documentElement.scrollTop; 673 | var pageX = pos.left + (typeof event._stageX === "number" ? event._stageX : 0); 674 | var pageY = pos.top + (typeof event._stageY === "number" ? event._stageY : 0); 675 | var clientX = pageX - scrollLeft; 676 | var clientY = pageY - scrollTop; 677 | var screenX = screenLeft + clientX; 678 | var screenY = screenTop + clientY; 679 | var moveX = typeof event.movementX === "number" ? event.movementX : 0; 680 | var moveY = typeof event.movementY === "number" ? event.movementY : 0; 681 | delete event._stageX; 682 | delete event._stageY; 683 | _extend(event, { 684 | srcElement: srcElement, 685 | fromElement: fromElement, 686 | toElement: toElement, 687 | screenX: screenX, 688 | screenY: screenY, 689 | pageX: pageX, 690 | pageY: pageY, 691 | clientX: clientX, 692 | clientY: clientY, 693 | x: clientX, 694 | y: clientY, 695 | movementX: moveX, 696 | movementY: moveY, 697 | offsetX: 0, 698 | offsetY: 0, 699 | layerX: 0, 700 | layerY: 0 701 | }); 702 | } 703 | return event; 704 | }; 705 | /** 706 | * Determine if an event's registered handlers should be execute synchronously or asynchronously. 707 | * 708 | * @returns {boolean} 709 | * @private 710 | */ 711 | var _shouldPerformAsync = function(event) { 712 | var eventType = event && typeof event.type === "string" && event.type || ""; 713 | return !/^(?:(?:before)?copy|destroy)$/.test(eventType); 714 | }; 715 | /** 716 | * Control if a callback should be executed asynchronously or not. 717 | * 718 | * @returns `undefined` 719 | * @private 720 | */ 721 | var _dispatchCallback = function(func, context, args, async) { 722 | if (async) { 723 | _setTimeout(function() { 724 | func.apply(context, args); 725 | }, 0); 726 | } else { 727 | func.apply(context, args); 728 | } 729 | }; 730 | /** 731 | * Handle the actual dispatching of events to client instances. 732 | * 733 | * @returns `undefined` 734 | * @private 735 | */ 736 | var _dispatchCallbacks = function(event) { 737 | if (!(typeof event === "object" && event && event.type)) { 738 | return; 739 | } 740 | var async = _shouldPerformAsync(event); 741 | var wildcardTypeHandlers = _handlers["*"] || []; 742 | var specificTypeHandlers = _handlers[event.type] || []; 743 | var handlers = wildcardTypeHandlers.concat(specificTypeHandlers); 744 | if (handlers && handlers.length) { 745 | var i, len, func, context, eventCopy, originalContext = this; 746 | for (i = 0, len = handlers.length; i < len; i++) { 747 | func = handlers[i]; 748 | context = originalContext; 749 | if (typeof func === "string" && typeof _window[func] === "function") { 750 | func = _window[func]; 751 | } 752 | if (typeof func === "object" && func && typeof func.handleEvent === "function") { 753 | context = func; 754 | func = func.handleEvent; 755 | } 756 | if (typeof func === "function") { 757 | eventCopy = _extend({}, event); 758 | _dispatchCallback(func, context, [ eventCopy ], async); 759 | } 760 | } 761 | } 762 | return this; 763 | }; 764 | /** 765 | * Preprocess any special behaviors, reactions, or state changes after receiving this event. 766 | * Executes only once per event emitted, NOT once per client. 767 | * @private 768 | */ 769 | var _preprocessEvent = function(event) { 770 | var element = event.target || _currentElement || null; 771 | var sourceIsSwf = event._source === "swf"; 772 | delete event._source; 773 | var flashErrorNames = [ "flash-disabled", "flash-outdated", "flash-unavailable", "flash-deactivated", "flash-overdue" ]; 774 | switch (event.type) { 775 | case "error": 776 | if (flashErrorNames.indexOf(event.name) !== -1) { 777 | _extend(_flashState, { 778 | disabled: event.name === "flash-disabled", 779 | outdated: event.name === "flash-outdated", 780 | unavailable: event.name === "flash-unavailable", 781 | deactivated: event.name === "flash-deactivated", 782 | overdue: event.name === "flash-overdue", 783 | ready: false 784 | }); 785 | } 786 | break; 787 | 788 | case "ready": 789 | var wasDeactivated = _flashState.deactivated === true; 790 | _extend(_flashState, { 791 | disabled: false, 792 | outdated: false, 793 | unavailable: false, 794 | deactivated: false, 795 | overdue: wasDeactivated, 796 | ready: !wasDeactivated 797 | }); 798 | break; 799 | 800 | case "copy": 801 | var textContent, htmlContent, targetEl = event.relatedTarget; 802 | if (!(_clipData["text/html"] || _clipData["text/plain"]) && targetEl && (htmlContent = targetEl.value || targetEl.outerHTML || targetEl.innerHTML) && (textContent = targetEl.value || targetEl.textContent || targetEl.innerText)) { 803 | event.clipboardData.clearData(); 804 | event.clipboardData.setData("text/plain", textContent); 805 | if (htmlContent !== textContent) { 806 | event.clipboardData.setData("text/html", htmlContent); 807 | } 808 | } else if (!_clipData["text/plain"] && event.target && (textContent = event.target.getAttribute("data-clipboard-text"))) { 809 | event.clipboardData.clearData(); 810 | event.clipboardData.setData("text/plain", textContent); 811 | } 812 | break; 813 | 814 | case "aftercopy": 815 | ZeroClipboard.clearData(); 816 | if (element && element !== _safeActiveElement() && element.focus) { 817 | element.focus(); 818 | } 819 | break; 820 | 821 | case "_mouseover": 822 | ZeroClipboard.focus(element); 823 | if (_globalConfig.bubbleEvents === true && sourceIsSwf) { 824 | if (element && element !== event.relatedTarget && !_containedBy(event.relatedTarget, element)) { 825 | _fireMouseEvent(_extend({}, event, { 826 | type: "mouseenter", 827 | bubbles: false, 828 | cancelable: false 829 | })); 830 | } 831 | _fireMouseEvent(_extend({}, event, { 832 | type: "mouseover" 833 | })); 834 | } 835 | break; 836 | 837 | case "_mouseout": 838 | ZeroClipboard.blur(); 839 | if (_globalConfig.bubbleEvents === true && sourceIsSwf) { 840 | if (element && element !== event.relatedTarget && !_containedBy(event.relatedTarget, element)) { 841 | _fireMouseEvent(_extend({}, event, { 842 | type: "mouseleave", 843 | bubbles: false, 844 | cancelable: false 845 | })); 846 | } 847 | _fireMouseEvent(_extend({}, event, { 848 | type: "mouseout" 849 | })); 850 | } 851 | break; 852 | 853 | case "_mousedown": 854 | _addClass(element, _globalConfig.activeClass); 855 | if (_globalConfig.bubbleEvents === true && sourceIsSwf) { 856 | _fireMouseEvent(_extend({}, event, { 857 | type: event.type.slice(1) 858 | })); 859 | } 860 | break; 861 | 862 | case "_mouseup": 863 | _removeClass(element, _globalConfig.activeClass); 864 | if (_globalConfig.bubbleEvents === true && sourceIsSwf) { 865 | _fireMouseEvent(_extend({}, event, { 866 | type: event.type.slice(1) 867 | })); 868 | } 869 | break; 870 | 871 | case "_click": 872 | case "_mousemove": 873 | if (_globalConfig.bubbleEvents === true && sourceIsSwf) { 874 | _fireMouseEvent(_extend({}, event, { 875 | type: event.type.slice(1) 876 | })); 877 | } 878 | break; 879 | } 880 | if (/^_(?:click|mouse(?:over|out|down|up|move))$/.test(event.type)) { 881 | return true; 882 | } 883 | }; 884 | /** 885 | * Dispatch a synthetic MouseEvent. 886 | * 887 | * @returns `undefined` 888 | * @private 889 | */ 890 | var _fireMouseEvent = function(event) { 891 | if (!(event && typeof event.type === "string" && event)) { 892 | return; 893 | } 894 | var e, target = event.target || null, doc = target && target.ownerDocument || _document, defaults = { 895 | view: doc.defaultView || _window, 896 | canBubble: true, 897 | cancelable: true, 898 | detail: event.type === "click" ? 1 : 0, 899 | button: typeof event.which === "number" ? event.which - 1 : typeof event.button === "number" ? event.button : doc.createEvent ? 0 : 1 900 | }, args = _extend(defaults, event); 901 | if (!target) { 902 | return; 903 | } 904 | if (doc.createEvent && target.dispatchEvent) { 905 | args = [ args.type, args.canBubble, args.cancelable, args.view, args.detail, args.screenX, args.screenY, args.clientX, args.clientY, args.ctrlKey, args.altKey, args.shiftKey, args.metaKey, args.button, args.relatedTarget ]; 906 | e = doc.createEvent("MouseEvents"); 907 | if (e.initMouseEvent) { 908 | e.initMouseEvent.apply(e, args); 909 | e._source = "js"; 910 | target.dispatchEvent(e); 911 | } 912 | } 913 | }; 914 | /** 915 | * Create the HTML bridge element to embed the Flash object into. 916 | * @private 917 | */ 918 | var _createHtmlBridge = function() { 919 | var container = _document.createElement("div"); 920 | container.id = _globalConfig.containerId; 921 | container.className = _globalConfig.containerClass; 922 | container.style.position = "absolute"; 923 | container.style.left = "0px"; 924 | container.style.top = "-9999px"; 925 | container.style.width = "1px"; 926 | container.style.height = "1px"; 927 | container.style.zIndex = "" + _getSafeZIndex(_globalConfig.zIndex); 928 | return container; 929 | }; 930 | /** 931 | * Get the HTML element container that wraps the Flash bridge object/element. 932 | * @private 933 | */ 934 | var _getHtmlBridge = function(flashBridge) { 935 | var htmlBridge = flashBridge && flashBridge.parentNode; 936 | while (htmlBridge && htmlBridge.nodeName === "OBJECT" && htmlBridge.parentNode) { 937 | htmlBridge = htmlBridge.parentNode; 938 | } 939 | return htmlBridge || null; 940 | }; 941 | /** 942 | * Create the SWF object. 943 | * 944 | * @returns The SWF object reference. 945 | * @private 946 | */ 947 | var _embedSwf = function() { 948 | var len, flashBridge = _flashState.bridge, container = _getHtmlBridge(flashBridge); 949 | if (!flashBridge) { 950 | var allowScriptAccess = _determineScriptAccess(_window.location.host, _globalConfig); 951 | var allowNetworking = allowScriptAccess === "never" ? "none" : "all"; 952 | var flashvars = _vars(_globalConfig); 953 | var swfUrl = _globalConfig.swfPath + _cacheBust(_globalConfig.swfPath, _globalConfig); 954 | container = _createHtmlBridge(); 955 | var divToBeReplaced = _document.createElement("div"); 956 | container.appendChild(divToBeReplaced); 957 | _document.body.appendChild(container); 958 | var tmpDiv = _document.createElement("div"); 959 | var oldIE = _flashState.pluginType === "activex"; 960 | tmpDiv.innerHTML = '" + (oldIE ? '' : "") + '' + '' + '' + '' + '' + ""; 961 | flashBridge = tmpDiv.firstChild; 962 | tmpDiv = null; 963 | flashBridge.ZeroClipboard = ZeroClipboard; 964 | container.replaceChild(flashBridge, divToBeReplaced); 965 | } 966 | if (!flashBridge) { 967 | flashBridge = _document[_globalConfig.swfObjectId]; 968 | if (flashBridge && (len = flashBridge.length)) { 969 | flashBridge = flashBridge[len - 1]; 970 | } 971 | if (!flashBridge && container) { 972 | flashBridge = container.firstChild; 973 | } 974 | } 975 | _flashState.bridge = flashBridge || null; 976 | return flashBridge; 977 | }; 978 | /** 979 | * Destroy the SWF object. 980 | * @private 981 | */ 982 | var _unembedSwf = function() { 983 | var flashBridge = _flashState.bridge; 984 | if (flashBridge) { 985 | var htmlBridge = _getHtmlBridge(flashBridge); 986 | if (htmlBridge) { 987 | if (_flashState.pluginType === "activex" && "readyState" in flashBridge) { 988 | flashBridge.style.display = "none"; 989 | (function removeSwfFromIE() { 990 | if (flashBridge.readyState === 4) { 991 | for (var prop in flashBridge) { 992 | if (typeof flashBridge[prop] === "function") { 993 | flashBridge[prop] = null; 994 | } 995 | } 996 | if (flashBridge.parentNode) { 997 | flashBridge.parentNode.removeChild(flashBridge); 998 | } 999 | if (htmlBridge.parentNode) { 1000 | htmlBridge.parentNode.removeChild(htmlBridge); 1001 | } 1002 | } else { 1003 | _setTimeout(removeSwfFromIE, 10); 1004 | } 1005 | })(); 1006 | } else { 1007 | if (flashBridge.parentNode) { 1008 | flashBridge.parentNode.removeChild(flashBridge); 1009 | } 1010 | if (htmlBridge.parentNode) { 1011 | htmlBridge.parentNode.removeChild(htmlBridge); 1012 | } 1013 | } 1014 | } 1015 | _flashState.ready = null; 1016 | _flashState.bridge = null; 1017 | _flashState.deactivated = null; 1018 | } 1019 | }; 1020 | /** 1021 | * Map the data format names of the "clipData" to Flash-friendly names. 1022 | * 1023 | * @returns A new transformed object. 1024 | * @private 1025 | */ 1026 | var _mapClipDataToFlash = function(clipData) { 1027 | var newClipData = {}, formatMap = {}; 1028 | if (!(typeof clipData === "object" && clipData)) { 1029 | return; 1030 | } 1031 | for (var dataFormat in clipData) { 1032 | if (dataFormat && _hasOwn.call(clipData, dataFormat) && typeof clipData[dataFormat] === "string" && clipData[dataFormat]) { 1033 | switch (dataFormat.toLowerCase()) { 1034 | case "text/plain": 1035 | case "text": 1036 | case "air:text": 1037 | case "flash:text": 1038 | newClipData.text = clipData[dataFormat]; 1039 | formatMap.text = dataFormat; 1040 | break; 1041 | 1042 | case "text/html": 1043 | case "html": 1044 | case "air:html": 1045 | case "flash:html": 1046 | newClipData.html = clipData[dataFormat]; 1047 | formatMap.html = dataFormat; 1048 | break; 1049 | 1050 | case "application/rtf": 1051 | case "text/rtf": 1052 | case "rtf": 1053 | case "richtext": 1054 | case "air:rtf": 1055 | case "flash:rtf": 1056 | newClipData.rtf = clipData[dataFormat]; 1057 | formatMap.rtf = dataFormat; 1058 | break; 1059 | 1060 | default: 1061 | break; 1062 | } 1063 | } 1064 | } 1065 | return { 1066 | data: newClipData, 1067 | formatMap: formatMap 1068 | }; 1069 | }; 1070 | /** 1071 | * Map the data format names from Flash-friendly names back to their original "clipData" names (via a format mapping). 1072 | * 1073 | * @returns A new transformed object. 1074 | * @private 1075 | */ 1076 | var _mapClipResultsFromFlash = function(clipResults, formatMap) { 1077 | if (!(typeof clipResults === "object" && clipResults && typeof formatMap === "object" && formatMap)) { 1078 | return clipResults; 1079 | } 1080 | var newResults = {}; 1081 | for (var prop in clipResults) { 1082 | if (_hasOwn.call(clipResults, prop)) { 1083 | if (prop !== "success" && prop !== "data") { 1084 | newResults[prop] = clipResults[prop]; 1085 | continue; 1086 | } 1087 | newResults[prop] = {}; 1088 | var tmpHash = clipResults[prop]; 1089 | for (var dataFormat in tmpHash) { 1090 | if (dataFormat && _hasOwn.call(tmpHash, dataFormat) && _hasOwn.call(formatMap, dataFormat)) { 1091 | newResults[prop][formatMap[dataFormat]] = tmpHash[dataFormat]; 1092 | } 1093 | } 1094 | } 1095 | } 1096 | return newResults; 1097 | }; 1098 | /** 1099 | * Will look at a path, and will create a "?noCache={time}" or "&noCache={time}" 1100 | * query param string to return. Does NOT append that string to the original path. 1101 | * This is useful because ExternalInterface often breaks when a Flash SWF is cached. 1102 | * 1103 | * @returns The `noCache` query param with necessary "?"/"&" prefix. 1104 | * @private 1105 | */ 1106 | var _cacheBust = function(path, options) { 1107 | var cacheBust = options == null || options && options.cacheBust === true; 1108 | if (cacheBust) { 1109 | return (path.indexOf("?") === -1 ? "?" : "&") + "noCache=" + _now(); 1110 | } else { 1111 | return ""; 1112 | } 1113 | }; 1114 | /** 1115 | * Creates a query string for the FlashVars param. 1116 | * Does NOT include the cache-busting query param. 1117 | * 1118 | * @returns FlashVars query string 1119 | * @private 1120 | */ 1121 | var _vars = function(options) { 1122 | var i, len, domain, domains, str = "", trustedOriginsExpanded = []; 1123 | if (options.trustedDomains) { 1124 | if (typeof options.trustedDomains === "string") { 1125 | domains = [ options.trustedDomains ]; 1126 | } else if (typeof options.trustedDomains === "object" && "length" in options.trustedDomains) { 1127 | domains = options.trustedDomains; 1128 | } 1129 | } 1130 | if (domains && domains.length) { 1131 | for (i = 0, len = domains.length; i < len; i++) { 1132 | if (_hasOwn.call(domains, i) && domains[i] && typeof domains[i] === "string") { 1133 | domain = _extractDomain(domains[i]); 1134 | if (!domain) { 1135 | continue; 1136 | } 1137 | if (domain === "*") { 1138 | trustedOriginsExpanded.length = 0; 1139 | trustedOriginsExpanded.push(domain); 1140 | break; 1141 | } 1142 | trustedOriginsExpanded.push.apply(trustedOriginsExpanded, [ domain, "//" + domain, _window.location.protocol + "//" + domain ]); 1143 | } 1144 | } 1145 | } 1146 | if (trustedOriginsExpanded.length) { 1147 | str += "trustedOrigins=" + _encodeURIComponent(trustedOriginsExpanded.join(",")); 1148 | } 1149 | if (options.forceEnhancedClipboard === true) { 1150 | str += (str ? "&" : "") + "forceEnhancedClipboard=true"; 1151 | } 1152 | if (typeof options.swfObjectId === "string" && options.swfObjectId) { 1153 | str += (str ? "&" : "") + "swfObjectId=" + _encodeURIComponent(options.swfObjectId); 1154 | } 1155 | return str; 1156 | }; 1157 | /** 1158 | * Extract the domain (e.g. "github.com") from an origin (e.g. "https://github.com") or 1159 | * URL (e.g. "https://github.com/zeroclipboard/zeroclipboard/"). 1160 | * 1161 | * @returns the domain 1162 | * @private 1163 | */ 1164 | var _extractDomain = function(originOrUrl) { 1165 | if (originOrUrl == null || originOrUrl === "") { 1166 | return null; 1167 | } 1168 | originOrUrl = originOrUrl.replace(/^\s+|\s+$/g, ""); 1169 | if (originOrUrl === "") { 1170 | return null; 1171 | } 1172 | var protocolIndex = originOrUrl.indexOf("//"); 1173 | originOrUrl = protocolIndex === -1 ? originOrUrl : originOrUrl.slice(protocolIndex + 2); 1174 | var pathIndex = originOrUrl.indexOf("/"); 1175 | originOrUrl = pathIndex === -1 ? originOrUrl : protocolIndex === -1 || pathIndex === 0 ? null : originOrUrl.slice(0, pathIndex); 1176 | if (originOrUrl && originOrUrl.slice(-4).toLowerCase() === ".swf") { 1177 | return null; 1178 | } 1179 | return originOrUrl || null; 1180 | }; 1181 | /** 1182 | * Set `allowScriptAccess` based on `trustedDomains` and `window.location.host` vs. `swfPath`. 1183 | * 1184 | * @returns The appropriate script access level. 1185 | * @private 1186 | */ 1187 | var _determineScriptAccess = function() { 1188 | var _extractAllDomains = function(origins) { 1189 | var i, len, tmp, resultsArray = []; 1190 | if (typeof origins === "string") { 1191 | origins = [ origins ]; 1192 | } 1193 | if (!(typeof origins === "object" && origins && typeof origins.length === "number")) { 1194 | return resultsArray; 1195 | } 1196 | for (i = 0, len = origins.length; i < len; i++) { 1197 | if (_hasOwn.call(origins, i) && (tmp = _extractDomain(origins[i]))) { 1198 | if (tmp === "*") { 1199 | resultsArray.length = 0; 1200 | resultsArray.push("*"); 1201 | break; 1202 | } 1203 | if (resultsArray.indexOf(tmp) === -1) { 1204 | resultsArray.push(tmp); 1205 | } 1206 | } 1207 | } 1208 | return resultsArray; 1209 | }; 1210 | return function(currentDomain, configOptions) { 1211 | var swfDomain = _extractDomain(configOptions.swfPath); 1212 | if (swfDomain === null) { 1213 | swfDomain = currentDomain; 1214 | } 1215 | var trustedDomains = _extractAllDomains(configOptions.trustedDomains); 1216 | var len = trustedDomains.length; 1217 | if (len > 0) { 1218 | if (len === 1 && trustedDomains[0] === "*") { 1219 | return "always"; 1220 | } 1221 | if (trustedDomains.indexOf(currentDomain) !== -1) { 1222 | if (len === 1 && currentDomain === swfDomain) { 1223 | return "sameDomain"; 1224 | } 1225 | return "always"; 1226 | } 1227 | } 1228 | return "never"; 1229 | }; 1230 | }(); 1231 | /** 1232 | * Get the currently active/focused DOM element. 1233 | * 1234 | * @returns the currently active/focused element, or `null` 1235 | * @private 1236 | */ 1237 | var _safeActiveElement = function() { 1238 | try { 1239 | return _document.activeElement; 1240 | } catch (err) { 1241 | return null; 1242 | } 1243 | }; 1244 | /** 1245 | * Add a class to an element, if it doesn't already have it. 1246 | * 1247 | * @returns The element, with its new class added. 1248 | * @private 1249 | */ 1250 | var _addClass = function(element, value) { 1251 | if (!element || element.nodeType !== 1) { 1252 | return element; 1253 | } 1254 | if (element.classList) { 1255 | if (!element.classList.contains(value)) { 1256 | element.classList.add(value); 1257 | } 1258 | return element; 1259 | } 1260 | if (value && typeof value === "string") { 1261 | var classNames = (value || "").split(/\s+/); 1262 | if (element.nodeType === 1) { 1263 | if (!element.className) { 1264 | element.className = value; 1265 | } else { 1266 | var className = " " + element.className + " ", setClass = element.className; 1267 | for (var c = 0, cl = classNames.length; c < cl; c++) { 1268 | if (className.indexOf(" " + classNames[c] + " ") < 0) { 1269 | setClass += " " + classNames[c]; 1270 | } 1271 | } 1272 | element.className = setClass.replace(/^\s+|\s+$/g, ""); 1273 | } 1274 | } 1275 | } 1276 | return element; 1277 | }; 1278 | /** 1279 | * Remove a class from an element, if it has it. 1280 | * 1281 | * @returns The element, with its class removed. 1282 | * @private 1283 | */ 1284 | var _removeClass = function(element, value) { 1285 | if (!element || element.nodeType !== 1) { 1286 | return element; 1287 | } 1288 | if (element.classList) { 1289 | if (element.classList.contains(value)) { 1290 | element.classList.remove(value); 1291 | } 1292 | return element; 1293 | } 1294 | if (typeof value === "string" && value) { 1295 | var classNames = value.split(/\s+/); 1296 | if (element.nodeType === 1 && element.className) { 1297 | var className = (" " + element.className + " ").replace(/[\n\t]/g, " "); 1298 | for (var c = 0, cl = classNames.length; c < cl; c++) { 1299 | className = className.replace(" " + classNames[c] + " ", " "); 1300 | } 1301 | element.className = className.replace(/^\s+|\s+$/g, ""); 1302 | } 1303 | } 1304 | return element; 1305 | }; 1306 | /** 1307 | * Attempt to interpret the element's CSS styling. If `prop` is `"cursor"`, 1308 | * then we assume that it should be a hand ("pointer") cursor if the element 1309 | * is an anchor element ("a" tag). 1310 | * 1311 | * @returns The computed style property. 1312 | * @private 1313 | */ 1314 | var _getStyle = function(el, prop) { 1315 | var value = _window.getComputedStyle(el, null).getPropertyValue(prop); 1316 | if (prop === "cursor") { 1317 | if (!value || value === "auto") { 1318 | if (el.nodeName === "A") { 1319 | return "pointer"; 1320 | } 1321 | } 1322 | } 1323 | return value; 1324 | }; 1325 | /** 1326 | * Get the zoom factor of the browser. Always returns `1.0`, except at 1327 | * non-default zoom levels in IE<8 and some older versions of WebKit. 1328 | * 1329 | * @returns Floating unit percentage of the zoom factor (e.g. 150% = `1.5`). 1330 | * @private 1331 | */ 1332 | var _getZoomFactor = function() { 1333 | var rect, physicalWidth, logicalWidth, zoomFactor = 1; 1334 | if (typeof _document.body.getBoundingClientRect === "function") { 1335 | rect = _document.body.getBoundingClientRect(); 1336 | physicalWidth = rect.right - rect.left; 1337 | logicalWidth = _document.body.offsetWidth; 1338 | zoomFactor = _round(physicalWidth / logicalWidth * 100) / 100; 1339 | } 1340 | return zoomFactor; 1341 | }; 1342 | /** 1343 | * Get the DOM positioning info of an element. 1344 | * 1345 | * @returns Object containing the element's position, width, and height. 1346 | * @private 1347 | */ 1348 | var _getDOMObjectPosition = function(obj) { 1349 | var info = { 1350 | left: 0, 1351 | top: 0, 1352 | width: 0, 1353 | height: 0 1354 | }; 1355 | if (obj.getBoundingClientRect) { 1356 | var rect = obj.getBoundingClientRect(); 1357 | var pageXOffset, pageYOffset, zoomFactor; 1358 | if ("pageXOffset" in _window && "pageYOffset" in _window) { 1359 | pageXOffset = _window.pageXOffset; 1360 | pageYOffset = _window.pageYOffset; 1361 | } else { 1362 | zoomFactor = _getZoomFactor(); 1363 | pageXOffset = _round(_document.documentElement.scrollLeft / zoomFactor); 1364 | pageYOffset = _round(_document.documentElement.scrollTop / zoomFactor); 1365 | } 1366 | var leftBorderWidth = _document.documentElement.clientLeft || 0; 1367 | var topBorderWidth = _document.documentElement.clientTop || 0; 1368 | info.left = rect.left + pageXOffset - leftBorderWidth; 1369 | info.top = rect.top + pageYOffset - topBorderWidth; 1370 | info.width = "width" in rect ? rect.width : rect.right - rect.left; 1371 | info.height = "height" in rect ? rect.height : rect.bottom - rect.top; 1372 | } 1373 | return info; 1374 | }; 1375 | /** 1376 | * Reposition the Flash object to cover the currently activated element. 1377 | * 1378 | * @returns `undefined` 1379 | * @private 1380 | */ 1381 | var _reposition = function() { 1382 | var htmlBridge; 1383 | if (_currentElement && (htmlBridge = _getHtmlBridge(_flashState.bridge))) { 1384 | var pos = _getDOMObjectPosition(_currentElement); 1385 | _extend(htmlBridge.style, { 1386 | width: pos.width + "px", 1387 | height: pos.height + "px", 1388 | top: pos.top + "px", 1389 | left: pos.left + "px", 1390 | zIndex: "" + _getSafeZIndex(_globalConfig.zIndex) 1391 | }); 1392 | } 1393 | }; 1394 | /** 1395 | * Sends a signal to the Flash object to display the hand cursor if `true`. 1396 | * 1397 | * @returns `undefined` 1398 | * @private 1399 | */ 1400 | var _setHandCursor = function(enabled) { 1401 | if (_flashState.ready === true) { 1402 | if (_flashState.bridge && typeof _flashState.bridge.setHandCursor === "function") { 1403 | _flashState.bridge.setHandCursor(enabled); 1404 | } else { 1405 | _flashState.ready = false; 1406 | } 1407 | } 1408 | }; 1409 | /** 1410 | * Get a safe value for `zIndex` 1411 | * 1412 | * @returns an integer, or "auto" 1413 | * @private 1414 | */ 1415 | var _getSafeZIndex = function(val) { 1416 | if (/^(?:auto|inherit)$/.test(val)) { 1417 | return val; 1418 | } 1419 | var zIndex; 1420 | if (typeof val === "number" && !_isNaN(val)) { 1421 | zIndex = val; 1422 | } else if (typeof val === "string") { 1423 | zIndex = _getSafeZIndex(_parseInt(val, 10)); 1424 | } 1425 | return typeof zIndex === "number" ? zIndex : "auto"; 1426 | }; 1427 | /** 1428 | * Detect the Flash Player status, version, and plugin type. 1429 | * 1430 | * @see {@link https://code.google.com/p/doctype-mirror/wiki/ArticleDetectFlash#The_code} 1431 | * @see {@link http://stackoverflow.com/questions/12866060/detecting-pepper-ppapi-flash-with-javascript} 1432 | * 1433 | * @returns `undefined` 1434 | * @private 1435 | */ 1436 | var _detectFlashSupport = function(ActiveXObject) { 1437 | var plugin, ax, mimeType, hasFlash = false, isActiveX = false, isPPAPI = false, flashVersion = ""; 1438 | /** 1439 | * Derived from Apple's suggested sniffer. 1440 | * @param {String} desc e.g. "Shockwave Flash 7.0 r61" 1441 | * @returns {String} "7.0.61" 1442 | * @private 1443 | */ 1444 | function parseFlashVersion(desc) { 1445 | var matches = desc.match(/[\d]+/g); 1446 | matches.length = 3; 1447 | return matches.join("."); 1448 | } 1449 | function isPepperFlash(flashPlayerFileName) { 1450 | return !!flashPlayerFileName && (flashPlayerFileName = flashPlayerFileName.toLowerCase()) && (/^(pepflashplayer\.dll|libpepflashplayer\.so|pepperflashplayer\.plugin)$/.test(flashPlayerFileName) || flashPlayerFileName.slice(-13) === "chrome.plugin"); 1451 | } 1452 | function inspectPlugin(plugin) { 1453 | if (plugin) { 1454 | hasFlash = true; 1455 | if (plugin.version) { 1456 | flashVersion = parseFlashVersion(plugin.version); 1457 | } 1458 | if (!flashVersion && plugin.description) { 1459 | flashVersion = parseFlashVersion(plugin.description); 1460 | } 1461 | if (plugin.filename) { 1462 | isPPAPI = isPepperFlash(plugin.filename); 1463 | } 1464 | } 1465 | } 1466 | if (_navigator.plugins && _navigator.plugins.length) { 1467 | plugin = _navigator.plugins["Shockwave Flash"]; 1468 | inspectPlugin(plugin); 1469 | if (_navigator.plugins["Shockwave Flash 2.0"]) { 1470 | hasFlash = true; 1471 | flashVersion = "2.0.0.11"; 1472 | } 1473 | } else if (_navigator.mimeTypes && _navigator.mimeTypes.length) { 1474 | mimeType = _navigator.mimeTypes["application/x-shockwave-flash"]; 1475 | plugin = mimeType && mimeType.enabledPlugin; 1476 | inspectPlugin(plugin); 1477 | } else if (typeof ActiveXObject !== "undefined") { 1478 | isActiveX = true; 1479 | try { 1480 | ax = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); 1481 | hasFlash = true; 1482 | flashVersion = parseFlashVersion(ax.GetVariable("$version")); 1483 | } catch (e1) { 1484 | try { 1485 | ax = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); 1486 | hasFlash = true; 1487 | flashVersion = "6.0.21"; 1488 | } catch (e2) { 1489 | try { 1490 | ax = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); 1491 | hasFlash = true; 1492 | flashVersion = parseFlashVersion(ax.GetVariable("$version")); 1493 | } catch (e3) { 1494 | isActiveX = false; 1495 | } 1496 | } 1497 | } 1498 | } 1499 | _flashState.disabled = hasFlash !== true; 1500 | _flashState.outdated = flashVersion && _parseFloat(flashVersion) < _parseFloat(_minimumFlashVersion); 1501 | _flashState.version = flashVersion || "0.0.0"; 1502 | _flashState.pluginType = isPPAPI ? "pepper" : isActiveX ? "activex" : hasFlash ? "netscape" : "unknown"; 1503 | }; 1504 | /** 1505 | * Invoke the Flash detection algorithms immediately upon inclusion so we're not waiting later. 1506 | */ 1507 | _detectFlashSupport(_ActiveXObject); 1508 | /** 1509 | * A shell constructor for `ZeroClipboard` client instances. 1510 | * 1511 | * @constructor 1512 | */ 1513 | var ZeroClipboard = function() { 1514 | if (!(this instanceof ZeroClipboard)) { 1515 | return new ZeroClipboard(); 1516 | } 1517 | if (typeof ZeroClipboard._createClient === "function") { 1518 | ZeroClipboard._createClient.apply(this, _args(arguments)); 1519 | } 1520 | }; 1521 | /** 1522 | * The ZeroClipboard library's version number. 1523 | * 1524 | * @static 1525 | * @readonly 1526 | * @property {string} 1527 | */ 1528 | _defineProperty(ZeroClipboard, "version", { 1529 | value: "2.1.2", 1530 | writable: false, 1531 | configurable: true, 1532 | enumerable: true 1533 | }); 1534 | /** 1535 | * Update or get a copy of the ZeroClipboard global configuration. 1536 | * Returns a copy of the current/updated configuration. 1537 | * 1538 | * @returns Object 1539 | * @static 1540 | */ 1541 | ZeroClipboard.config = function() { 1542 | return _config.apply(this, _args(arguments)); 1543 | }; 1544 | /** 1545 | * Diagnostic method that describes the state of the browser, Flash Player, and ZeroClipboard. 1546 | * 1547 | * @returns Object 1548 | * @static 1549 | */ 1550 | ZeroClipboard.state = function() { 1551 | return _state.apply(this, _args(arguments)); 1552 | }; 1553 | /** 1554 | * Check if Flash is unusable for any reason: disabled, outdated, deactivated, etc. 1555 | * 1556 | * @returns Boolean 1557 | * @static 1558 | */ 1559 | ZeroClipboard.isFlashUnusable = function() { 1560 | return _isFlashUnusable.apply(this, _args(arguments)); 1561 | }; 1562 | /** 1563 | * Register an event listener. 1564 | * 1565 | * @returns `ZeroClipboard` 1566 | * @static 1567 | */ 1568 | ZeroClipboard.on = function() { 1569 | return _on.apply(this, _args(arguments)); 1570 | }; 1571 | /** 1572 | * Unregister an event listener. 1573 | * If no `listener` function/object is provided, it will unregister all listeners for the provided `eventType`. 1574 | * If no `eventType` is provided, it will unregister all listeners for every event type. 1575 | * 1576 | * @returns `ZeroClipboard` 1577 | * @static 1578 | */ 1579 | ZeroClipboard.off = function() { 1580 | return _off.apply(this, _args(arguments)); 1581 | }; 1582 | /** 1583 | * Retrieve event listeners for an `eventType`. 1584 | * If no `eventType` is provided, it will retrieve all listeners for every event type. 1585 | * 1586 | * @returns array of listeners for the `eventType`; if no `eventType`, then a map/hash object of listeners for all event types; or `null` 1587 | */ 1588 | ZeroClipboard.handlers = function() { 1589 | return _listeners.apply(this, _args(arguments)); 1590 | }; 1591 | /** 1592 | * Event emission receiver from the Flash object, forwarding to any registered JavaScript event listeners. 1593 | * 1594 | * @returns For the "copy" event, returns the Flash-friendly "clipData" object; otherwise `undefined`. 1595 | * @static 1596 | */ 1597 | ZeroClipboard.emit = function() { 1598 | return _emit.apply(this, _args(arguments)); 1599 | }; 1600 | /** 1601 | * Create and embed the Flash object. 1602 | * 1603 | * @returns The Flash object 1604 | * @static 1605 | */ 1606 | ZeroClipboard.create = function() { 1607 | return _create.apply(this, _args(arguments)); 1608 | }; 1609 | /** 1610 | * Self-destruct and clean up everything, including the embedded Flash object. 1611 | * 1612 | * @returns `undefined` 1613 | * @static 1614 | */ 1615 | ZeroClipboard.destroy = function() { 1616 | return _destroy.apply(this, _args(arguments)); 1617 | }; 1618 | /** 1619 | * Set the pending data for clipboard injection. 1620 | * 1621 | * @returns `undefined` 1622 | * @static 1623 | */ 1624 | ZeroClipboard.setData = function() { 1625 | return _setData.apply(this, _args(arguments)); 1626 | }; 1627 | /** 1628 | * Clear the pending data for clipboard injection. 1629 | * If no `format` is provided, all pending data formats will be cleared. 1630 | * 1631 | * @returns `undefined` 1632 | * @static 1633 | */ 1634 | ZeroClipboard.clearData = function() { 1635 | return _clearData.apply(this, _args(arguments)); 1636 | }; 1637 | /** 1638 | * Get a copy of the pending data for clipboard injection. 1639 | * If no `format` is provided, a copy of ALL pending data formats will be returned. 1640 | * 1641 | * @returns `String` or `Object` 1642 | * @static 1643 | */ 1644 | ZeroClipboard.getData = function() { 1645 | return _getData.apply(this, _args(arguments)); 1646 | }; 1647 | /** 1648 | * Sets the current HTML object that the Flash object should overlay. This will put the global 1649 | * Flash object on top of the current element; depending on the setup, this may also set the 1650 | * pending clipboard text data as well as the Flash object's wrapping element's title attribute 1651 | * based on the underlying HTML element and ZeroClipboard configuration. 1652 | * 1653 | * @returns `undefined` 1654 | * @static 1655 | */ 1656 | ZeroClipboard.focus = ZeroClipboard.activate = function() { 1657 | return _focus.apply(this, _args(arguments)); 1658 | }; 1659 | /** 1660 | * Un-overlays the Flash object. This will put the global Flash object off-screen; depending on 1661 | * the setup, this may also unset the Flash object's wrapping element's title attribute based on 1662 | * the underlying HTML element and ZeroClipboard configuration. 1663 | * 1664 | * @returns `undefined` 1665 | * @static 1666 | */ 1667 | ZeroClipboard.blur = ZeroClipboard.deactivate = function() { 1668 | return _blur.apply(this, _args(arguments)); 1669 | }; 1670 | /** 1671 | * Returns the currently focused/"activated" HTML element that the Flash object is wrapping. 1672 | * 1673 | * @returns `HTMLElement` or `null` 1674 | * @static 1675 | */ 1676 | ZeroClipboard.activeElement = function() { 1677 | return _activeElement.apply(this, _args(arguments)); 1678 | }; 1679 | if (typeof define === "function" && define.amd) { 1680 | define(function() { 1681 | return ZeroClipboard; 1682 | }); 1683 | } else if (typeof module === "object" && module && typeof module.exports === "object" && module.exports) { 1684 | module.exports = ZeroClipboard; 1685 | } else { 1686 | window.ZeroClipboard = ZeroClipboard; 1687 | } 1688 | })(function() { 1689 | return this || window; 1690 | }()); 1691 | (function($, window, undefined) { 1692 | var mouseEnterBindingCount = 0, customEventNamespace = ".zeroclipboard", ZeroClipboard = window.ZeroClipboard, _trustedDomains = ZeroClipboard.config("trustedDomains"); 1693 | function getSelectionData() { 1694 | var range, selectedText = "", selectedData = {}, sel = window.getSelection(), tmp = document.createElement("div"); 1695 | for (var i = 0, len = sel.rangeCount; i < len; i++) { 1696 | range = sel.getRangeAt(i); 1697 | selectedText += range.toString(); 1698 | tmp.appendChild(range.cloneContents()); 1699 | } 1700 | selectedData["text/plain"] = selectedText; 1701 | if (selectedText.replace(/\s/g, "")) { 1702 | selectedData["text/html"] = tmp.innerHTML; 1703 | } 1704 | return selectedData; 1705 | } 1706 | function convertHtmlToRtf(html) { 1707 | if (!(typeof html === "string" && html)) { 1708 | return null; 1709 | } 1710 | var tmpRichText, hasHyperlinks, richText = html; 1711 | richText = richText.replace(/<(?:hr)(?:\s+[^>]*)?\s*[\/]?>/gi, "{\\pard \\brdrb \\brdrs \\brdrw10 \\brsp20 \\par}\n{\\pard\\par}\n"); 1712 | richText = richText.replace(/<(?:br)(?:\s+[^>]*)?\s*[\/]?>/gi, "{\\pard\\par}\n"); 1713 | richText = richText.replace(/<(?:p|div|section|article)(?:\s+[^>]*)?\s*[\/]>/gi, "{\\pard\\par}\n"); 1714 | richText = richText.replace(/<(?:[^>]+)\/>/g, ""); 1715 | richText = richText.replace(/]*)?(?:\s+href=(["'])(?:javascript:void\(0?\);?|#|return false;?|void\(0?\);?|)\1)(?:\s+[^>]*)?>/gi, "{{{\n"); 1716 | tmpRichText = richText; 1717 | richText = richText.replace(/]*)?(?:\s+href=(["'])(.+)\1)(?:\s+[^>]*)?>/gi, '{\\field{\\*\\fldinst{HYPERLINK\n "$2"\n}}{\\fldrslt{\\ul\\cf1\n'); 1718 | hasHyperlinks = richText !== tmpRichText; 1719 | richText = richText.replace(/]*)?>/gi, "{{{\n"); 1720 | richText = richText.replace(/<\/a(?:\s+[^>]*)?>/gi, "\n}}}"); 1721 | richText = richText.replace(/<(?:b|strong)(?:\s+[^>]*)?>/gi, "{\\b\n"); 1722 | richText = richText.replace(/<(?:i|em)(?:\s+[^>]*)?>/gi, "{\\i\n"); 1723 | richText = richText.replace(/<(?:u|ins)(?:\s+[^>]*)?>/gi, "{\\ul\n"); 1724 | richText = richText.replace(/<(?:strike|del)(?:\s+[^>]*)?>/gi, "{\\strike\n"); 1725 | richText = richText.replace(/]*)?>/gi, "{\\super\n"); 1726 | richText = richText.replace(/]*)?>/gi, "{\\sub\n"); 1727 | richText = richText.replace(/<(?:p|div|section|article)(?:\s+[^>]*)?>/gi, "{\\pard\n"); 1728 | richText = richText.replace(/<\/(?:p|div|section|article)(?:\s+[^>]*)?>/gi, "\n\\par}\n"); 1729 | richText = richText.replace(/<\/(?:b|strong|i|em|u|ins|strike|del|sup|sub)(?:\s+[^>]*)?>/gi, "\n}"); 1730 | richText = richText.replace(/<(?:[^>]+)>/g, ""); 1731 | richText = "{\\rtf1\\ansi\n" + (hasHyperlinks ? "{\\colortbl\n;\n\\red0\\green0\\blue255;\n}\n" : "") + richText + "\n}"; 1732 | return richText; 1733 | } 1734 | function zcEventHandler(e) { 1735 | var $event = $.Event(e.type, $.extend(e, { 1736 | _source: "swf" 1737 | })); 1738 | $(e.target).trigger($event); 1739 | if ($event.type === "copy") { 1740 | if ($.event.special.copy.options.requirePreventDefault === true && !$event.isDefaultPrevented()) { 1741 | e.clipboardData.clearData(); 1742 | var selectionData = getSelectionData(); 1743 | if (selectionData["text/plain"] || selectionData["text/html"]) { 1744 | e.clipboardData.setData(selectionData); 1745 | } 1746 | } 1747 | var _clipData = ZeroClipboard.getData(); 1748 | if ($.event.special.copy.options.autoConvertHtmlToRtf === true && _clipData["text/html"] && !_clipData["application/rtf"]) { 1749 | var richText = convertHtmlToRtf(_clipData["text/html"]); 1750 | e.clipboardData.setData("application/rtf", richText); 1751 | } 1752 | } 1753 | } 1754 | function zcErrorHandler(e) { 1755 | var $event = $.Event("copy-error", $.extend(e, { 1756 | type: "copy-error", 1757 | _source: "swf" 1758 | })); 1759 | $(e.target).trigger($event); 1760 | } 1761 | function setup() { 1762 | $.event.props.push("clipboardData"); 1763 | ZeroClipboard.config($.extend(true, { 1764 | autoActivate: false 1765 | }, copyEventDef.options)); 1766 | ZeroClipboard.on("beforecopy copy aftercopy", zcEventHandler); 1767 | ZeroClipboard.on("error", zcErrorHandler); 1768 | ZeroClipboard.create(); 1769 | } 1770 | function teardown() { 1771 | ZeroClipboard.destroy(); 1772 | var indy = $.event.props.indexOf("clipboardData"); 1773 | if (indy !== -1) { 1774 | $.event.props.splice(indy, 1); 1775 | } 1776 | } 1777 | function mouseEnterHandler($event) { 1778 | mouseSuppressor($event); 1779 | if ($event.target && $event.target !== ZeroClipboard.activeElement() && $event.target !== $("#" + ZeroClipboard.config("containerId"))[0] && $event.target !== $("#" + ZeroClipboard.config("swfObjectId"))[0]) { 1780 | ZeroClipboard.focus($event.target); 1781 | } 1782 | } 1783 | function mouseLeaveHandler($event) { 1784 | mouseSuppressor($event); 1785 | if ($event.relatedTarget && $event.relatedTarget !== ZeroClipboard.activeElement() && $event.relatedTarget !== $("#" + ZeroClipboard.config("containerId"))[0] && $event.relatedTarget !== $("#" + ZeroClipboard.config("swfObjectId"))[0]) { 1786 | ZeroClipboard.blur(); 1787 | } 1788 | } 1789 | function mouseSuppressor($event) { 1790 | if (!ZeroClipboard.isFlashUnusable() && $event.originalEvent._source !== "js") { 1791 | $event.stopImmediatePropagation(); 1792 | $event.preventDefault(); 1793 | } 1794 | } 1795 | var copyEventDef = { 1796 | add: function(handleObj) { 1797 | if (0 === mouseEnterBindingCount++) { 1798 | setup(); 1799 | } 1800 | var namespaces = customEventNamespace + (handleObj.namespace ? "." + handleObj.namespace : ""), selector = handleObj.selector, zcDataKey = "zc|{" + selector + "}|{" + namespaces + "}|count", $this = $(this); 1801 | if (typeof $this.data(zcDataKey) !== "number") { 1802 | $this.data(zcDataKey, 0); 1803 | } 1804 | if ($this.data(zcDataKey) === 0) { 1805 | $this.on("mouseenter" + namespaces, selector, mouseEnterHandler); 1806 | $this.on("mouseleave" + namespaces, selector, mouseLeaveHandler); 1807 | $this.on("mouseover" + namespaces, selector, mouseSuppressor); 1808 | $this.on("mouseout" + namespaces, selector, mouseSuppressor); 1809 | $this.on("mousemove" + namespaces, selector, mouseSuppressor); 1810 | $this.on("mousedown" + namespaces, selector, mouseSuppressor); 1811 | $this.on("mouseup" + namespaces, selector, mouseSuppressor); 1812 | $this.on("click" + namespaces, selector, mouseSuppressor); 1813 | } 1814 | $this.data(zcDataKey, $this.data(zcDataKey) + 1); 1815 | }, 1816 | remove: function(handleObj) { 1817 | var namespaces = customEventNamespace + (handleObj.namespace ? "." + handleObj.namespace : ""), selector = handleObj.selector, zcDataKey = "zc|{" + selector + "}|{" + namespaces + "}|count", $this = $(this); 1818 | $this.data(zcDataKey, $this.data(zcDataKey) - 1); 1819 | if ($this.data(zcDataKey) === 0) { 1820 | $this.off("click" + namespaces, selector, mouseSuppressor); 1821 | $this.off("mouseup" + namespaces, selector, mouseSuppressor); 1822 | $this.off("mousedown" + namespaces, selector, mouseSuppressor); 1823 | $this.off("mousemove" + namespaces, selector, mouseSuppressor); 1824 | $this.off("mouseout" + namespaces, selector, mouseSuppressor); 1825 | $this.off("mouseover" + namespaces, selector, mouseSuppressor); 1826 | $this.off("mouseleave" + namespaces, selector, mouseLeaveHandler); 1827 | $this.off("mouseenter" + namespaces, selector, mouseEnterHandler); 1828 | $this.removeData(zcDataKey); 1829 | } 1830 | if (0 === --mouseEnterBindingCount) { 1831 | teardown(); 1832 | } 1833 | }, 1834 | trigger: function($event) { 1835 | if ($event.type === "copy") { 1836 | var $this = $(this); 1837 | var sourceIsSwf = $event._source === "swf"; 1838 | delete $event._source; 1839 | if (!sourceIsSwf) { 1840 | $this.trigger($.extend(true, {}, $event, { 1841 | type: "beforecopy" 1842 | })); 1843 | $this.one("copy", function() { 1844 | var successData = {}, _clipData = ZeroClipboard.getData(); 1845 | $.each(_clipData, function(key) { 1846 | successData[key] = false; 1847 | }); 1848 | var $e = $.extend(true, {}, $event, { 1849 | type: "aftercopy", 1850 | data: $.extend(true, {}, _clipData), 1851 | success: successData 1852 | }); 1853 | $this.trigger($e); 1854 | }); 1855 | } 1856 | } 1857 | }, 1858 | _default: function() { 1859 | return true; 1860 | }, 1861 | options: { 1862 | requirePreventDefault: true, 1863 | autoConvertHtmlToRtf: true, 1864 | trustedDomains: _trustedDomains, 1865 | hoverClass: "hover", 1866 | activeClass: "active" 1867 | } 1868 | }; 1869 | $.event.special.beforecopy = copyEventDef; 1870 | $.event.special.copy = copyEventDef; 1871 | $.event.special.aftercopy = copyEventDef; 1872 | $.event.special["copy-error"] = copyEventDef; 1873 | })(jQuery, function() { 1874 | return this || window; 1875 | }()); 1876 | if (!zcExistsAlready) { 1877 | delete window.ZeroClipboard; 1878 | } 1879 | })(jQuery, function() { 1880 | return this || window; 1881 | }()); -------------------------------------------------------------------------------- /dist/jquery.zeroclipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jquery.zeroclipboard 3 | * Bind to the `beforecopy`, `copy`, `aftercopy`, and `copy-error` events, custom DOM-like events for clipboard injection generated using jQuery's Special Events API and ZeroClipboard's Core module. 4 | * Copyright (c) 2014 5 | * Licensed MIT 6 | * https://github.com/zeroclipboard/jquery.zeroclipboard 7 | * v0.2.0 8 | */ 9 | !function(a,b){"use strict";var c,d=!!b.ZeroClipboard;/*! 10 | * ZeroClipboard 11 | * The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface. 12 | * Copyright (c) 2014 Jon Rohan, James M. Greene 13 | * Licensed MIT 14 | * http://zeroclipboard.org/ 15 | * v2.1.2 16 | */ 17 | !function(a,b){var d,e=a,f=e.document,g=e.navigator,h=e.setTimeout,i=e.encodeURIComponent,j=e.ActiveXObject,k=e.Number.parseInt||e.parseInt,l=e.Number.parseFloat||e.parseFloat,m=e.Number.isNaN||e.isNaN,n=e.Math.round,o=e.Date.now,p=e.Object.keys,q=e.Object.defineProperty,r=e.Object.prototype.hasOwnProperty,s=e.Array.prototype.slice,t=function(a){return s.call(a,0)},u=function(){var a,c,d,e,f,g,h=t(arguments),i=h[0]||{};for(a=1,c=h.length;c>a;a++)if(null!=(d=h[a]))for(e in d)r.call(d,e)&&(f=i[e],g=d[e],i!==g&&g!==b&&(i[e]=g));return i},v=function(a){var b,c,d,e;if("object"!=typeof a||null==a)b=a;else if("number"==typeof a.length)for(b=[],c=0,d=a.length;d>c;c++)r.call(a,c)&&(b[c]=v(a[c]));else{b={};for(e in a)r.call(a,e)&&(b[e]=v(a[e]))}return b},w=function(a,b){for(var c={},d=0,e=b.length;e>d;d++)b[d]in a&&(c[b[d]]=a[b[d]]);return c},x=function(a,b){var c={};for(var d in a)-1===b.indexOf(d)&&(c[d]=a[d]);return c},y=function(a){if(a)for(var b in a)r.call(a,b)&&delete a[b];return a},z=function(a,b){if(a&&1===a.nodeType&&a.ownerDocument&&b&&(1===b.nodeType&&b.ownerDocument&&b.ownerDocument===a.ownerDocument||9===b.nodeType&&!b.ownerDocument&&b===a.ownerDocument))do{if(a===b)return!0;a=a.parentNode}while(a);return!1},A={bridge:null,version:"0.0.0",pluginType:"unknown",disabled:null,outdated:null,unavailable:null,deactivated:null,overdue:null,ready:null},B="11.0.0",C={},D={},E=null,F={ready:"Flash communication is established",error:{"flash-disabled":"Flash is disabled or not installed","flash-outdated":"Flash is too outdated to support ZeroClipboard","flash-unavailable":"Flash is unable to communicate bidirectionally with JavaScript","flash-deactivated":"Flash is too outdated for your browser and/or is configured as click-to-activate","flash-overdue":"Flash communication was established but NOT within the acceptable time limit"}},G=function(){var a,b,c,d,e="ZeroClipboard.swf";if(!f.currentScript||!(d=f.currentScript.src)){var g=f.getElementsByTagName("script");if("readyState"in g[0])for(a=g.length;a--&&("interactive"!==g[a].readyState||!(d=g[a].src)););else if("loading"===f.readyState)d=g[g.length-1].src;else{for(a=g.length;a--;){if(c=g[a].src,!c){b=null;break}if(c=c.split("#")[0].split("?")[0],c=c.slice(0,c.lastIndexOf("/")+1),null==b)b=c;else if(b!==c){b=null;break}}null!==b&&(d=b)}}return d&&(d=d.split("#")[0].split("?")[0],e=d.slice(0,d.lastIndexOf("/")+1)+e),e}(),H={swfPath:G,trustedDomains:a.location.host?[a.location.host]:[],cacheBust:!0,forceEnhancedClipboard:!1,flashLoadTimeout:3e4,autoActivate:!0,bubbleEvents:!0,containerId:"global-zeroclipboard-html-bridge",containerClass:"global-zeroclipboard-container",swfObjectId:"global-zeroclipboard-flash-bridge",hoverClass:"zeroclipboard-is-hover",activeClass:"zeroclipboard-is-active",forceHandCursor:!1,title:null,zIndex:999999999},I=function(a){if("object"==typeof a&&null!==a)for(var b in a)if(r.call(a,b))if(/^(?:forceHandCursor|title|zIndex|bubbleEvents)$/.test(b))H[b]=a[b];else if(null==A.bridge)if("containerId"===b||"swfObjectId"===b){if(!X(a[b]))throw new Error("The specified `"+b+"` value is not valid as an HTML4 Element ID");H[b]=a[b]}else H[b]=a[b];{if("string"!=typeof a||!a)return v(H);if(r.call(H,a))return H[a]}},J=function(){return{browser:w(g,["userAgent","platform","appName"]),flash:x(A,["bridge"]),zeroclipboard:{version:yb.version,config:yb.config()}}},K=function(){return!!(A.disabled||A.outdated||A.unavailable||A.deactivated)},L=function(a,b){var c,d,e,f={};if("string"==typeof a&&a)e=a.toLowerCase().split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)r.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&yb.on(c,a[c]);if(e&&e.length){for(c=0,d=e.length;d>c;c++)a=e[c].replace(/^on/,""),f[a]=!0,C[a]||(C[a]=[]),C[a].push(b);if(f.ready&&A.ready&&yb.emit({type:"ready"}),f.error){var g=["disabled","outdated","unavailable","deactivated","overdue"];for(c=0,d=g.length;d>c;c++)if(A[g[c]]===!0){yb.emit({type:"error",name:"flash-"+g[c]});break}}}return yb},M=function(a,b){var c,d,e,f,g;if(0===arguments.length)f=p(C);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)r.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&yb.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=C[a],g&&g.length)if(b)for(e=g.indexOf(b);-1!==e;)g.splice(e,1),e=g.indexOf(b,e);else g.length=0;return yb},N=function(a){var b;return b="string"==typeof a&&a?v(C[a])||null:v(C)},O=function(a){var b,c,d;return a=Y(a),a&&!cb(a)?"ready"===a.type&&A.overdue===!0?yb.emit({type:"error",name:"flash-overdue"}):(b=u({},a),bb.call(this,b),"copy"===a.type&&(d=ib(D),c=d.data,E=d.formatMap),c):void 0},P=function(){if("boolean"!=typeof A.ready&&(A.ready=!1),!yb.isFlashUnusable()&&null===A.bridge){var a=H.flashLoadTimeout;"number"==typeof a&&a>=0&&h(function(){"boolean"!=typeof A.deactivated&&(A.deactivated=!0),A.deactivated===!0&&yb.emit({type:"error",name:"flash-deactivated"})},a),A.overdue=!1,gb()}},Q=function(){yb.clearData(),yb.blur(),yb.emit("destroy"),hb(),yb.off()},R=function(a,b){var c;if("object"==typeof a&&a&&"undefined"==typeof b)c=a,yb.clearData();else{if("string"!=typeof a||!a)return;c={},c[a]=b}for(var d in c)"string"==typeof d&&d&&r.call(c,d)&&"string"==typeof c[d]&&c[d]&&(D[d]=c[d])},S=function(a){"undefined"==typeof a?(y(D),E=null):"string"==typeof a&&r.call(D,a)&&delete D[a]},T=function(a){return"undefined"==typeof a?v(D):"string"==typeof a&&r.call(D,a)?D[a]:void 0},U=function(a){if(a&&1===a.nodeType){d&&(qb(d,H.activeClass),d!==a&&qb(d,H.hoverClass)),d=a,pb(a,H.hoverClass);var b=a.getAttribute("title")||H.title;if("string"==typeof b&&b){var c=fb(A.bridge);c&&c.setAttribute("title",b)}var e=H.forceHandCursor===!0||"pointer"===rb(a,"cursor");vb(e),ub()}},V=function(){var a=fb(A.bridge);a&&(a.removeAttribute("title"),a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.top="1px"),d&&(qb(d,H.hoverClass),qb(d,H.activeClass),d=null)},W=function(){return d||null},X=function(a){return"string"==typeof a&&a&&/^[A-Za-z][A-Za-z0-9_:\-\.]*$/.test(a)},Y=function(a){var b;if("string"==typeof a&&a?(b=a,a={}):"object"==typeof a&&a&&"string"==typeof a.type&&a.type&&(b=a.type),b){u(a,{type:b.toLowerCase(),target:a.target||d||null,relatedTarget:a.relatedTarget||null,currentTarget:A&&A.bridge||null,timeStamp:a.timeStamp||o()||null});var c=F[a.type];return"error"===a.type&&a.name&&c&&(c=c[a.name]),c&&(a.message=c),"ready"===a.type&&u(a,{target:null,version:A.version}),"error"===a.type&&(/^flash-(disabled|outdated|unavailable|deactivated|overdue)$/.test(a.name)&&u(a,{target:null,minimumVersion:B}),/^flash-(outdated|unavailable|deactivated|overdue)$/.test(a.name)&&u(a,{version:A.version})),"copy"===a.type&&(a.clipboardData={setData:yb.setData,clearData:yb.clearData}),"aftercopy"===a.type&&(a=jb(a,E)),a.target&&!a.relatedTarget&&(a.relatedTarget=Z(a.target)),a=$(a)}},Z=function(a){var b=a&&a.getAttribute&&a.getAttribute("data-clipboard-target");return b?f.getElementById(b):null},$=function(a){if(a&&/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)){var c=a.target,d="_mouseover"===a.type&&a.relatedTarget?a.relatedTarget:b,g="_mouseout"===a.type&&a.relatedTarget?a.relatedTarget:b,h=tb(c),i=e.screenLeft||e.screenX||0,j=e.screenTop||e.screenY||0,k=f.body.scrollLeft+f.documentElement.scrollLeft,l=f.body.scrollTop+f.documentElement.scrollTop,m=h.left+("number"==typeof a._stageX?a._stageX:0),n=h.top+("number"==typeof a._stageY?a._stageY:0),o=m-k,p=n-l,q=i+o,r=j+p,s="number"==typeof a.movementX?a.movementX:0,t="number"==typeof a.movementY?a.movementY:0;delete a._stageX,delete a._stageY,u(a,{srcElement:c,fromElement:d,toElement:g,screenX:q,screenY:r,pageX:m,pageY:n,clientX:o,clientY:p,x:o,y:p,movementX:s,movementY:t,offsetX:0,offsetY:0,layerX:0,layerY:0})}return a},_=function(a){var b=a&&"string"==typeof a.type&&a.type||"";return!/^(?:(?:before)?copy|destroy)$/.test(b)},ab=function(a,b,c,d){d?h(function(){a.apply(b,c)},0):a.apply(b,c)},bb=function(a){if("object"==typeof a&&a&&a.type){var b=_(a),c=C["*"]||[],d=C[a.type]||[],f=c.concat(d);if(f&&f.length){var g,h,i,j,k,l=this;for(g=0,h=f.length;h>g;g++)i=f[g],j=l,"string"==typeof i&&"function"==typeof e[i]&&(i=e[i]),"object"==typeof i&&i&&"function"==typeof i.handleEvent&&(j=i,i=i.handleEvent),"function"==typeof i&&(k=u({},a),ab(i,j,[k],b))}return this}},cb=function(a){var b=a.target||d||null,c="swf"===a._source;delete a._source;var e=["flash-disabled","flash-outdated","flash-unavailable","flash-deactivated","flash-overdue"];switch(a.type){case"error":-1!==e.indexOf(a.name)&&u(A,{disabled:"flash-disabled"===a.name,outdated:"flash-outdated"===a.name,unavailable:"flash-unavailable"===a.name,deactivated:"flash-deactivated"===a.name,overdue:"flash-overdue"===a.name,ready:!1});break;case"ready":var f=A.deactivated===!0;u(A,{disabled:!1,outdated:!1,unavailable:!1,deactivated:!1,overdue:f,ready:!f});break;case"copy":var g,h,i=a.relatedTarget;!D["text/html"]&&!D["text/plain"]&&i&&(h=i.value||i.outerHTML||i.innerHTML)&&(g=i.value||i.textContent||i.innerText)?(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",g),h!==g&&a.clipboardData.setData("text/html",h)):!D["text/plain"]&&a.target&&(g=a.target.getAttribute("data-clipboard-text"))&&(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",g));break;case"aftercopy":yb.clearData(),b&&b!==ob()&&b.focus&&b.focus();break;case"_mouseover":yb.focus(b),H.bubbleEvents===!0&&c&&(b&&b!==a.relatedTarget&&!z(a.relatedTarget,b)&&db(u({},a,{type:"mouseenter",bubbles:!1,cancelable:!1})),db(u({},a,{type:"mouseover"})));break;case"_mouseout":yb.blur(),H.bubbleEvents===!0&&c&&(b&&b!==a.relatedTarget&&!z(a.relatedTarget,b)&&db(u({},a,{type:"mouseleave",bubbles:!1,cancelable:!1})),db(u({},a,{type:"mouseout"})));break;case"_mousedown":pb(b,H.activeClass),H.bubbleEvents===!0&&c&&db(u({},a,{type:a.type.slice(1)}));break;case"_mouseup":qb(b,H.activeClass),H.bubbleEvents===!0&&c&&db(u({},a,{type:a.type.slice(1)}));break;case"_click":case"_mousemove":H.bubbleEvents===!0&&c&&db(u({},a,{type:a.type.slice(1)}))}return/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)?!0:void 0},db=function(a){if(a&&"string"==typeof a.type&&a){var b,c=a.target||null,d=c&&c.ownerDocument||f,g={view:d.defaultView||e,canBubble:!0,cancelable:!0,detail:"click"===a.type?1:0,button:"number"==typeof a.which?a.which-1:"number"==typeof a.button?a.button:d.createEvent?0:1},h=u(g,a);c&&d.createEvent&&c.dispatchEvent&&(h=[h.type,h.canBubble,h.cancelable,h.view,h.detail,h.screenX,h.screenY,h.clientX,h.clientY,h.ctrlKey,h.altKey,h.shiftKey,h.metaKey,h.button,h.relatedTarget],b=d.createEvent("MouseEvents"),b.initMouseEvent&&(b.initMouseEvent.apply(b,h),b._source="js",c.dispatchEvent(b)))}},eb=function(){var a=f.createElement("div");return a.id=H.containerId,a.className=H.containerClass,a.style.position="absolute",a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.height="1px",a.style.zIndex=""+wb(H.zIndex),a},fb=function(a){for(var b=a&&a.parentNode;b&&"OBJECT"===b.nodeName&&b.parentNode;)b=b.parentNode;return b||null},gb=function(){var a,b=A.bridge,c=fb(b);if(!b){var d=nb(e.location.host,H),g="never"===d?"none":"all",h=lb(H),i=H.swfPath+kb(H.swfPath,H);c=eb();var j=f.createElement("div");c.appendChild(j),f.body.appendChild(c);var k=f.createElement("div"),l="activex"===A.pluginType;k.innerHTML='"+(l?'':"")+'',b=k.firstChild,k=null,b.ZeroClipboard=yb,c.replaceChild(b,j)}return b||(b=f[H.swfObjectId],b&&(a=b.length)&&(b=b[a-1]),!b&&c&&(b=c.firstChild)),A.bridge=b||null,b},hb=function(){var a=A.bridge;if(a){var b=fb(a);b&&("activex"===A.pluginType&&"readyState"in a?(a.style.display="none",function c(){if(4===a.readyState){for(var d in a)"function"==typeof a[d]&&(a[d]=null);a.parentNode&&a.parentNode.removeChild(a),b.parentNode&&b.parentNode.removeChild(b)}else h(c,10)}()):(a.parentNode&&a.parentNode.removeChild(a),b.parentNode&&b.parentNode.removeChild(b))),A.ready=null,A.bridge=null,A.deactivated=null}},ib=function(a){var b={},c={};if("object"==typeof a&&a){for(var d in a)if(d&&r.call(a,d)&&"string"==typeof a[d]&&a[d])switch(d.toLowerCase()){case"text/plain":case"text":case"air:text":case"flash:text":b.text=a[d],c.text=d;break;case"text/html":case"html":case"air:html":case"flash:html":b.html=a[d],c.html=d;break;case"application/rtf":case"text/rtf":case"rtf":case"richtext":case"air:rtf":case"flash:rtf":b.rtf=a[d],c.rtf=d}return{data:b,formatMap:c}}},jb=function(a,b){if("object"!=typeof a||!a||"object"!=typeof b||!b)return a;var c={};for(var d in a)if(r.call(a,d)){if("success"!==d&&"data"!==d){c[d]=a[d];continue}c[d]={};var e=a[d];for(var f in e)f&&r.call(e,f)&&r.call(b,f)&&(c[d][b[f]]=e[f])}return c},kb=function(a,b){var c=null==b||b&&b.cacheBust===!0;return c?(-1===a.indexOf("?")?"?":"&")+"noCache="+o():""},lb=function(a){var b,c,d,f,g="",h=[];if(a.trustedDomains&&("string"==typeof a.trustedDomains?f=[a.trustedDomains]:"object"==typeof a.trustedDomains&&"length"in a.trustedDomains&&(f=a.trustedDomains)),f&&f.length)for(b=0,c=f.length;c>b;b++)if(r.call(f,b)&&f[b]&&"string"==typeof f[b]){if(d=mb(f[b]),!d)continue;if("*"===d){h.length=0,h.push(d);break}h.push.apply(h,[d,"//"+d,e.location.protocol+"//"+d])}return h.length&&(g+="trustedOrigins="+i(h.join(","))),a.forceEnhancedClipboard===!0&&(g+=(g?"&":"")+"forceEnhancedClipboard=true"),"string"==typeof a.swfObjectId&&a.swfObjectId&&(g+=(g?"&":"")+"swfObjectId="+i(a.swfObjectId)),g},mb=function(a){if(null==a||""===a)return null;if(a=a.replace(/^\s+|\s+$/g,""),""===a)return null;var b=a.indexOf("//");a=-1===b?a:a.slice(b+2);var c=a.indexOf("/");return a=-1===c?a:-1===b||0===c?null:a.slice(0,c),a&&".swf"===a.slice(-4).toLowerCase()?null:a||null},nb=function(){var a=function(a){var b,c,d,e=[];if("string"==typeof a&&(a=[a]),"object"!=typeof a||!a||"number"!=typeof a.length)return e;for(b=0,c=a.length;c>b;b++)if(r.call(a,b)&&(d=mb(a[b]))){if("*"===d){e.length=0,e.push("*");break}-1===e.indexOf(d)&&e.push(d)}return e};return function(b,c){var d=mb(c.swfPath);null===d&&(d=b);var e=a(c.trustedDomains),f=e.length;if(f>0){if(1===f&&"*"===e[0])return"always";if(-1!==e.indexOf(b))return 1===f&&b===d?"sameDomain":"always"}return"never"}}(),ob=function(){try{return f.activeElement}catch(a){return null}},pb=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)||a.classList.add(b),a;if(b&&"string"==typeof b){var c=(b||"").split(/\s+/);if(1===a.nodeType)if(a.className){for(var d=" "+a.className+" ",e=a.className,f=0,g=c.length;g>f;f++)d.indexOf(" "+c[f]+" ")<0&&(e+=" "+c[f]);a.className=e.replace(/^\s+|\s+$/g,"")}else a.className=b}return a},qb=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)&&a.classList.remove(b),a;if("string"==typeof b&&b){var c=b.split(/\s+/);if(1===a.nodeType&&a.className){for(var d=(" "+a.className+" ").replace(/[\n\t]/g," "),e=0,f=c.length;f>e;e++)d=d.replace(" "+c[e]+" "," ");a.className=d.replace(/^\s+|\s+$/g,"")}}return a},rb=function(a,b){var c=e.getComputedStyle(a,null).getPropertyValue(b);return"cursor"!==b||c&&"auto"!==c||"A"!==a.nodeName?c:"pointer"},sb=function(){var a,b,c,d=1;return"function"==typeof f.body.getBoundingClientRect&&(a=f.body.getBoundingClientRect(),b=a.right-a.left,c=f.body.offsetWidth,d=n(b/c*100)/100),d},tb=function(a){var b={left:0,top:0,width:0,height:0};if(a.getBoundingClientRect){var c,d,g,h=a.getBoundingClientRect();"pageXOffset"in e&&"pageYOffset"in e?(c=e.pageXOffset,d=e.pageYOffset):(g=sb(),c=n(f.documentElement.scrollLeft/g),d=n(f.documentElement.scrollTop/g));var i=f.documentElement.clientLeft||0,j=f.documentElement.clientTop||0;b.left=h.left+c-i,b.top=h.top+d-j,b.width="width"in h?h.width:h.right-h.left,b.height="height"in h?h.height:h.bottom-h.top}return b},ub=function(){var a;if(d&&(a=fb(A.bridge))){var b=tb(d);u(a.style,{width:b.width+"px",height:b.height+"px",top:b.top+"px",left:b.left+"px",zIndex:""+wb(H.zIndex)})}},vb=function(a){A.ready===!0&&(A.bridge&&"function"==typeof A.bridge.setHandCursor?A.bridge.setHandCursor(a):A.ready=!1)},wb=function(a){if(/^(?:auto|inherit)$/.test(a))return a;var b;return"number"!=typeof a||m(a)?"string"==typeof a&&(b=wb(k(a,10))):b=a,"number"==typeof b?b:"auto"},xb=function(a){function b(a){var b=a.match(/[\d]+/g);return b.length=3,b.join(".")}function c(a){return!!a&&(a=a.toLowerCase())&&(/^(pepflashplayer\.dll|libpepflashplayer\.so|pepperflashplayer\.plugin)$/.test(a)||"chrome.plugin"===a.slice(-13))}function d(a){a&&(i=!0,a.version&&(m=b(a.version)),!m&&a.description&&(m=b(a.description)),a.filename&&(k=c(a.filename)))}var e,f,h,i=!1,j=!1,k=!1,m="";if(g.plugins&&g.plugins.length)e=g.plugins["Shockwave Flash"],d(e),g.plugins["Shockwave Flash 2.0"]&&(i=!0,m="2.0.0.11");else if(g.mimeTypes&&g.mimeTypes.length)h=g.mimeTypes["application/x-shockwave-flash"],e=h&&h.enabledPlugin,d(e);else if("undefined"!=typeof a){j=!0;try{f=new a("ShockwaveFlash.ShockwaveFlash.7"),i=!0,m=b(f.GetVariable("$version"))}catch(n){try{f=new a("ShockwaveFlash.ShockwaveFlash.6"),i=!0,m="6.0.21"}catch(o){try{f=new a("ShockwaveFlash.ShockwaveFlash"),i=!0,m=b(f.GetVariable("$version"))}catch(p){j=!1}}}}A.disabled=i!==!0,A.outdated=m&&l(m)g;g++)a=e.getRangeAt(g),c+=a.toString(),f.appendChild(a.cloneContents());return d["text/plain"]=c,c.replace(/\s/g,"")&&(d["text/html"]=f.innerHTML),d}function d(a){if("string"!=typeof a||!a)return null;var b,c,d=a;return d=d.replace(/<(?:hr)(?:\s+[^>]*)?\s*[\/]?>/gi,"{\\pard \\brdrb \\brdrs \\brdrw10 \\brsp20 \\par}\n{\\pard\\par}\n"),d=d.replace(/<(?:br)(?:\s+[^>]*)?\s*[\/]?>/gi,"{\\pard\\par}\n"),d=d.replace(/<(?:p|div|section|article)(?:\s+[^>]*)?\s*[\/]>/gi,"{\\pard\\par}\n"),d=d.replace(/<(?:[^>]+)\/>/g,""),d=d.replace(/]*)?(?:\s+href=(["'])(?:javascript:void\(0?\);?|#|return false;?|void\(0?\);?|)\1)(?:\s+[^>]*)?>/gi,"{{{\n"),b=d,d=d.replace(/]*)?(?:\s+href=(["'])(.+)\1)(?:\s+[^>]*)?>/gi,'{\\field{\\*\\fldinst{HYPERLINK\n "$2"\n}}{\\fldrslt{\\ul\\cf1\n'),c=d!==b,d=d.replace(/]*)?>/gi,"{{{\n"),d=d.replace(/<\/a(?:\s+[^>]*)?>/gi,"\n}}}"),d=d.replace(/<(?:b|strong)(?:\s+[^>]*)?>/gi,"{\\b\n"),d=d.replace(/<(?:i|em)(?:\s+[^>]*)?>/gi,"{\\i\n"),d=d.replace(/<(?:u|ins)(?:\s+[^>]*)?>/gi,"{\\ul\n"),d=d.replace(/<(?:strike|del)(?:\s+[^>]*)?>/gi,"{\\strike\n"),d=d.replace(/]*)?>/gi,"{\\super\n"),d=d.replace(/]*)?>/gi,"{\\sub\n"),d=d.replace(/<(?:p|div|section|article)(?:\s+[^>]*)?>/gi,"{\\pard\n"),d=d.replace(/<\/(?:p|div|section|article)(?:\s+[^>]*)?>/gi,"\n\\par}\n"),d=d.replace(/<\/(?:b|strong|i|em|u|ins|strike|del|sup|sub)(?:\s+[^>]*)?>/gi,"\n}"),d=d.replace(/<(?:[^>]+)>/g,""),d="{\\rtf1\\ansi\n"+(c?"{\\colortbl\n;\n\\red0\\green0\\blue255;\n}\n":"")+d+"\n}"}function e(b){var e=a.Event(b.type,a.extend(b,{_source:"swf"}));if(a(b.target).trigger(e),"copy"===e.type){if(a.event.special.copy.options.requirePreventDefault===!0&&!e.isDefaultPrevented()){b.clipboardData.clearData();var f=c();(f["text/plain"]||f["text/html"])&&b.clipboardData.setData(f)}var g=n.getData();if(a.event.special.copy.options.autoConvertHtmlToRtf===!0&&g["text/html"]&&!g["application/rtf"]){var h=d(g["text/html"]);b.clipboardData.setData("application/rtf",h)}}}function f(b){var c=a.Event("copy-error",a.extend(b,{type:"copy-error",_source:"swf"}));a(b.target).trigger(c)}function g(){a.event.props.push("clipboardData"),n.config(a.extend(!0,{autoActivate:!1},p.options)),n.on("beforecopy copy aftercopy",e),n.on("error",f),n.create()}function h(){n.destroy();var b=a.event.props.indexOf("clipboardData");-1!==b&&a.event.props.splice(b,1)}function i(b){k(b),b.target&&b.target!==n.activeElement()&&b.target!==a("#"+n.config("containerId"))[0]&&b.target!==a("#"+n.config("swfObjectId"))[0]&&n.focus(b.target)}function j(b){k(b),b.relatedTarget&&b.relatedTarget!==n.activeElement()&&b.relatedTarget!==a("#"+n.config("containerId"))[0]&&b.relatedTarget!==a("#"+n.config("swfObjectId"))[0]&&n.blur()}function k(a){n.isFlashUnusable()||"js"===a.originalEvent._source||(a.stopImmediatePropagation(),a.preventDefault())}var l=0,m=".zeroclipboard",n=b.ZeroClipboard,o=n.config("trustedDomains"),p={add:function(b){0===l++&&g();var c=m+(b.namespace?"."+b.namespace:""),d=b.selector,e="zc|{"+d+"}|{"+c+"}|count",f=a(this);"number"!=typeof f.data(e)&&f.data(e,0),0===f.data(e)&&(f.on("mouseenter"+c,d,i),f.on("mouseleave"+c,d,j),f.on("mouseover"+c,d,k),f.on("mouseout"+c,d,k),f.on("mousemove"+c,d,k),f.on("mousedown"+c,d,k),f.on("mouseup"+c,d,k),f.on("click"+c,d,k)),f.data(e,f.data(e)+1)},remove:function(b){var c=m+(b.namespace?"."+b.namespace:""),d=b.selector,e="zc|{"+d+"}|{"+c+"}|count",f=a(this);f.data(e,f.data(e)-1),0===f.data(e)&&(f.off("click"+c,d,k),f.off("mouseup"+c,d,k),f.off("mousedown"+c,d,k),f.off("mousemove"+c,d,k),f.off("mouseout"+c,d,k),f.off("mouseover"+c,d,k),f.off("mouseleave"+c,d,j),f.off("mouseenter"+c,d,i),f.removeData(e)),0===--l&&h()},trigger:function(b){if("copy"===b.type){var c=a(this),d="swf"===b._source;delete b._source,d||(c.trigger(a.extend(!0,{},b,{type:"beforecopy"})),c.one("copy",function(){var d={},e=n.getData();a.each(e,function(a){d[a]=!1});var f=a.extend(!0,{},b,{type:"aftercopy",data:a.extend(!0,{},e),success:d});c.trigger(f)}))}},_default:function(){return!0},options:{requirePreventDefault:!0,autoConvertHtmlToRtf:!0,trustedDomains:o,hoverClass:"hover",activeClass:"active"}};a.event.special.beforecopy=p,a.event.special.copy=p,a.event.special.aftercopy=p,a.event.special["copy-error"]=p}(jQuery,function(){return this||b}()),d||delete b.ZeroClipboard}(jQuery,function(){return this||window}()); -------------------------------------------------------------------------------- /libs/jquery-loader.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // Default to the local version. 3 | var path = '../libs/jquery/jquery.js'; 4 | // Get any jquery=___ param from the query string. 5 | var jqversion = location.search.match(/[?&]jquery=(.*?)(?=&|$)/); 6 | // If a version was specified, use that version from code.jquery.com. 7 | if (jqversion) { 8 | path = 'http://code.jquery.com/jquery-' + jqversion[1] + '.js'; 9 | } 10 | // This is the only time I'll ever use document.write, I promise! 11 | document.write(''); 12 | }()); 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "jquery-plugin", 4 | "version": "0.0.0-ignored", 5 | "engines": { 6 | "node": ">= 0.8.0" 7 | }, 8 | "scripts": { 9 | "test": "grunt travis --verbose" 10 | }, 11 | "devDependencies": { 12 | "findup-sync": "^0.1.3", 13 | "grunt": "~0.4.5", 14 | "grunt-contrib-clean": "^0.5.0", 15 | "grunt-contrib-concat": "^0.4.0", 16 | "grunt-contrib-connect": "^0.7.1", 17 | "grunt-contrib-copy": "^0.5.0", 18 | "grunt-contrib-jshint": "^0.10.0", 19 | "grunt-contrib-qunit": "^0.5.1", 20 | "grunt-contrib-uglify": "^0.4.0", 21 | "qunitjs": "^1.14.0", 22 | "zeroclipboard": "^2.1.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/end.js: -------------------------------------------------------------------------------- 1 | 2 | if (!zcExistsAlready) { 3 | delete window.ZeroClipboard; 4 | } 5 | 6 | })( 7 | jQuery, 8 | (function() { 9 | /*jshint strict: false */ 10 | return this || window; 11 | })() 12 | ); 13 | -------------------------------------------------------------------------------- /src/jquery.zeroclipboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jquery.zeroclipboard 3 | * https://github.com/zeroclipboard/jquery.zeroclipboard 4 | * 5 | * Copyright (c) 2014 James M. Greene 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | (function($, window, undefined) { 10 | "use strict"; 11 | 12 | 13 | var mouseEnterBindingCount = 0, 14 | customEventNamespace = ".zeroclipboard", 15 | ZeroClipboard = window.ZeroClipboard, 16 | _trustedDomains = ZeroClipboard.config("trustedDomains"); 17 | 18 | 19 | function getSelectionData() { 20 | var range, 21 | selectedText = "", 22 | selectedData = {}, 23 | sel = window.getSelection(), 24 | tmp = document.createElement("div"); 25 | 26 | for (var i = 0, len = sel.rangeCount; i < len; i++) { 27 | range = sel.getRangeAt(i); 28 | selectedText += range.toString(); 29 | tmp.appendChild(range.cloneContents()); 30 | } 31 | 32 | selectedData["text/plain"] = selectedText; 33 | if (selectedText.replace(/\s/g, "")) { 34 | selectedData["text/html"] = tmp.innerHTML; 35 | } 36 | 37 | return selectedData; 38 | } 39 | 40 | // 41 | // Testing: http://jsfiddle.net/JamesMGreene/2b6Lc/ 42 | // 43 | function convertHtmlToRtf(html) { 44 | if (!(typeof html === "string" && html)) { 45 | return null; 46 | } 47 | 48 | var tmpRichText, hasHyperlinks, 49 | richText = html; 50 | 51 | // Singleton tags 52 | richText = richText.replace(/<(?:hr)(?:\s+[^>]*)?\s*[\/]?>/ig, "{\\pard \\brdrb \\brdrs \\brdrw10 \\brsp20 \\par}\n{\\pard\\par}\n"); 53 | richText = richText.replace(/<(?:br)(?:\s+[^>]*)?\s*[\/]?>/ig, "{\\pard\\par}\n"); 54 | 55 | // Empty tags 56 | richText = richText.replace(/<(?:p|div|section|article)(?:\s+[^>]*)?\s*[\/]>/ig, "{\\pard\\par}\n"); 57 | richText = richText.replace(/<(?:[^>]+)\/>/g, ""); 58 | 59 | // Hyperlinks 60 | richText = richText.replace( 61 | /]*)?(?:\s+href=(["'])(?:javascript:void\(0?\);?|#|return false;?|void\(0?\);?|)\1)(?:\s+[^>]*)?>/ig, 62 | "{{{\n" 63 | ); 64 | tmpRichText = richText; 65 | richText = richText.replace( 66 | /]*)?(?:\s+href=(["'])(.+)\1)(?:\s+[^>]*)?>/ig, 67 | "{\\field{\\*\\fldinst{HYPERLINK\n \"$2\"\n}}{\\fldrslt{\\ul\\cf1\n" 68 | ); 69 | hasHyperlinks = richText !== tmpRichText; 70 | richText = richText.replace(/]*)?>/ig, "{{{\n"); 71 | richText = richText.replace(/<\/a(?:\s+[^>]*)?>/ig, "\n}}}"); 72 | 73 | // Start tags 74 | richText = richText.replace(/<(?:b|strong)(?:\s+[^>]*)?>/ig, "{\\b\n"); 75 | richText = richText.replace(/<(?:i|em)(?:\s+[^>]*)?>/ig, "{\\i\n"); 76 | richText = richText.replace(/<(?:u|ins)(?:\s+[^>]*)?>/ig, "{\\ul\n"); 77 | richText = richText.replace(/<(?:strike|del)(?:\s+[^>]*)?>/ig, "{\\strike\n"); 78 | richText = richText.replace(/]*)?>/ig, "{\\super\n"); 79 | richText = richText.replace(/]*)?>/ig, "{\\sub\n"); 80 | richText = richText.replace(/<(?:p|div|section|article)(?:\s+[^>]*)?>/ig, "{\\pard\n"); 81 | 82 | // End tags 83 | richText = richText.replace(/<\/(?:p|div|section|article)(?:\s+[^>]*)?>/ig, "\n\\par}\n"); 84 | richText = richText.replace(/<\/(?:b|strong|i|em|u|ins|strike|del|sup|sub)(?:\s+[^>]*)?>/ig, "\n}"); 85 | 86 | // Strip any other remaining HTML tags [but leave their contents] 87 | richText = richText.replace(/<(?:[^>]+)>/g, ""); 88 | 89 | // Prefix and suffix the rich text with the necessary syntax 90 | richText = 91 | "{\\rtf1\\ansi\n" + 92 | (hasHyperlinks ? "{\\colortbl\n;\n\\red0\\green0\\blue255;\n}\n" : "") + 93 | richText + 94 | "\n}"; 95 | 96 | return richText; 97 | } 98 | 99 | function zcEventHandler(e) { 100 | var $event = $.Event(e.type, $.extend(e, { "_source": "swf" })); 101 | $(e.target).trigger($event); 102 | 103 | if ($event.type === "copy") { 104 | // If `$event.preventDefault();` was not called... 105 | if ( 106 | $.event.special.copy.options.requirePreventDefault === true && 107 | !$event.isDefaultPrevented() 108 | ) { 109 | // Clear out any pending data 110 | e.clipboardData.clearData(); 111 | 112 | // And then copy the currently selected text instead, if any 113 | var selectionData = getSelectionData(); 114 | if (selectionData["text/plain"] || selectionData["text/html"]) { 115 | e.clipboardData.setData(selectionData); 116 | } 117 | } 118 | 119 | // If there is pending HTML to transfer but no RTF, automatically do a basic conversion of HTML to RTF 120 | var _clipData = ZeroClipboard.getData(); 121 | if ( 122 | $.event.special.copy.options.autoConvertHtmlToRtf === true && 123 | _clipData["text/html"] && 124 | !_clipData["application/rtf"] 125 | ) { 126 | var richText = convertHtmlToRtf(_clipData["text/html"]); 127 | e.clipboardData.setData("application/rtf", richText); 128 | } 129 | } 130 | } 131 | 132 | function zcErrorHandler(e) { 133 | var $event = $.Event("copy-error", $.extend(e, { "type": "copy-error", "_source": "swf" })); 134 | $(e.target).trigger($event); 135 | } 136 | 137 | function setup() { 138 | $.event.props.push("clipboardData"); 139 | 140 | ZeroClipboard.config($.extend(true, { autoActivate: false }, copyEventDef.options)); 141 | ZeroClipboard.on("beforecopy copy aftercopy", zcEventHandler); 142 | ZeroClipboard.on("error", zcErrorHandler); 143 | ZeroClipboard.create(); 144 | } 145 | 146 | function teardown() { 147 | ZeroClipboard.destroy(); 148 | 149 | var indy = $.event.props.indexOf("clipboardData"); 150 | if (indy !== -1) { 151 | $.event.props.splice(indy, 1); 152 | } 153 | } 154 | 155 | function mouseEnterHandler($event) { 156 | mouseSuppressor($event); 157 | 158 | if ( 159 | $event.target && 160 | $event.target !== ZeroClipboard.activeElement() && 161 | $event.target !== $("#" + ZeroClipboard.config("containerId"))[0] && 162 | $event.target !== $("#" + ZeroClipboard.config("swfObjectId"))[0] 163 | ) { 164 | ZeroClipboard.focus($event.target); 165 | } 166 | } 167 | 168 | function mouseLeaveHandler($event) { 169 | mouseSuppressor($event); 170 | 171 | if ( 172 | $event.relatedTarget && 173 | $event.relatedTarget !== ZeroClipboard.activeElement() && 174 | $event.relatedTarget !== $("#" + ZeroClipboard.config("containerId"))[0] && 175 | $event.relatedTarget !== $("#" + ZeroClipboard.config("swfObjectId"))[0] 176 | ) { 177 | ZeroClipboard.blur(); 178 | } 179 | } 180 | 181 | function mouseSuppressor($event) { 182 | if (!ZeroClipboard.isFlashUnusable() && $event.originalEvent._source !== "js") { 183 | $event.stopImmediatePropagation(); 184 | $event.preventDefault(); 185 | } 186 | } 187 | 188 | 189 | 190 | var copyEventDef = { 191 | 192 | /* Invoked each time this event is bound */ 193 | add: function(handleObj) { 194 | // If this is the first 'beforecopy'/'copy' binding on the page, we need to configure and create ZeroClipboard 195 | if (0 === mouseEnterBindingCount++) { 196 | setup(); 197 | } 198 | 199 | var namespaces = customEventNamespace + (handleObj.namespace ? "." + handleObj.namespace : ""), 200 | selector = handleObj.selector, 201 | zcDataKey = "zc|{" + selector + "}|{" + namespaces + "}|count", 202 | $this = $(this); 203 | 204 | if (typeof $this.data(zcDataKey) !== "number") { 205 | $this.data(zcDataKey, 0); 206 | } 207 | 208 | if ($this.data(zcDataKey) === 0) { 209 | $this.on("mouseenter" + namespaces, selector, mouseEnterHandler); 210 | $this.on("mouseleave" + namespaces, selector, mouseLeaveHandler); 211 | $this.on("mouseover" + namespaces, selector, mouseSuppressor); 212 | $this.on("mouseout" + namespaces, selector, mouseSuppressor); 213 | $this.on("mousemove" + namespaces, selector, mouseSuppressor); 214 | $this.on("mousedown" + namespaces, selector, mouseSuppressor); 215 | $this.on("mouseup" + namespaces, selector, mouseSuppressor); 216 | $this.on("click" + namespaces, selector, mouseSuppressor); 217 | } 218 | 219 | $this.data(zcDataKey, $this.data(zcDataKey) + 1); 220 | }, 221 | 222 | 223 | /* Invoked each time this event is unbound */ 224 | remove: function(handleObj) { 225 | var namespaces = customEventNamespace + (handleObj.namespace ? "." + handleObj.namespace : ""), 226 | selector = handleObj.selector, 227 | zcDataKey = "zc|{" + selector + "}|{" + namespaces + "}|count", 228 | $this = $(this); 229 | 230 | $this.data(zcDataKey, $this.data(zcDataKey) - 1); 231 | 232 | if ($this.data(zcDataKey) === 0) { 233 | $this.off("click" + namespaces, selector, mouseSuppressor); 234 | $this.off("mouseup" + namespaces, selector, mouseSuppressor); 235 | $this.off("mousedown" + namespaces, selector, mouseSuppressor); 236 | $this.off("mousemove" + namespaces, selector, mouseSuppressor); 237 | $this.off("mouseout" + namespaces, selector, mouseSuppressor); 238 | $this.off("mouseover" + namespaces, selector, mouseSuppressor); 239 | $this.off("mouseleave" + namespaces, selector, mouseLeaveHandler); 240 | $this.off("mouseenter" + namespaces, selector, mouseEnterHandler); 241 | 242 | $this.removeData(zcDataKey); 243 | } 244 | 245 | // If this is the last 'beforecopy'/'copy' unbinding on the page, we should also destroy ZeroClipboard 246 | if (0 === --mouseEnterBindingCount) { 247 | teardown(); 248 | } 249 | }, 250 | 251 | 252 | /* Invoked each time a manual call to `trigger`/`triggerHandler` is made for this event type */ 253 | trigger: function($event /*, data */) { 254 | if ($event.type === "copy") { 255 | var $this = $(this); 256 | 257 | var sourceIsSwf = $event._source === "swf"; 258 | delete $event._source; 259 | 260 | if (!sourceIsSwf) { 261 | // First, trigger 'beforecopy' and ensure it gets handled before continuing 262 | $this.trigger($.extend(true, {}, $event, { type: "beforecopy" })); 263 | 264 | // Then allow this 'copy' event to be handled... 265 | 266 | // Then add a one-time handler for 'copy' to trigger an 'aftercopy' event 267 | $this.one("copy", function(/* $copyEvent */) { 268 | // Mark all statuses as failed since we can't inject into the clipboard during a simulated event 269 | var successData = {}, 270 | _clipData = ZeroClipboard.getData(); 271 | $.each(_clipData, function(key /*, val */) { 272 | successData[key] = false; 273 | }); 274 | 275 | // Trigger an 'aftercopy' event 276 | var $e = $.extend( 277 | true, 278 | {}, 279 | $event, 280 | { 281 | type: "aftercopy", 282 | data: $.extend(true, {}, _clipData), 283 | success: successData 284 | } 285 | ); 286 | $this.trigger($e); 287 | }); 288 | } 289 | } 290 | }, 291 | 292 | 293 | /* Invoked each time this event type is about to dispatch its default action */ 294 | _default: function(/* $event, data */) { 295 | // Prevent the element's default method from being called. 296 | return true; 297 | }, 298 | 299 | 300 | /* Add some default configuration options that can be overridden */ 301 | options: { 302 | 303 | // The default action for the W3C Clipboard API spec (as it stands today) is to 304 | // copy the currently selected text [and specificially ignore any pending data] 305 | // unless `e.preventDefault();` is called. 306 | requirePreventDefault: true, 307 | 308 | // If HTML has been added to the pending data, this plugin can automatically 309 | // convert the HTML into RTF (RichText) for use in non-HTML-capable editors. 310 | autoConvertHtmlToRtf: true, 311 | 312 | // SWF inbound scripting policy: page domains that the SWF should trust. 313 | // (single string, or array of strings) 314 | trustedDomains: _trustedDomains, 315 | 316 | // The CSS class name used to mimic the `:hover` pseudo-class 317 | hoverClass: "hover", 318 | 319 | // The CSS class name used to mimic the `:active` pseudo-class 320 | activeClass: "active" 321 | 322 | } 323 | 324 | }; 325 | 326 | 327 | 328 | /* Leverage the jQuery Special Events API to expose these events in a seemingly more natural way */ 329 | $.event.special.beforecopy = copyEventDef; 330 | $.event.special.copy = copyEventDef; 331 | $.event.special.aftercopy = copyEventDef; 332 | $.event.special["copy-error"] = copyEventDef; 333 | 334 | })( 335 | jQuery, 336 | (function() { 337 | /*jshint strict: false */ 338 | return this || window; 339 | })() 340 | ); 341 | 342 | -------------------------------------------------------------------------------- /src/start.js: -------------------------------------------------------------------------------- 1 | (function($, window, undefined) { 2 | "use strict"; 3 | 4 | var require, module, exports; //jshint ignore:line 5 | 6 | var zcExistsAlready = !!window.ZeroClipboard; 7 | -------------------------------------------------------------------------------- /test/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jquery.zeroclipboard Demo 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |

Direct bindings:

26 | 27 | 28 | 29 |
30 |
31 |

Delegate bindings:

32 | 33 | 34 | 35 |
36 |
37 | 38 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /test/jquery.zeroclipboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jquery.zeroclipboard Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 |
31 |
32 |
Copy Button
33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /test/jquery.zeroclipboard_tests.js: -------------------------------------------------------------------------------- 1 | /*global QUnit:false */ 2 | 3 | (function($, module, test) { 4 | "use strict"; 5 | 6 | 7 | module("jQuery#zeroclipboard"); 8 | 9 | test("Should exist", function(assert) { 10 | assert.expect(4); 11 | 12 | assert.ok($.event.special.beforecopy); 13 | assert.ok($.event.special.copy); 14 | assert.ok($.event.special.aftercopy); 15 | assert.ok($.event.special["copy-error"]); 16 | }); 17 | 18 | })(jQuery, QUnit.module, QUnit.test); 19 | -------------------------------------------------------------------------------- /zeroclipboard.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zeroclipboard", 3 | "title": "jquery.zeroclipboard", 4 | "description": "Bind to the `beforecopy`, `copy`, `aftercopy`, and `copy-error` events, custom DOM-like events for clipboard injection generated using jQuery's Special Events API and ZeroClipboard's Core module.", 5 | "version": "0.2.3", 6 | "homepage": "https://github.com/zeroclipboard/jquery.zeroclipboard", 7 | "author": { 8 | "name": "James M. Greene", 9 | "email": "james.m.greene@gmail.com", 10 | "url": "http://greene.io/" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/zeroclipboard/jquery.zeroclipboard.git" 15 | }, 16 | "bugs": "https://github.com/zeroclipboard/jquery.zeroclipboard/issues", 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/zeroclipboard/jquery.zeroclipboard/blob/master/LICENSE-MIT" 21 | } 22 | ], 23 | "dependencies": { 24 | "jquery": ">=1.7" 25 | }, 26 | "keywords": [ 27 | "flash", 28 | "clipboard", 29 | "copy", 30 | "cut", 31 | "paste", 32 | "zclip", 33 | "clip", 34 | "clippy" 35 | ] 36 | } 37 | --------------------------------------------------------------------------------