├── .editorconfig ├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── configuration-options.md ├── network-hooks.md └── styles.md ├── examples └── animation.html ├── gulpfile.js ├── index.html ├── package.json ├── pioneer.json ├── src ├── share-button.js ├── share-button.styl ├── share-utils.js ├── string-utils.js └── svg │ ├── email.svg │ ├── export.svg │ ├── facebook.svg │ ├── googlePlus.svg │ ├── linkedin.svg │ ├── pinterest.svg │ ├── reddit.svg │ ├── twitter.svg │ └── whatsapp.svg └── tests ├── features ├── animation.feature ├── basic.feature ├── email.feature ├── facebook.feature ├── googleplus.feature ├── linkedin.feature ├── meta.feature ├── ordering.feature ├── pinterest.feature ├── reddit.feature ├── twitter.feature ├── ui.feature └── whatsapp.feature ├── fixtures ├── animation.html ├── basic.html ├── email.html ├── facebook.html ├── facebook_no_sdk.html ├── googleplus.html ├── linkedin.html ├── meta.html ├── ordering.html ├── pinterest.html ├── reddit.html ├── twitter.html ├── ui.html └── whatsapp.html ├── helpers └── helpers.coffee ├── steps ├── animation.coffee ├── basic.coffee ├── email.coffee ├── facebook.coffee ├── googleplus.coffee ├── linkedin.coffee ├── meta.coffee ├── ordering.coffee ├── pinterest.coffee ├── reddit.coffee ├── twitter.coffee ├── ui.coffee └── whatsapp.coffee └── widgets └── shareButton.coffee /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ## Details 2 | 3 | ### Description 4 | 5 | ### Affected Tech 6 | 7 | #### OS(s) 8 | 9 | - ​ 10 | 11 | #### Browser(s) 12 | 13 | - ​ 14 | 15 | ### Steps to Reproduce 16 | 17 | [jsfiddle Example](http://jsfiddle.net/28fn23bj/) 18 | 19 | 1. ​ 20 | 2. ​ 21 | 3. ​ 22 | 23 | ### TL; DR 24 | 25 | ... 26 | 27 | ## Comments 28 | 29 | Anything else related to the issue that doesn't fit above. 30 | 31 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Link to issue number]() 4 | 5 | ### Changes 6 | 7 | Explain why? 8 | 9 | #### Screenshot 10 | 11 | ![]() 12 | 13 | 14 | 15 | ### PR Checklist 16 | 17 | - [ ] [Clean commits](https://github.com/carrot/share-button/blob/master/CONTRIBUTING.md#commit-cleanliness) 18 | - [ ] Passing Tests -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .com.apple.timemachine.supported 4 | phantomjsdriver.log 5 | dist 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "browser": true, 4 | "devel": true 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .com.apple.timemachine.supported 4 | phantomjsdriver.log 5 | .github 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | before_script: 4 | - python -m SimpleHTTPServer > /dev/null 2>&1 & 5 | - sleep 2 6 | - gulp build 7 | node_js: 8 | - 'node' 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Share Button 2 | Hello there! First of all, thanks for being interested in share-button and helping out. We all think you are awesome, and by contributing to open source projects, you are making the world a better place. That being said, there are a few ways to make the process of contributing code to share-button smoother, detailed below: 3 | 4 | ## Filing Issues 5 | If you are filing an issue, it is **required** that you include a jsfiddle that reproduces the issue you are having. The share button is a very simple and straightforward script, and often times if you are encountering a bug it is the result of some other code on your page. This is not something you should file an issue about, as we don't have time to debug your code. By taking a look at the issue with the button in isolation, it is easier to determine whether you have found a bug with share-button or whether the issue was introduced by your code. 6 | 7 | There is a [template jsfiddle here](http://jsfiddle.net/28fn23bj/) for you to experiment with if you'd like. 8 | 9 | If you are opening an issue to suggest a feature, that's totally fine, although we might not add the feature for you immediately. If you are after a new feature, the best course of action is to open an issue describing the feature you want in detail. If we approve of the feature, try submitting a pull request to add it yourself! As mentioned above, this project is fairly straightforward, and there are explicit instructions below for getting set up -- a contribution is a good way to get your feet wet with open source. 10 | 11 | ## Getting Set Up 12 | - Clone the project down 13 | - Install [nodejs](http://nodejs.org) (version 4.0.0 or higher) 14 | - _Windows users only:_ Install [Python](https://wiki.python.org/moin/BeginnersGuide/Download) (version 2.x.x) and set up [environment path](http://stackoverflow.com/a/6318188/1308734). 15 | - Run `npm install` 16 | - Make changes to the files in the `src` folder 17 | - Run `gulp build` to build the js files into `dist` 18 | - Open `index.html` locally to see your changes. 19 | 20 | ## Testing 21 | This project is constantly evolving, and to ensure that things are secure and working for everyone, we need to have tests. If you are adding a new feature, please make sure to add a test for it. We are using [pioneer](pioneerjs.com) for testing with [phantom.js](http://phantomjs.org/). For getting setup with those please checkout their setup instructions on their sites. All said and done, tests for this library are tough - at very least make sure to visually confirm that it's working using index.html in the root of the project. If it's a visual please make sure that everything looks okay in [examples/animation.html]. 22 | 23 | To run the test suite, make sure you have installed [PhantomJS](//phantomjs.org/download.html), then you can use the `npm test` command to run them. 24 | 25 | ## Commit Cleanliness 26 | It's ok if you start out with a bunch of experimentation and your commit log isn't totally clean, but before any pull requests are accepted, we like to have a nice clean commit log. That means [well-written and clear commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) and commits that each do something significant, rather than being typo or bug fixes. 27 | 28 | If you submit a pull request that doesn't have a clean commit log, we will ask you to clean it up before we accept. This means being familiar with rebasing - if you are not, [this guide](https://help.github.com/articles/interactive-rebase) by github should help you to get started, and feel free to ask us anything, we are happy to help. 29 | 30 | ## Deployment 31 | When deploying new changes you'll run create a new git tag, push to github, run `gulp build`, publish to npm. Then you'll want to package all the files in the `dist` folder up into (a zip & tarball), and these will be added to your release on github. 32 | 33 | Happy Developing! 34 | 35 | ![cute dog](http://hellogiggles.hellogiggles.netdna-cdn.com/wp-content/uploads/2015/03/17/53451-Cute-Dog.jpg) 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License (MIT) 2 | ------------- 3 | 4 | Copyright (c) 2013 Jeff Escalante, Carrot Creative 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Share Button

2 | 3 |

4 | 5 |

6 |

7 | 8 | 9 | 10 |

11 | 12 | # An Introduction 13 | Simple, light, flexible, and good-looking share button. [See it in action!](http://sharebutton.co/) 14 | 15 | ## Why Should You Use This? 16 | All major social networks have their own share widgets you can put on your page, but this isn't ideal for a variety of reasons: 17 | 18 | 1. They tend to be slow-loading. 19 | 2. They inject extra javascript and DOM elements into your page making it slower. 20 | 3. They generally aren't customizable enough to fit the design of your site. 21 | 4. Managing each provider's code snippets etc is repetitive and needless. Additionally, they can make your front-end code quite messy. 22 | 5. The buttons themselves take up a lot of space (especially the Facebook share button). 23 | 24 | Let's take a quick look at the alternative, using this little plugin: 25 | 26 | 1. It doesn't load any iframes or extra javascript making the overall load time much faster. 27 | 2. It looks simple and clean by default, and can be customized in any and every way. 28 | 3. All you have to do to use it is include the script and call `new ShareButton` on a `share-button` element. That's two lines of code total, the script link and the share call. 29 | 4. It's tiny and compact, expanding only when the user actually wants to share something. 30 | 31 | # Getting Started 32 | 1. [Download the latest script & stylesheet](https://github.com/carrot/share-button/releases) and include it on your page. 33 | 2. Make a `share-button` element on your page 34 | 3. In your javascript, call `new ShareButton()` 35 | 4. Pass options to the share call if you want (details below) 36 | 5. Profit! 37 | 38 | ```html 39 | 40 | ``` 41 | 42 | ```js 43 | new ShareButton({ 44 | networks: { 45 | facebook: { 46 | appId: "abc123" 47 | } 48 | } 49 | }); 50 | ``` 51 | 52 | ## NPM installation 53 | 54 | 1. `npm i --save-dev share-button` 55 | 2. Make a `share-button` element on your page 56 | 3. In your javascript, `var ShareButton = require('share-button');` 57 | 4. Pass options to the share call if you want (details below) 58 | 5. Profit! 59 | 60 | ```html 61 | 62 | ``` 63 | 64 | ```js 65 | var ShareButton = require('share-button'); 66 | 67 | new ShareButton({ 68 | networks: { 69 | facebook: { 70 | appId: "abc123" 71 | } 72 | } 73 | }); 74 | ``` 75 | 76 | # Customization 77 | ## Configuration Options 78 | The share button is extremely flexible. As such we provide the ability to pass a wide array of options for additional configuration. All configuration options are available here: [Configuration Options](https://github.com/carrot/share-button/blob/master/docs/configuration-options.md) 79 | 80 | ## Styles 81 | Additionally, you're able to customize the look and feel of the button and animations though CSS. All CSS styles and how to modify them are available here: [CSS Styles](https://github.com/carrot/share-button/blob/master/docs/styles.md) 82 | 83 | ## Hooks 84 | You are able to set `before` and `after` hooks when a user clicks a network. This allows you to dynamically change attributes for that button. For more information: [click here](https://github.com/carrot/share-button/blob/master/docs/network-hooks.md) 85 | 86 | # Public API 87 | The share button also returns a simple API that can be used to control it should you need to. Example shown below: 88 | 89 | ```js 90 | var share = new ShareButton(); // Grabs all share-button elements on page 91 | 92 | share.toggle(); // toggles the share button popup 93 | share.open(); // open the share button popup 94 | share.close(); // closes the share button popup 95 | share.config; // exposes the configurations listed above 96 | ``` 97 | 98 | # Fonts 99 | As of version 1.0.0 we completely removed the `Entypo` font set! 100 | 101 | # Inspiration 102 | This project was inspired by [this dribbble shot](http://dribbble.com/shots/1072278) and [this cssdeck experiment](http://cssdeck.com/labs/css-social-share-button) - huge props to these two guys for some incredible ideas and work. 103 | 104 | # Contributing and License 105 | - Contributing Guidelines can be found [here](https://github.com/carrot/share-button/blob/master/CONTRIBUTING.md) 106 | - Licensed under MIT - [details here](https://github.com/carrot/share-button/blob/master/LICENSE) 107 | -------------------------------------------------------------------------------- /docs/configuration-options.md: -------------------------------------------------------------------------------- 1 | # Configuration Options 2 | You can pass an options object when you call `ShareButton` on an element to make things a little more flexible. 3 | 4 | Passing configuration options: 5 | 6 | ```js 7 | config = { 8 | networks: { 9 | facebook: { 10 | appId: '12345' 11 | } 12 | } 13 | } 14 | 15 | var share = new ShareButton('.share-button', config); 16 | ``` 17 | 18 | All options: 19 | 20 | ```js 21 | config = { 22 | protocol: // the protocol you'd prefer to use. [Default: your current protocol] 23 | url: // the url you'd like to share. [Default: `window.location.href`] 24 | title: // title to be shared alongside your link [Default: See below in defaults section] 25 | description: // text to be shared alongside your link, [Default: See below in defaults section] 26 | image: // image to be shared [Default: See below in defaults section] 27 | ui: { 28 | flyout: // change the flyout direction of the shares. chose from `top left`, `top center`, `top right`, `bottom left`, `bottom right`, `bottom center`, `middle left`, or `middle right` [Default: `top center`] 29 | button_font: // include the Lato font set from the Google Fonts API. [Default: `true`] 30 | buttonText: // change the text of the button, [Default: `Share`] 31 | icon_font: // include the minified Entypo font set. [Default: `true`] 32 | }, 33 | networks: { 34 | googlePlus: { 35 | enabled: // Enable Google+. [Default: true] 36 | url: // the url you'd like to share to Google+ [Default: config.url] 37 | }, 38 | twitter: { 39 | enabled: // Enable Twitter. [Default: true] 40 | url: // the url you'd like to share to Twitter [Default: config.url] 41 | description: // text to be shared alongside your link to Twitter [Default: config.description] 42 | }, 43 | facebook: { 44 | enabled: // Enable Facebook. [Default: true] 45 | load_sdk: // Load the FB SDK. If false, it will default to Facebook's sharer.php implementation. 46 | // NOTE: This will disable the ability to dynamically set values and rely directly on applicable Open Graph tags. 47 | // [Default: true] 48 | url: // the url you'd like to share to Facebook [Default: config.url] 49 | appId: // Facebook app id for tracking shares. if provided, will use the facebook API 50 | title: // title to be shared alongside your link to Facebook [Default: config.title] 51 | caption: // caption to be shared alongside your link to Facebook [Default: null] 52 | description: // text to be shared alongside your link to Facebook [Default: config.description] 53 | image: // image to be shared to Facebook [Default: config.image] 54 | }, 55 | pinterest: { 56 | enabled: // Enable Pinterest. [Default: true] 57 | url: // the url you'd like to share to Pinterest [Default: config.url] 58 | image: // image to be shared to Pinterest [Default: config.image] 59 | description: // text to be shared alongside your link to Pinterest [Default: config.description] 60 | }, 61 | reddit: { 62 | enabled: // Enable Reddit. [Default: true] 63 | url: // the url you'd like to share to Reddit [Default: config.url] 64 | title: // title to be shared alongside your link to Reddit [Default: config.title] 65 | }, 66 | linkedin: { 67 | enabled: // Enable LinkedIn. [Default: true] 68 | url: // the url you'd like to share to LinkedIn [Default: config.url] 69 | title: // title to be shared alongside your link to LinkedIn [Default: config.title], 70 | description: // text to be shared alongside your link to LinkedIn [Default: config.description] 71 | }, 72 | whatsapp: { 73 | enabled: // Enable WhatsApp. [Default: true] 74 | description: // text to be shared alongside your link to WhatsApp [Default: config.description], 75 | url: // the url you'd like to share to WhatsApp [Default: config.url] 76 | }, 77 | email: { 78 | enabled: // Enable Email. [Default: true] 79 | title: // the subject of the email [Default: config.title] 80 | description: // The body of the email [Default: config.description] 81 | } 82 | } 83 | } 84 | ``` 85 | 86 | ## Defaults 87 | The follow logic is used to populate default values for some of the config keys above: 88 | - _title_ - The first defined, non-null value out of (in order): 89 | - meta tag name='og:title' content attribute value 90 | - meta tag name='twitter:title' content attribute value 91 | - document's title tag value 92 | 93 | - _image_ - the first defined, non-null value out of (in order): 94 | - meta tag name='og:image' content attribute value 95 | - meta tag name='twitter:image' content attribute value 96 | 97 | - _description_ - the first defined, non-null value out of (in order): 98 | - meta tag name='og:description' content attribute value 99 | - meta tag name='twitter:description' content attribute value 100 | - meta tag name='description' content attribute value 101 | -------------------------------------------------------------------------------- /docs/network-hooks.md: -------------------------------------------------------------------------------- 1 | # Network Hooks 2 | You are able to set `before` and `after` hooks when a user clicks a network. The context passed to the hook is the current network's configuration. To change any of the network's configuration before or after instantiating the share, you must alter the value and return `this` as shown in the examples below. 3 | 4 | ```js 5 | config = { 6 | networks: { 7 | facebook: { 8 | before: function() { 9 | this.url = "https://github.com/carrot/share-button"; 10 | this.text = "Changing the Facebook Share Configurations"; 11 | }, 12 | after: function() { 13 | console.log("User shared:", this.url); 14 | } 15 | } 16 | } 17 | } 18 | ``` 19 | 20 | **Example:** 21 | 22 | ```js 23 | new Share(".share-button-top", { 24 | title: "Share Button", 25 | networks: { 26 | facebook: { 27 | appId: "602752456409826", 28 | before: function() { 29 | console.log("BEFORE", this); 30 | this.url = "https://github.com/carrot/share-button"; 31 | this.text = "Changing the Facebook Share Configurations"; 32 | }, 33 | after: function() { 34 | console.log("User shared:", this.url); 35 | } 36 | } 37 | } 38 | }); 39 | ``` 40 | 41 | On a page with multiple share buttons, you can use the `before` hook to dynamically set the share URL: 42 | 43 | ```js 44 | new Share(".share-button", { 45 | networks: { 46 | facebook: { 47 | before: function(element) { 48 | this.url = element.getAttribute("data-url"); 49 | }, 50 | after: function() { 51 | console.log("User shared:", this.url); 52 | } 53 | } 54 | } 55 | }); 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/styles.md: -------------------------------------------------------------------------------- 1 | # Styles 2 | The Share Button styles attempt to be an all-for-one solution for any site. To customize your share button you can modify the options you're passing into your object or you can create your own stylesheet! 3 | 4 | Within `share-button.styl` you'll find two mixins: `network()` & `socialIcon()`. The `network()` mixin is what makes the network list responsive. `socialIcon()` is how we insert each svg and define their colors. After all the mixins and colors are defined then we setup the share-button's styles. 5 | 6 | ## Customization 7 | If you're going to cusomtize the share button you'll need to define styles for these main elements: 8 | 9 | ``` 10 | share-button - the button that holds ` Share` 11 | ul - holds the list of social networks 12 | li - holds each network 13 | a - the link tag that is a network 14 | ``` 15 | 16 | If you'd like to control the width of each network based on the amount of networks, you can use `.networks-{num}`. If you're using all the networks, don't forget about `whats-app` for mobile, there will be 8. 17 | 18 | Happy developing! :D 19 | -------------------------------------------------------------------------------- /examples/animation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Share Button Collision Test 4 | 5 | 6 | 83 | 84 | 85 |
86 | 87 |
88 | 89 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var uglify = require('gulp-uglify'); 3 | var rename = require('gulp-rename'); 4 | var accord = require('gulp-accord'); 5 | var browserify = require('gulp-browserify'); 6 | var shell = require('gulp-shell'); 7 | var del = require('del'); 8 | var autoprefixer = require('autoprefixer-stylus'); 9 | var axis = require('axis'); 10 | var poststylus = require('poststylus'); 11 | var postcssSVG = require('postcss-svg'); 12 | 13 | gulp.task('unbuild', function() { 14 | del(['dist']); 15 | }); 16 | 17 | gulp.task('style', function() { 18 | var styleShareButton = gulp 19 | .src('src/share-button.styl') 20 | .pipe(accord('stylus', { 21 | use: [ 22 | autoprefixer(), 23 | axis(), 24 | poststylus([postcssSVG({ paths: ['./src/svg' ]})]) 25 | ] 26 | })) 27 | .pipe(gulp.dest('dist/')) 28 | .pipe(shell( 29 | ['node_modules/.bin/minify --output dist/share-button.min.css dist/share-button.css'] 30 | )) 31 | }); 32 | 33 | gulp.task('script', function() { 34 | var umdShareButton = gulp 35 | .src(['src/share-button.js'], { read: false }) 36 | .pipe(browserify({ 37 | transform: ['babelify'], 38 | standalone: 'ShareButton' 39 | })) 40 | .pipe(gulp.dest('dist/')) 41 | .pipe(uglify()) 42 | .pipe(rename({ suffix: '.min' })) 43 | .pipe(gulp.dest('dist/')); 44 | }); 45 | 46 | gulp.task('build', ['script', 'style']); 47 | gulp.task('default', ['build']); 48 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Share Button Test 6 | 7 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Fork me on GitHub 84 | 85 | 86 | 87 |
new share-button();
88 | 89 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "share-button", 3 | "description": "fast, beautiful, and painless social shares", 4 | "version": "1.0.3", 5 | "homepage": "http://sharebutton.co", 6 | "main": "dist/share-button.js", 7 | "bugs": { 8 | "url": "https://github.com/carrot/share-button/issues" 9 | }, 10 | "author": "Jeff Escalante ", 11 | "contributors": [ 12 | { 13 | "name": "Tom Milewski", 14 | "email": "tmilewski@gmail.com" 15 | }, 16 | { 17 | "name": "Henry Snopek", 18 | "email": "hhsnopek@gmail.com" 19 | }, 20 | { 21 | "name": "Patrick Bacon-Blaber", 22 | "email": "pablaber225@gmail.com" 23 | } 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/carrot/share-button" 28 | }, 29 | "license": "MIT", 30 | "dependencies": { 31 | }, 32 | "devDependencies": { 33 | "autoprefixer-stylus": "^0.8.0", 34 | "axis": "^0.5.0", 35 | "babelify": "^6.3.0", 36 | "core-js": "^1.2.0", 37 | "del": "^2.0.2", 38 | "gulp": "^3.9.0", 39 | "gulp-accord": "^0.2.0", 40 | "gulp-browserify": "^0.5.1", 41 | "gulp-rename": "^1.2.2", 42 | "gulp-shell": "^0.5.0", 43 | "gulp-uglify": "^1.4.1", 44 | "minifier": "^0.7.1", 45 | "pioneer": "^0.11.6", 46 | "postcss-svg": "^1.0.1", 47 | "poststylus": "^0.2.1", 48 | "stylus": "^0.52.4" 49 | }, 50 | "scripts": { 51 | "test": "pioneer", 52 | "build": "gulp build", 53 | "unbuild": "gulp unbuild", 54 | "build-css": "gulp style", 55 | "build-js": "gulp script" 56 | }, 57 | "keywords": [ 58 | "share", 59 | "social" 60 | ], 61 | "engines": {} 62 | } 63 | -------------------------------------------------------------------------------- /pioneer.json: -------------------------------------------------------------------------------- 1 | { 2 | "feature": "tests/features", 3 | "require": [ 4 | "tests/steps", 5 | "tests/widgets", 6 | "tests/helpers" 7 | ], 8 | "format": "pioneerformat.js", 9 | "driver": "phantomjs", 10 | "error_formatter": "errorformat.js", 11 | "preventReload": false, 12 | "coffee": true, 13 | "verbose": true 14 | } 15 | -------------------------------------------------------------------------------- /src/share-button.js: -------------------------------------------------------------------------------- 1 | require('core-js/fn/symbol'); 2 | require('core-js/fn/array/iterator'); 3 | require('core-js/fn/math/trunc'); 4 | import ShareUtils from './share-utils'; 5 | import StringUtils from './string-utils'; 6 | 7 | /** 8 | * Sharebutton 9 | * @class 10 | * @classdesc 11 | * @extends ShareUtils 12 | 13 | * @param {String} element 14 | * @param {Object} options 15 | */ 16 | class ShareButton extends ShareUtils { 17 | constructor(element, options) { 18 | super(); 19 | 20 | if (typeof element === 'object') { 21 | this.element = undefined; 22 | options = element; 23 | } else 24 | this.element = element; 25 | 26 | this.el = { 27 | head: document.getElementsByTagName('head')[0], 28 | body: document.getElementsByTagName('body')[0] 29 | }; 30 | 31 | this.config = { 32 | enabledNetworks: 0, 33 | protocol: '//', 34 | url: window.location.href, 35 | caption: null, 36 | title: this._defaultTitle(), 37 | image: this._defaultImage(), 38 | description: this._defaultDescription(), 39 | 40 | ui: { 41 | flyout: 'sb-top sb-center', 42 | buttonText: 'Share', 43 | namespace: 'sb-', 44 | networkOrder: [], 45 | collision: false, 46 | }, 47 | 48 | networks: { 49 | googlePlus: { 50 | enabled: true, 51 | url: null 52 | }, 53 | twitter: { 54 | enabled: true, 55 | url: null, 56 | description: null 57 | }, 58 | facebook: { 59 | enabled: true, 60 | loadSdk: true, 61 | url: null, 62 | appId: null, 63 | title: null, 64 | caption: null, 65 | description: null, 66 | image: null 67 | }, 68 | pinterest: { 69 | enabled: true, 70 | url: null, 71 | image: null, 72 | description: null 73 | }, 74 | reddit: { 75 | enabled: true, 76 | url: null, 77 | title: null 78 | }, 79 | linkedin: { 80 | enabled: true, 81 | url: null, 82 | title: null, 83 | description: null 84 | }, 85 | whatsapp: { 86 | enabled: true, 87 | description: null, 88 | url: null 89 | }, 90 | email: { 91 | enabled: true, 92 | title: null, 93 | description: null 94 | } 95 | } 96 | }; 97 | 98 | this.listener = null; 99 | this._setup(this.element, options); 100 | } 101 | 102 | /** 103 | * @method open 104 | * @description Opens Share Button 105 | */ 106 | open() { this._public('Open'); } 107 | 108 | /** 109 | * @method close 110 | * @description Cpens Share Button 111 | */ 112 | close() { this._public('Close'); } 113 | 114 | /** 115 | * @method toggle 116 | * @description Toggles Share Button 117 | */ 118 | toggle() { this._public('Toggle'); } 119 | 120 | /** 121 | * @method toggleListen 122 | * @description Toggles the Share Button listener, good for updaing share 123 | * button for CSS animations. 124 | */ 125 | toggleListen() { this._public('Listen'); } 126 | 127 | /** 128 | * @method _public 129 | * @description Executes action 130 | * @private 131 | * 132 | * @param {String} action 133 | */ 134 | _public(action) { 135 | let instances; 136 | 137 | if (typeof element === 'undefined') 138 | instances = 139 | super._objToArray(document.getElementsByTagName('share-button')); 140 | else 141 | instances = document.querySelectorAll(element); 142 | 143 | for (let instance of instances) { 144 | let networks = 145 | instance.getElementsByClassName(`${this.config.ui.namespace}social`)[0]; 146 | this[`_event${action}`](instance, networks); 147 | } 148 | } 149 | 150 | /** 151 | * @method _setup 152 | * @description Sets up Share Button 153 | * @private 154 | * 155 | * @param {String} element selector 156 | * @param {Object} opts 157 | */ 158 | _setup(element, opts) { 159 | let instances; 160 | 161 | if (typeof element === 'undefined') 162 | instances = 163 | super._objToArray(document.getElementsByTagName('share-button')); 164 | else { 165 | instances = document.querySelectorAll(`share-button${element}`); 166 | if (typeof instances === 'object') 167 | instances = super._objToArray(instances); 168 | } 169 | 170 | // Adding user configs to default configs 171 | this._merge(this.config, opts); 172 | 173 | // Disable whatsapp display if not a mobile device 174 | if (this.config.networks.whatsapp.enabled && !this._isMobile()) 175 | this.config.networks.whatsapp.enabled = false; 176 | 177 | // Default order of networks if no network order entered 178 | if (this.config.ui.networkOrder.length === 0) 179 | this.config.ui.networkOrder = [ 180 | 'pinterest', 181 | 'twitter', 182 | 'facebook', 183 | 'whatsapp', 184 | 'googlePlus', 185 | 'reddit', 186 | 'linkedin', 187 | 'email' 188 | ]; 189 | 190 | for (let network of Object.keys(this.config.networks)) { 191 | if (this.config.ui.networkOrder.indexOf(network.toString()) < 0) { 192 | this.config.networks[network].enabled = false; 193 | this.config.ui.networkOrder.push(network); 194 | } 195 | } 196 | 197 | this._fixFlyout(); 198 | this._detectNetworks(); 199 | this._normalizeNetworkConfiguration(); 200 | 201 | // Inject Facebook JS SDK (if Facebook is enabled) 202 | if (this.config.networks.facebook.enabled && 203 | this.config.networks.facebook.loadSdk) 204 | this._injectFacebookSdk(); 205 | 206 | // Initialize instances 207 | let index = 0; 208 | for (let instance of instances) { 209 | this._setupInstance(instance, index++); 210 | } 211 | } 212 | 213 | /** 214 | * @method _setupInstance 215 | * @description Sets up each instance with config and styles 216 | * @private 217 | * 218 | * @param {DOMNode} instance 219 | * @param {Integer} index 220 | */ 221 | _setupInstance(instance, index) { 222 | this._hide(instance); 223 | 224 | // Add necessary classes to instance 225 | // (Note: FF doesn't support adding multiple classes in a single call) 226 | this._addClass(instance, `sharer-${index}`); 227 | this._injectHtml(instance); 228 | this._show(instance); 229 | 230 | let networksCon = 231 | instance.getElementsByClassName(`${this.config.ui.namespace}social`)[0]; 232 | let networks = instance.getElementsByTagName('li'); 233 | 234 | this._addClass(networksCon, `networks-${this.config.enabledNetworks}`); 235 | instance.addEventListener('click', () => 236 | this._eventToggle(instance, networksCon) 237 | ); 238 | 239 | // Add listener to activate networks and close button 240 | for (let k in Object.keys(networks)) { 241 | let network = networks[k]; 242 | 243 | if (typeof(network) !== "undefined") { 244 | let name = network.getAttribute('data-network'); 245 | let a = network.getElementsByTagName('a')[0]; 246 | 247 | this._addClass(network, this.config.networks[name].class); 248 | 249 | if (network.className.indexOf('email') < 0) 250 | a.setAttribute('onclick', 'return false'); 251 | 252 | a.addEventListener('mousedown', () => { 253 | this._hook('before', name, instance); 254 | }); 255 | a.addEventListener('mouseup', () => { 256 | this[`_network${StringUtils.capFLetter(name)}`](network); 257 | }); 258 | a.addEventListener('click', () => { 259 | this._hook('after', name, instance); 260 | }); 261 | } 262 | } 263 | } 264 | 265 | /** 266 | * @method _eventToggle 267 | * @description Toggles 'active' class on button 268 | * @private 269 | * 270 | * @param {DOMNode} button 271 | * @param {DOMNode} networks 272 | */ 273 | _eventToggle(button, networks) { 274 | if (this._hasClass(networks, 'active')) 275 | this._eventClose(networks); 276 | else 277 | this._eventOpen(button, networks); 278 | } 279 | 280 | /** 281 | * @method _eventOpen 282 | * @description Add 'active' class & remove 'load' class on button 283 | * @private 284 | * 285 | * @param {DOMNode} button 286 | * @param {DOMNode} networks 287 | */ 288 | _eventOpen(button, networks) { 289 | if (this._hasClass(networks, 'load')) 290 | this._removeClass(networks, 'load'); 291 | if (this.collision) 292 | this._collisionDetection(button, networks); 293 | 294 | this._addClass(networks, 'active'); 295 | } 296 | 297 | /** 298 | * @method _eventClose 299 | * @description Remove 'active' class on button 300 | * @private 301 | * 302 | * @param {DOMNode} button 303 | */ 304 | _eventClose(button) { 305 | this._removeClass(button, 'active'); 306 | } 307 | 308 | /** 309 | * @method _eventListen 310 | * @description Toggles weather or not a button's classes are being 311 | * constantly updated regardless of scrolls or window resizes. 312 | * @private 313 | * 314 | * @param {DOMNode} button 315 | * @param {DOMNode} networks 316 | */ 317 | _eventListen(button, networks) { 318 | let dimensions = this._getDimensions(button, networks); 319 | if (this.listener === null) 320 | this.listener = window.setInterval(() => 321 | this._adjustClasses(button, networks, dimensions), 100 322 | ); 323 | else { 324 | window.clearInterval(this.listener); 325 | this.listener = null; 326 | } 327 | } 328 | 329 | /** 330 | * @method _fixFlyout 331 | * @description Fixes the flyout entered by the user to match their provided 332 | * namespace 333 | *@private 334 | */ 335 | _fixFlyout() { 336 | let flyouts = this.config.ui.flyout.split(' '); 337 | if (flyouts[0].substring(0,this.config.ui.namespace.length) !== 338 | this.config.ui.namespace) 339 | flyouts[0] = `${this.config.ui.namespace}${flyouts[0]}`; 340 | if (flyouts[1].substring(0,this.config.ui.namespace.length) !== 341 | this.config.ui.namespace) 342 | flyouts[1] = `${this.config.ui.namespace}${flyouts[1]}`; 343 | this.config.ui.flyout = flyouts.join(' '); 344 | } 345 | 346 | /** 347 | * @method _collisionDetection 348 | * @description Adds listeners the first time a button is clicked to call 349 | * this._adjustClasses during scrolls and resizes. 350 | * @private 351 | * 352 | * @param {DOMNode} button - share button 353 | * @param {DOMNode} networks - list of social networks 354 | */ 355 | _collisionDetection(button, networks) { 356 | let dimensions = this._getDimensions(button, networks); 357 | this._adjustClasses(button, networks, dimensions); 358 | 359 | if (!button.classList.contains('clicked')) { 360 | window.addEventListener('scroll', () => 361 | this._adjustClasses(button, dimensions)); 362 | window.addEventListener('resize', () => 363 | this._adjustClasses(button, dimensions)); 364 | button.classList.add('clicked'); 365 | } 366 | } 367 | 368 | /** 369 | * @method _getDimensions 370 | * @description Returns an object with the dimensions of the button and 371 | * label elements of a Share Button. 372 | * @private 373 | * 374 | * @param {DOMNode} button 375 | * @param {DOMNode} networks 376 | * @returns {Object} 377 | */ 378 | _getDimensions(button, networks) { 379 | return { 380 | networksWidth: networks.offsetWidth, 381 | buttonHeight: button.offsetHeight, 382 | buttonWidth: button.offsetWidth 383 | }; 384 | } 385 | 386 | /** 387 | * @method _adjustClasses 388 | * @description Adjusts the positioning of the list of social networks based 389 | * off of where the share button is relative to the window. 390 | * 391 | * @private 392 | * @param {DOMNode} button 393 | * @param {DOMNode} networks 394 | * @param {Object} dimensions 395 | */ 396 | _adjustClasses(button, networks, dimensions) { 397 | let windowWidth = window.innerWidth; 398 | let windowHeight = window.innerHeight; 399 | let leftOffset = button.getBoundingClientRect().left + 400 | dimensions.buttonWidth / 2; 401 | let rightOffset = windowWidth - leftOffset; 402 | let topOffset = button.getBoundingClientRect().top + 403 | dimensions.buttonHeight / 2; 404 | let position = 405 | this._findLocation(leftOffset, topOffset, windowWidth, windowHeight); 406 | 407 | if (position[1] === "middle" && position[0] !== "center" && 408 | ((position[0] === "left" && 409 | windowWidth <= leftOffset + 220 + dimensions.buttonWidth / 2) || 410 | (position[0] === "right" && 411 | windowWidth <= rightOffset + 220 + dimensions.buttonWidth / 2) 412 | ) 413 | ) { 414 | networks.classList.add(`${this.config.ui.namespace}top`); 415 | networks.classList.remove(`${this.config.ui.namespace}middle`); 416 | networks.classList.remove(`${this.config.ui.namespace}bottom`); 417 | } 418 | else { 419 | switch(position[0]) { 420 | case "left": 421 | networks.classList.add(`${this.config.ui.namespace}right`); 422 | networks.classList.remove(`${this.config.ui.namespace}center`); 423 | networks.classList.remove(`${this.config.ui.namespace}left`); 424 | break; 425 | case "center": 426 | if (position[1] !== "top") 427 | networks.classList.add(`${this.config.ui.namespace}top`); 428 | networks.classList.add(`${this.config.ui.namespace}center`); 429 | networks.classList.remove(`${this.config.ui.namespace}left`); 430 | networks.classList.remove(`${this.config.ui.namespace}right`); 431 | networks.classList.remove(`${this.config.ui.namespace}middle`); 432 | break; 433 | case "right": 434 | networks.classList.add(`${this.config.ui.namespace}left`); 435 | networks.classList.remove(`${this.config.ui.namespace}center`); 436 | networks.classList.remove(`${this.config.ui.namespace}right`); 437 | break; 438 | } 439 | switch(position[1]) { 440 | case "top": 441 | networks.classList.add(`${this.config.ui.namespace}bottom`); 442 | networks.classList.remove(`${this.config.ui.namespace}middle`); 443 | if (position[0] !== "center") 444 | networks.classList.remove(`${this.config.ui.namespace}top`); 445 | break; 446 | case "middle": 447 | if (position[0] !== "center") { 448 | networks.classList.add(`${this.config.ui.namespace}middle`); 449 | networks.classList.remove(`${this.config.ui.namespace}top`); 450 | } 451 | networks.classList.remove(`${this.config.ui.namespace}bottom`); 452 | break; 453 | case "bottom": 454 | networks.classList.add(`${this.config.ui.namespace}top`); 455 | networks.classList.remove(`${this.config.ui.namespace}middle`); 456 | networks.classList.remove(`${this.config.ui.namespace}bottom`); 457 | break; 458 | } 459 | } 460 | } 461 | 462 | /** 463 | * @method _findLocation 464 | * @description Finds the location of the label given by its x and y value 465 | * with respect to the window width and window height given. 466 | * @private 467 | * 468 | * @param {number} labelX 469 | * @param {number} labelY 470 | * @param {number} windowWidth 471 | * @param {number} windowHeight 472 | * @returns {Array} 473 | */ 474 | _findLocation(labelX, labelY, windowWidth, windowHeight) { 475 | let xPosition = ["left", "center", "right"]; 476 | let yPosition = ["top", "middle", "bottom"]; 477 | let xLocation = 478 | Math.trunc(3 * (1 - ((windowWidth - labelX) / windowWidth))); 479 | let yLocation = 480 | Math.trunc(3 * (1 - ((windowHeight - labelY) / windowHeight))); 481 | if (xLocation >= 3) xLocation = 2; 482 | else if (xLocation <= -1) xLocation = 0; 483 | if (yLocation >= 3) yLocation = 2; 484 | else if (yLocation <= -1) yLocation = 0; 485 | return [xPosition[xLocation], yPosition[yLocation]]; 486 | } 487 | 488 | /** 489 | * @method _networkFacebook 490 | * @description Create & display a Facebook window 491 | * @private 492 | */ 493 | _networkFacebook(element) { 494 | if (this.config.networks.facebook.loadSdk) { 495 | if (!window.FB) { 496 | console.error('The Facebook JS SDK hasn\'t loaded yet.'); 497 | return this._updateHref(element, 'https://www.facebook.com/sharer/sharer.php', { 498 | u: this.config.networks.facebook.url 499 | }); 500 | } 501 | return FB.ui({ 502 | method:'feed', 503 | name: this.config.networks.facebook.title, 504 | link: this.config.networks.facebook.url, 505 | picture: this.config.networks.facebook.image, 506 | caption: this.config.networks.facebook.caption, 507 | description: this.config.networks.facebook.description 508 | }); 509 | } else { 510 | return this._updateHref( 511 | element, 512 | 'https://www.facebook.com/sharer/sharer.php', { 513 | u: this.config.networks.facebook.url 514 | } 515 | ); 516 | } 517 | } 518 | 519 | /** 520 | * @method _networkTwitter 521 | * @description Create & display a Twitter window 522 | * @private 523 | */ 524 | _networkTwitter(element) { 525 | this._updateHref(element, 'https://twitter.com/intent/tweet', { 526 | text: this.config.networks.twitter.description, 527 | url: this.config.networks.twitter.url 528 | }); 529 | } 530 | 531 | /** 532 | * @method _networkGooglePlus 533 | * @description Create & display a Google Plus window 534 | * @private 535 | */ 536 | _networkGooglePlus(element) { 537 | this._updateHref(element, 'https://plus.google.com/share', { 538 | url: this.config.networks.googlePlus.url 539 | }); 540 | } 541 | 542 | /** 543 | * @method _networkPinterest 544 | * @description Create & display a Pinterest window 545 | * @private 546 | */ 547 | _networkPinterest(element) { 548 | this._updateHref(element, 'https://www.pinterest.com/pin/create/button', { 549 | url: this.config.networks.pinterest.url, 550 | media: this.config.networks.pinterest.image, 551 | description: this.config.networks.pinterest.description 552 | }); 553 | } 554 | 555 | /** 556 | * @method _networkLinkedIn 557 | * @description Create & display a Linkedin window 558 | * @private 559 | */ 560 | _networkLinkedin(element) { 561 | this._updateHref(element, 'https://www.linkedin.com/shareArticle', { 562 | mini: 'true', 563 | url: this.config.networks.linkedin.url, 564 | title: this.config.networks.linkedin.title, 565 | summary: this.config.networks.linkedin.description 566 | }); 567 | } 568 | 569 | /** 570 | * @method _networkEmail 571 | * @description Create & display an Email window 572 | * @private 573 | */ 574 | _networkEmail(element) { 575 | this._updateHref(element, 'mailto:', { 576 | subject: this.config.networks.email.title, 577 | body: this.config.networks.email.description 578 | }); 579 | } 580 | 581 | /** 582 | * @method _networkReddit 583 | * @description Create & display a Reddit window 584 | * @private 585 | */ 586 | _networkReddit(element) { 587 | this._updateHref(element, 'http://www.reddit.com/submit', { 588 | url: this.config.networks.reddit.url, 589 | title: this.config.networks.reddit.title 590 | }); 591 | } 592 | 593 | /** 594 | * @method _networkWhatsapp 595 | * @description Create & display a Whatsapp window 596 | * @private 597 | */ 598 | _networkWhatsapp(element) { 599 | this._updateHref(element, 'whatsapp://send', { 600 | text: this.config.networks.whatsapp.description + " " + 601 | this.config.networks.whatsapp.url 602 | }); 603 | } 604 | 605 | /** 606 | * @method _injectStylesheet 607 | * @description Inject link to stylesheet 608 | * @private 609 | * 610 | * @param {String} url 611 | */ 612 | _injectStylesheet(url) { 613 | if (!this.el.head.querySelector(`link[href='${url}']`)) { 614 | let link = document.createElement("link"); 615 | link.setAttribute("rel", "stylesheet"); 616 | link.setAttribute("href", url); 617 | this.el.head.appendChild(link); 618 | } 619 | } 620 | 621 | /** 622 | * @method _injectHtml 623 | * @description Inject button structure 624 | * @private 625 | * 626 | * @param {DOMNode} instance 627 | */ 628 | _injectHtml(instance) { 629 | let networks = this.config.ui.networkOrder; 630 | let networkList = ''; 631 | 632 | for (let network of networks) { 633 | networkList += `
  • `; 634 | } 635 | instance.innerHTML = `${this.config.ui.buttonText}
      ` + networkList + `
    `; 636 | } 637 | 638 | /** 639 | * @method _injectFacebookSdk 640 | * @description Inject Facebook SDK 641 | * @private 642 | */ 643 | _injectFacebookSdk() { 644 | if (!window.FB && this.config.networks.facebook.appId && 645 | !this.el.body.querySelector('#fb-root')) { 646 | let script = document.createElement('script'); 647 | script.text = `window.fbAsyncInit=function(){FB.init({appId:'${this.config.networks.facebook.appId}',status:true,xfbml:true})};(function(e,t,n){var r,i=e.getElementsByTagName(t)[0];if (e.getElementById(n)){return}r=e.createElement(t);r.id=n;r.src='//connect.facebook.net/en_US/all.js';i.parentNode.insertBefore(r,i)})(document,'script','facebook-jssdk');`; 648 | 649 | let fbRoot = document.createElement('div'); 650 | fbRoot.id = 'fb-root'; 651 | 652 | this.el.body.appendChild(fbRoot); 653 | this.el.body.appendChild(script); 654 | } 655 | } 656 | 657 | /** 658 | * @method _hook 659 | * @description Hook helper function 660 | * @private 661 | * 662 | * @param {String} type 663 | * @param {String} network 664 | * @param {DOMNode} instance 665 | */ 666 | _hook(type, network, instance) { 667 | let fn = this.config.networks[network][type]; 668 | 669 | if (typeof fn === 'function') { 670 | let opts = fn.call(this.config.networks[network], instance); 671 | 672 | if (opts !== undefined) { 673 | opts = this._normalizeFilterConfigUpdates(opts); 674 | this.extend(this.config.networks[network], opts, true); 675 | this._normalizeNetworkConfiguration(); 676 | } 677 | } 678 | } 679 | 680 | /** 681 | * @method _defaultTitle 682 | * @description Gets default title 683 | * @private 684 | * 685 | * @returns {String} 686 | */ 687 | _defaultTitle() { 688 | let content; 689 | if ((content = (document.querySelector('meta[property="og:title"]') || 690 | document.querySelector('meta[name="twitter:title"]')))) 691 | return content.getAttribute('content'); 692 | else if ((content = document.querySelector('title'))) 693 | return content.textContent || content.innerText; 694 | } 695 | 696 | /** 697 | * @method _defaultImage 698 | * @description Gets default image 699 | * @private 700 | * 701 | * @returns {String} 702 | */ 703 | _defaultImage() { 704 | let content; 705 | if ((content = (document.querySelector('meta[property="og:image"]') || 706 | document.querySelector('meta[name="twitter:image"]')))) 707 | return content.getAttribute('content'); 708 | } 709 | 710 | /** 711 | * @method _defaultDescription 712 | * @description Gets default description 713 | * @private 714 | * 715 | * @returns {String} 716 | */ 717 | _defaultDescription() { 718 | let content; 719 | if ((content = (document.querySelector('meta[property="og:description"]') || 720 | document.querySelector('meta[name="twitter:description"]') || 721 | document.querySelector('meta[name="description"]')))) 722 | return content.getAttribute('content'); 723 | else 724 | return ''; 725 | } 726 | 727 | /** 728 | * @method _detectNetworks 729 | * @description Detect number of networks in use and display/hide 730 | * @private 731 | */ 732 | _detectNetworks() { 733 | // Update network-specific configuration with global configurations 734 | for (let network of Object.keys(this.config.networks)) { 735 | let display; 736 | for (let option of Object.keys(this.config.networks[network])) { 737 | if (this.config.networks[network][option] === null) { 738 | this.config.networks[network][option] = this.config[option]; 739 | } 740 | } 741 | 742 | // Check for enabled networks and display them 743 | if (this.config.networks[network].enabled) { 744 | this.class = 'enabled'; 745 | this.config.enabledNetworks += 1; 746 | } 747 | else 748 | this.class = 'disabled'; 749 | 750 | this.config.networks[network].class = this.class; 751 | } 752 | } 753 | 754 | /** 755 | * @method _normalizeNetworkConfiguration 756 | * @description Normalizes network configuration for Facebook & Twitter 757 | * @private 758 | */ 759 | _normalizeNetworkConfiguration() { 760 | // Don't load FB SDK if FB appId isn't present 761 | if (!this.config.networks.facebook.appId) 762 | this.config.networks.facebook.loadSdk = false; 763 | 764 | // Encode Twitter description for URL 765 | if (!!this.config.networks.twitter.description) 766 | if (!this._isEncoded(this.config.networks.twitter.description)) 767 | this.config.networks.twitter.description = 768 | encodeURIComponent(this.config.networks.twitter.description); 769 | 770 | // Typecast Facebook appId to a String 771 | if (typeof this.config.networks.facebook.appId === 'number') 772 | this.config.networks.facebook.appId = 773 | this.config.networks.facebook.appId.toString(); 774 | } 775 | 776 | /** 777 | * @method _normalizeFilterConfigUpdates 778 | * @description Normalizes Facebook config 779 | * @private 780 | * 781 | * @param {Object} opts 782 | * @returns {Object} 783 | */ 784 | _normalizeFilterConfigUpdates(opts) { 785 | if (this.config.networks.facebook.appId !== opts.appId) { 786 | console.warn('You are unable to change the Facebook appId after the button has been initialized. Please update your Facebook filters accordingly.'); 787 | delete(opts.appId); 788 | } 789 | 790 | if (this.config.networks.facebook.loadSdk !== opts.loadSdk) { 791 | console.warn('You are unable to change the Facebook loadSdk option after the button has been initialized. Please update your Facebook filters accordingly.'); 792 | delete(opts.appId); 793 | } 794 | 795 | return opts; 796 | } 797 | } 798 | 799 | module.exports = ShareButton; 800 | -------------------------------------------------------------------------------- /src/share-button.styl: -------------------------------------------------------------------------------- 1 | @import url('//fonts.googleapis.com/css?family=Lato'); 2 | 3 | network($amt) 4 | $width = ($amt * 60)px 5 | 6 | &.sb-center 7 | if ($width >= 480px) 8 | @media screen and (max-width: 520px) 9 | white-space: initial 10 | text-align: center 11 | width: 420px 12 | 13 | if ($width >= 380px) 14 | @media screen and (max-width: 460px) 15 | white-space: initial 16 | text-align: center 17 | width: 360px 18 | 19 | if ($width >= 320px) 20 | @media screen and (max-width: 400px) 21 | white-space: initial 22 | text-align: center 23 | width: 300px 24 | 25 | socialIcon($name) 26 | li[class*={"'" + $name + "'"}] 27 | background: convert($name) 28 | 29 | &:before 30 | background-image: svg($name, '[fill]: #fff') 31 | 32 | &:after 33 | background-image: svg($name, '[fill]: #000') 34 | 35 | networks = 'email' 'facebook' 'googlePlus' 'linkedin' 'pinterest' 'reddit' 'twitter' 'whatsapp' 36 | 37 | email = #42C5B0 38 | facebook = #3B5998 39 | googlePlus = #E34429 40 | linkedin = #4875B4 41 | pinterest = #C5282F 42 | reddit = #a1caf2 43 | twitter = #6CDFEA 44 | whatsapp = #4DC247 45 | label-bg = #a29baa 46 | label-color = #333333 47 | 48 | share-button 49 | position: relative 50 | font-size: 16px 51 | color: label-color 52 | background: label-bg 53 | padding: 5px 10px 5px 1.75em 54 | border-radius: 5px 55 | font-family: Lato, sans-serif 56 | font-weight: 800 57 | -webkit-font-smoothing: antialiased 58 | cursor: pointer 59 | white-space: nowrap 60 | transition() 61 | upcase() 62 | 63 | &:hover 64 | color: rgba(label-color, 0.8) 65 | background: rgba(label-bg, 0.8) 66 | 67 | &:before 68 | position: absolute 69 | line-height: 1em 70 | left: 0.6em 71 | width: 1em 72 | height: 1em 73 | content: ' ' 74 | background: svg("export", "[fill]: #000") no-repeat 75 | 76 | .sb-social 77 | position: absolute 78 | opacity: 0 79 | visibility: hidden 80 | transition: all 0.4s ease 81 | 82 | &.sb-center 83 | left: 50% 84 | 85 | &.sb-top 86 | top: 0 87 | transform: translate(-50%, -100%) 88 | 89 | &.sb-bottom 90 | bottom: 0 91 | transform: translate(-50%, 100%) 92 | 93 | &.active 94 | &.sb-top 95 | top: -1em 96 | 97 | &.sb-bottom 98 | bottom: -1em 99 | 100 | &.sb-left 101 | left: 50% 102 | 103 | &.sb-top 104 | top: 0 105 | transform: translate(calc(-100% + 30px), -100%) 106 | 107 | &.sb-middle 108 | top: 50% 109 | left: 0 110 | transform: translate(-100%, -50%) 111 | 112 | &.sb-bottom 113 | bottom: 0 114 | transform: translate(calc(-100% + 30px), 100%) 115 | 116 | &.active 117 | &.sb-top 118 | top: -1em 119 | 120 | &.sb-middle 121 | left: -1em 122 | 123 | &.sb-bottom 124 | bottom: -1em 125 | 126 | &.sb-right 127 | left: 50% 128 | 129 | &.sb-top 130 | top: 0 131 | transform: translate(-30px, -100%) 132 | 133 | &.sb-middle 134 | top: 50% 135 | left: 100% 136 | transform: translate(0, -50%) 137 | 138 | &.sb-bottom 139 | bottom: 0 140 | transform: translate(-30px, 100%) 141 | 142 | &.active 143 | &.sb-top 144 | top: -1em 145 | 146 | &.sb-middle 147 | left: calc(100% + 1em) 148 | 149 | &.sb-bottom 150 | bottom: -1em 151 | 152 | &.active 153 | opacity: 1 154 | transition: all .4s ease 155 | visibility: visible 156 | 157 | &.load 158 | transition: none !important 159 | 160 | for num in (1..8) 161 | &.networks-{num} 162 | network(num) 163 | 164 | ul 165 | margin: 0 166 | padding: 0 167 | list-style: none 168 | line-height: 0 169 | 170 | li 171 | position: relative 172 | height: 22px 173 | width: 60px 174 | padding: 12px 0 175 | margin: 0 176 | text-align: center 177 | font-size: 20px 178 | cursor: pointer 179 | z-index: 2 180 | box-sizing: content-box 181 | transition() 182 | 183 | &.enabled 184 | display: inline-block 185 | 186 | &.disabled 187 | display: none 188 | 189 | &:hover 190 | &:before 191 | opacity: 0 192 | 193 | &:after 194 | opacity: .5 195 | 196 | &:before, &:after 197 | content: ' ' 198 | position: absolute 199 | width: inherit 200 | height: inherit 201 | transform: translate(-20%, 0) 202 | transition() 203 | background-repeat: no-repeat !important 204 | 205 | &:before 206 | opacity: 1 207 | 208 | &:after 209 | opacity: 0 210 | 211 | a 212 | position: absolute 213 | top: 0 214 | left: 0 215 | width: 100% 216 | height: 100% 217 | z-index: 3 218 | 219 | for name in networks 220 | socialIcon(name) 221 | 222 | -------------------------------------------------------------------------------- /src/share-utils.js: -------------------------------------------------------------------------------- 1 | import StringUtils from './string-utils'; 2 | 3 | /** 4 | * ShareUtils 5 | * @class 6 | * @classdesc A nice set of utilities. 7 | */ 8 | class ShareUtils { 9 | _getStyle(ele, css) { 10 | var strValue = ""; 11 | 12 | if (document.defaultView && document.defaultView.getComputedStyle) { 13 | strValue = document.defaultView.getComputedStyle(ele, "") 14 | .getPropertyValue(css); 15 | } else if (ele.currentStyle) { 16 | css = css.replace(/\-(\w)/g, function (strMatch, p1) { 17 | return p1.toUpperCase(); 18 | }); 19 | strValue = ele.currentStyle[css]; 20 | } 21 | 22 | return strValue; 23 | } 24 | 25 | /** 26 | * @method _hide 27 | * @description Change element's display to 'none' 28 | * @private 29 | * 30 | * @param {DOMNode} el 31 | */ 32 | _hide(el) { 33 | el.style.display = "none"; 34 | } 35 | 36 | /** 37 | * @method _show 38 | * @description Change element's display to 'block' 39 | * @private 40 | * 41 | * @param {DOMNode} el 42 | */ 43 | _show(el) { 44 | el.style.display = "initial"; 45 | } 46 | 47 | /** 48 | * @method _hasClass 49 | * @description Wrapper to see if an element contains a class. 50 | * @private 51 | * 52 | * @param {DOMNode} el 53 | * @param {String} className 54 | * @returns {Boolean} 55 | */ 56 | _hasClass(el, className) { 57 | return el.classList.contains(className); 58 | } 59 | 60 | /** 61 | * @method addClass 62 | * @description Wrapper to add class to element. 63 | * @private 64 | * 65 | * @param {DOMNode} el 66 | * @param {String} className 67 | */ 68 | _addClass(el, className) { 69 | el.classList.add(className); 70 | } 71 | 72 | /** 73 | * @method removeClass 74 | * @description Wrapper to remove class from element. 75 | * @private 76 | * 77 | * @param {DOMNode} el 78 | * @param {String} className 79 | */ 80 | _removeClass(el, className) { 81 | el.classList.remove(className); 82 | } 83 | 84 | /** 85 | * @method _isEncoded 86 | * @description Wrapper to check if the string is encoded. 87 | * @private 88 | * 89 | * @param {String} str 90 | * @param {Boolean} 91 | */ 92 | _isEncoded(str) { 93 | str = StringUtils.toRFC3986(str); 94 | return decodeURIComponent(str) !== str; 95 | } 96 | 97 | /** 98 | * @method _encode 99 | * @description Wrapper to _encode a string if the string isn't already encoded. 100 | * @private 101 | * 102 | * @param {DOMNode} el 103 | * @param {String} className 104 | */ 105 | _encode(str) { 106 | if (typeof str === 'undefined' || str === null || this._isEncoded(str)) 107 | return encodeURIComponent(str); 108 | else 109 | return StringUtils.toRFC3986(str); 110 | } 111 | 112 | /** 113 | * @method _getUrl 114 | * @description Returns the correct share URL based off of the incoming 115 | * URL and parameters given 116 | * @private 117 | * 118 | * @param {String} url 119 | * @param {boolean} encode 120 | * @param {Object} params 121 | */ 122 | _getUrl(url, encode=false, params={}) { 123 | let qs = (() => { 124 | let results = []; 125 | for (let k of Object.keys(params)) { 126 | let v = params[k]; 127 | results.push(`${k}=${this._encode(v)}`); 128 | } 129 | return results.join('&'); 130 | })(); 131 | 132 | if (qs) qs = `?${qs}`; 133 | 134 | return url + qs; 135 | } 136 | 137 | /** 138 | * @method _updateHref 139 | * @description Makes the elements a tag have a href of the popup link and 140 | * as pops up the share window for the element 141 | * @private 142 | * 143 | * @param {DOMNode} element 144 | * @param {String} url 145 | * @param {Object} params 146 | */ 147 | _updateHref(element, url, params) { 148 | let encode = url.indexOf('mailto:') >= 0; 149 | let a = element.getElementsByTagName('a')[0]; 150 | a.setAttribute('href', this._getUrl(url, !encode, params)); 151 | if(!encode && (!this.config.networks.facebook.loadSdk || element.getAttribute('class') !== 'facebook')) { 152 | let popup = { 153 | width: 500, 154 | height: 350 155 | }; 156 | 157 | popup.top = (screen.height / 2) - (popup.height / 2); 158 | popup.left = (screen.width / 2) - (popup.width / 2); 159 | 160 | window.open( 161 | a.href, 162 | 'targetWindow', ` 163 | toolbar=no, 164 | location=no, 165 | status=no, 166 | menubar=no, 167 | scrollbars=yes, 168 | resizable=yes, 169 | left=${popup.left}, 170 | top=${popup.top}, 171 | width=${popup.width}, 172 | height=${popup.height} 173 | ` 174 | ); 175 | } 176 | } 177 | 178 | /** 179 | * @method popup 180 | * @description Create a window for specified network 181 | * 182 | * @param {String} url 183 | * @param {Object} params 184 | */ 185 | popup(url, params={}) { 186 | let popup = { 187 | width: 500, 188 | height: 350 189 | }; 190 | 191 | popup.top = (screen.height / 2) - (popup.height / 2); 192 | popup.left = (screen.width / 2) - (popup.width / 2); 193 | 194 | let qs = (() => { 195 | let results = []; 196 | for (let k of Object.keys(params)) { 197 | let v = params[k]; 198 | results.push(`${k}=${this._encode(v)}`); 199 | } 200 | return results.join('&'); 201 | })(); 202 | 203 | if (qs) qs = `?${qs}`; 204 | 205 | // This does work even though it contains \n once converted. 206 | window.open( 207 | url+qs, 208 | 'targetWindow', ` 209 | toolbar=no, 210 | location=no, 211 | status=no, 212 | menubar=no, 213 | scrollbars=yes, 214 | resizable=yes, 215 | left=${popup.left}, 216 | top=${popup.top}, 217 | width=${popup.width}, 218 | height=${popup.height} 219 | ` 220 | ); 221 | } 222 | 223 | /** 224 | * @method _merge 225 | * @description Combines two (or more) objects, giving the last one precedence 226 | * @author svlasov-gists 227 | * [Original Gist]{@link https://gist.github.com/svlasov-gists/2383751} 228 | * 229 | * @param {Object} target 230 | * @param {Object} source 231 | * @return {Object} target 232 | */ 233 | _merge(target, source) { 234 | if (typeof target !== 'object') target = {}; 235 | 236 | for (let property in source) { 237 | if (source.hasOwnProperty(property)) { 238 | let sourceProperty = source[property]; 239 | 240 | if (typeof sourceProperty === 'object') { 241 | target[property] = this._merge(target[property], sourceProperty); 242 | continue; 243 | } 244 | 245 | target[property] = sourceProperty; 246 | } 247 | } 248 | 249 | for (let a = 2, l = arguments.length; a < l; a++) 250 | _merge(target, arguments[a]); 251 | 252 | return target; 253 | } 254 | 255 | /** 256 | * @method _objectToArray 257 | * @description Takes an Object and converts it into an array of Objects. This is used when converting a list of DOMNodes into an array. 258 | * 259 | * @param {Object} obj 260 | * @returns {Array} arr 261 | */ 262 | _objToArray(obj) { 263 | let arr = []; 264 | 265 | for (let k in obj) 266 | if (typeof obj[k] === 'object') arr.push(obj[k]); 267 | 268 | return arr; 269 | } 270 | 271 | /** 272 | * @method _isMobile 273 | * @description Returns true if current device is mobile (or PhantomJS for 274 | * testing purposes), and false otherwise 275 | * @author kriskbx 276 | * [Original Gist] {@link https://github.com/kriskbx/whatsapp-sharing/blob/master/src/button.js} 277 | * @private 278 | */ 279 | _isMobile() { 280 | return navigator.userAgent.match(/Android|iPhone|PhantomJS/i) && 281 | !navigator.userAgent.match(/iPod|iPad/i); 282 | } 283 | } 284 | 285 | export default ShareUtils; 286 | -------------------------------------------------------------------------------- /src/string-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * StringUtils 3 | * @class 4 | * @classdesc String utilities. 5 | */ 6 | class StringUtils { 7 | /** 8 | * @method toRFC3986 9 | * @description Encodes the string in RFC3986 10 | * 11 | * @param {String} 12 | * @return {String} 13 | */ 14 | static toRFC3986(s) { 15 | let tmp = encodeURIComponent(s); 16 | tmp.replace(/[!'()*]/g, function(c) { 17 | return `%${c.charCodeAt(0).toString(16)}`; 18 | }); 19 | } 20 | 21 | /** 22 | * @method capFLetter 23 | * @description Returns a capitalized version of the string 24 | * 25 | * @param {String} 26 | * @return {String} 27 | */ 28 | static capFLetter(s) { 29 | return s.charAt(0).toUpperCase() + s.slice(1); 30 | } 31 | } 32 | 33 | export default StringUtils; 34 | -------------------------------------------------------------------------------- /src/svg/email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svg/export.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svg/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svg/googlePlus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svg/linkedin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svg/pinterest.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svg/reddit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svg/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/svg/whatsapp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/features/animation.feature: -------------------------------------------------------------------------------- 1 | Feature: Collision Detection 2 | 3 | Background: 4 | Given I create an Animated Share Button 5 | 6 | @animation 7 | Scenario: Classes will be porperly adjusted according to Share Button position 8 | Then The classes will be correct at middle center 9 | Then The classes will be correct at bottom left 10 | Then The classes will be correct at middle left 11 | Then The classes will be correct at top left 12 | Then The classes will be correct at top center 13 | Then The classes will be correct at top right 14 | Then The classes will be correct at middle right 15 | Then The classes will be correct at bottom right 16 | Then The classes will be correct at bottom center 17 | -------------------------------------------------------------------------------- /tests/features/basic.feature: -------------------------------------------------------------------------------- 1 | Feature: Basic Button 2 | 3 | Background: 4 | Given I create a basic Share Button 5 | 6 | @basic 7 | Scenario: All social networks should be displayed 8 | When I click the Share Button 9 | Then I should see all Social Networks 10 | -------------------------------------------------------------------------------- /tests/features/email.feature: -------------------------------------------------------------------------------- 1 | Feature: Email Network 2 | 3 | Background: 4 | Given I create an Email Share Button 5 | 6 | @email 7 | Scenario: Email network should be displayed and have the correct URL 8 | When I click the Email Share Button 9 | Then I should see the Email button 10 | When I click the Email button 11 | Then I should have a correct Email share url 12 | -------------------------------------------------------------------------------- /tests/features/facebook.feature: -------------------------------------------------------------------------------- 1 | Feature: Facebook Network 2 | 3 | Background: 4 | Given I create a Facebook Share Button with SDK enabled 5 | 6 | @facebook 7 | Scenario: Facebook network should be displayed and have no url 8 | When I click the Facebook Share Button 9 | Then I should see the Facebook button 10 | When I click the Facebook button 11 | Then I should have no url 12 | 13 | @facebook 14 | Scenario: Facebook network should be displayed and have the fallback URL 15 | When I click the Facebook Share Button 16 | Then I should see the Facebook button 17 | When the FB SDK is not loaded 18 | And I click the Facebook button 19 | Then I should have a PHP Facebook share url 20 | 21 | Background: 22 | Given I create a Facebook Share Button with SDK disabled 23 | 24 | @facebook 25 | Scenario: Facebook network should be displayed and have the fallback URL 26 | When I click the Facebook Share Button 27 | Then I should see the Facebook button 28 | When I click the Facebook button 29 | Then I should have a PHP Facebook share url 30 | -------------------------------------------------------------------------------- /tests/features/googleplus.feature: -------------------------------------------------------------------------------- 1 | Feature: Google Plus Network 2 | 3 | Background: 4 | Given I create a Google Plus Share Button 5 | 6 | @googleplus 7 | Scenario: Google Plus network should be displayed and have the correct URL 8 | When I click the Google Plus Share Button 9 | Then I should see the Google Plus button 10 | When I click the Google Plus button 11 | Then I should have a correct Google Plus share url 12 | -------------------------------------------------------------------------------- /tests/features/linkedin.feature: -------------------------------------------------------------------------------- 1 | Feature: Linkedin Network 2 | 3 | Background: 4 | Given I create a Linkedin Share Button 5 | 6 | @linkedin 7 | Scenario: Linkedin network should be displayed and have the correct URL 8 | When I click the Linkedin Share Button 9 | Then I should see the Linkedin button 10 | When I click the Linkedin button 11 | Then I should have a correct Linkedin share url 12 | -------------------------------------------------------------------------------- /tests/features/meta.feature: -------------------------------------------------------------------------------- 1 | Feature: Meta Tag Inheritance 2 | 3 | Background: 4 | Given I create and click a meta tag Share Button 5 | 6 | @meta 7 | Scenario: Network URLs should inherit meta tag properties 8 | When I click all the network buttons 9 | Then All buttons should have valid URLs 10 | -------------------------------------------------------------------------------- /tests/features/ordering.feature: -------------------------------------------------------------------------------- 1 | Feature: Network Ordering 2 | 3 | Background: 4 | Given I create a network ordering Share Button 5 | 6 | @ordering 7 | Scenario: The networks should be displayed in the correct order 8 | When I click the network ordering Share Button 9 | Then I should see the correct number of networks 10 | And They should be in the correct order 11 | -------------------------------------------------------------------------------- /tests/features/pinterest.feature: -------------------------------------------------------------------------------- 1 | Feature: Pinterest Network 2 | 3 | Background: 4 | Given I create a Pinterest Share Button 5 | 6 | @pinterest 7 | Scenario: Pinterest network should be displayed and have the correct URL 8 | When I click the Pinterest Share Button 9 | Then I should see the Pinterest button 10 | When I click the Pinterest button 11 | Then I should have a correct Pinterest share url 12 | -------------------------------------------------------------------------------- /tests/features/reddit.feature: -------------------------------------------------------------------------------- 1 | Feature: Reddit Network 2 | 3 | Background: 4 | Given I create a Reddit Share Button 5 | 6 | @reddit 7 | Scenario: Reddit network should be displayed and have the correct URL 8 | When I click the Reddit Share Button 9 | Then I should see the Reddit button 10 | When I click the Reddit button 11 | Then I should have a correct Reddit share url 12 | -------------------------------------------------------------------------------- /tests/features/twitter.feature: -------------------------------------------------------------------------------- 1 | Feature: Twitter Network 2 | 3 | Background: 4 | Given I create a Twitter Share Button 5 | 6 | @twitter 7 | Scenario: Twitter network should be displayed and have the correct URL 8 | When I click the Twitter Share Button 9 | Then I should see the Twitter button 10 | When I click the Twitter button 11 | Then I should have a correct Twitter share url 12 | -------------------------------------------------------------------------------- /tests/features/ui.feature: -------------------------------------------------------------------------------- 1 | Feature: UI Options 2 | 3 | Background: 4 | Given I create a UI Share Button 5 | 6 | @ui 7 | Scenario: User UI options should be implemented 8 | Then The Share Button should have the correct text 9 | And The buttons should have the correct classes 10 | -------------------------------------------------------------------------------- /tests/features/whatsapp.feature: -------------------------------------------------------------------------------- 1 | Feature: Whatsapp Network 2 | 3 | Background: 4 | Given I create a Whatsapp Share Button 5 | 6 | @whatsapp 7 | Scenario: Whatsapp network should be displayed and have the correct URL 8 | When I click the Whatsapp Share Button 9 | Then I should see the Whatsapp button 10 | When I click the Whatsapp button 11 | Then I should have a correct Whatsapp share url 12 | -------------------------------------------------------------------------------- /tests/fixtures/animation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Animation 5 | 6 | 57 | 58 | 59 | 60 |
    61 | 62 |
    63 | 64 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /tests/fixtures/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Basic 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/fixtures/email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Email 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /tests/fixtures/facebook.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Facebook 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /tests/fixtures/facebook_no_sdk.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Facebook 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /tests/fixtures/googleplus.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Google Plus 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/fixtures/linkedin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Linkedin 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /tests/fixtures/meta.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Meta Tag Networks 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /tests/fixtures/ordering.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Ordering 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/fixtures/pinterest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Pinterest 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /tests/fixtures/reddit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Reddit 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/fixtures/twitter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Twitter 5 | 6 | 7 | 22 | 23 | 24 | 25 | 26 | 27 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /tests/fixtures/ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: UI 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/fixtures/whatsapp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Button Test: Whatsapp 5 | 6 | 21 | 22 | 23 | 24 | 25 | 26 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/helpers/helpers.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | 3 | module.exports = -> 4 | 5 | @Helpers = @Helpers || {} 6 | 7 | @Helpers.fixture = (name) -> 8 | fixtureBase = path.join("tests/fixtures", name) 9 | return "http://localhost:8000/" + fixtureBase + '.html' 10 | -------------------------------------------------------------------------------- /tests/steps/animation.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | @Given /^I create an Animated Share Button$/, () -> 3 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/animation.html") 4 | 5 | @Then /^The classes will be correct at middle center$/, () -> 6 | shareButton = new @Widgets.ShareButton() 7 | social = new @Widgets.ShareButtonSocial() 8 | div = new @Widgets.SBDiv() 9 | shareButton.addClass('middle-center').then -> 10 | social.getAttribute('class').then (classList) -> 11 | (classList.indexOf('sb-top') >= 0 && 12 | classList.indexOf('sb-center') >=0).should.be.true 13 | 14 | @Then /^The classes will be correct at bottom left$/, () -> 15 | shareButton = new @Widgets.ShareButton() 16 | social = new @Widgets.ShareButtonSocial() 17 | shareButton.removeClass('middle-center').then -> 18 | shareButton.addClass('bottom-left').then -> 19 | social.getAttribute('class').then (classList) -> 20 | (classList.indexOf('sb-top') >= 0 && 21 | classList.indexOf('sb-right') >= 0 && 22 | classList.indexOf('sb-center') < 0).should.be.true 23 | 24 | @Then /^The classes will be correct at middle left$/, () -> 25 | shareButton = new @Widgets.ShareButton() 26 | social = new @Widgets.ShareButtonSocial() 27 | shareButton.removeClass('bottom-left').then -> 28 | shareButton.addClass('middle-left').then -> 29 | social.getAttribute('class').then (classList) -> 30 | (classList.indexOf('sb-middle') >= 0 && 31 | classList.indexOf('sb-right') >=0 && 32 | classList.indexOf('sb-top') < 0).should.be.true 33 | 34 | @Then /^The classes will be correct at top left$/, () -> 35 | shareButton = new @Widgets.ShareButton() 36 | social = new @Widgets.ShareButtonSocial() 37 | shareButton.removeClass('middle-left').then -> 38 | shareButton.addClass('top-left').then -> 39 | social.getAttribute('class').then (classList) -> 40 | (classList.indexOf('sb-bottom') >= 0 && 41 | classList.indexOf('sb-right') >=0 && 42 | classList.indexOf('sb-middle') < 0).should.be.true 43 | 44 | @Then /^The classes will be correct at top center$/, () -> 45 | shareButton = new @Widgets.ShareButton() 46 | social = new @Widgets.ShareButtonSocial() 47 | shareButton.removeClass('top-left').then -> 48 | shareButton.addClass('top-center').then -> 49 | social.getAttribute('class').then (classList) -> 50 | (classList.indexOf('sb-bottom') >= 0 && 51 | classList.indexOf('sb-center') >=0 && 52 | classList.indexOf('sb-right') < 0).should.be.true 53 | 54 | @Then /^The classes will be correct at top right$/, () -> 55 | shareButton = new @Widgets.ShareButton() 56 | social = new @Widgets.ShareButtonSocial() 57 | shareButton.removeClass('top-center').then -> 58 | shareButton.addClass('top-right').then -> 59 | social.getAttribute('class').then (classList) -> 60 | (classList.indexOf('sb-bottom') >= 0 && 61 | classList.indexOf('sb-left') >=0 && 62 | classList.indexOf('sb-center') < 0).should.be.true 63 | 64 | @Then /^The classes will be correct at middle right$/, () -> 65 | shareButton = new @Widgets.ShareButton() 66 | social = new @Widgets.ShareButtonSocial() 67 | shareButton.removeClass('top-right').then -> 68 | shareButton.addClass('middle-right').then -> 69 | social.getAttribute('class').then (classList) -> 70 | (classList.indexOf('sb-middle') >= 0 && 71 | classList.indexOf('sb-left') >=0 && 72 | classList.indexOf('sb-bottom') < 0).should.be.true 73 | 74 | @Then /^The classes will be correct at bottom right$/, () -> 75 | shareButton = new @Widgets.ShareButton() 76 | social = new @Widgets.ShareButtonSocial() 77 | shareButton.removeClass('middle-right').then -> 78 | shareButton.addClass('bottom-right').then -> 79 | social.getAttribute('class').then (classList) -> 80 | (classList.indexOf('sb-top') >= 0 && 81 | classList.indexOf('sb-left') >=0 && 82 | classList.indexOf('sb-middle') < 0).should.be.true 83 | 84 | @Then /^The classes will be correct at bottom center$/, () -> 85 | shareButton = new @Widgets.ShareButton() 86 | social = new @Widgets.ShareButtonSocial() 87 | shareButton.removeClass('bottom-right').then -> 88 | shareButton.addClass('bottom-center').then -> 89 | social.getAttribute('class').then (classList) -> 90 | (classList.indexOf('sb-top') >= 0 && 91 | classList.indexOf('sb-center') >=0 && 92 | classList.indexOf('sb-left') < 0).should.be.true 93 | -------------------------------------------------------------------------------- /tests/steps/basic.coffee: -------------------------------------------------------------------------------- 1 | networks = [ 2 | 'pinterest' 3 | 'twitter' 4 | 'facebook' 5 | 'whatsapp' 6 | 'gplus' 7 | 'reddit' 8 | 'linkedin' 9 | 'paper-plane' 10 | ] 11 | 12 | module.exports = -> 13 | @Given /^I create a basic Share Button$/, -> 14 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/basic.html") 15 | 16 | @When /^I click the Share Button$/, -> 17 | new @Widgets.ShareButton().click() 18 | 19 | @Then /^I should see all Social Networks$/, -> 20 | new @Widgets 21 | .ShareButtonNetworks() 22 | .each (item, i) -> 23 | unless (item.hasClass('whatsapp').then (tf) -> tf) 24 | item.isVisible().should.eventually.eql(true) 25 | -------------------------------------------------------------------------------- /tests/steps/email.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | @Given /^I create an Email Share Button$/, () -> 3 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/email.html") 4 | 5 | @When /^I click the Email Share Button$/, () -> 6 | new @Widgets.ShareButton().click() 7 | 8 | @Then /^I should see the Email button$/, () -> 9 | new @Widgets 10 | .ShareButtonNetworks() 11 | .filter( (item) -> 12 | item.hasClass('email') 13 | .then (class1) -> 14 | return class1 15 | .then (class1) -> 16 | item.hasClass('enabled') 17 | .then (class2) -> 18 | return (class1 && class2) 19 | ) 20 | .should.eventually.have.length(1) 21 | 22 | @When /^I click the Email button$/, () -> 23 | new @Widgets 24 | .ShareButtonNetworks() 25 | .filter( (item) -> 26 | item.hasClass('email') 27 | ) 28 | .then (list) -> 29 | list[0].click('a') 30 | 31 | @Then /^I should have a correct Email share url$/, () -> 32 | new @Widgets 33 | .ShareButtonNetworks() 34 | .filter( (item) -> 35 | item.hasClass('email') 36 | ) 37 | .then (list) -> 38 | list[0].getAttribute( 39 | selector: 'a', 40 | attribute: 'href' 41 | ).should.eventually.eql('mailto:?subject=email%20title&body=email%20description') 42 | -------------------------------------------------------------------------------- /tests/steps/facebook.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | @Given /^I create a Facebook Share Button with SDK enabled$/, () -> 3 | @driver.visit("http://localhost:8000/tests/fixtures/facebook.html") 4 | 5 | @Given /^I create a Facebook Share Button with SDK disabled$/, () -> 6 | @driver.visit("http://localhost:8000/tests/fixtures/facebook_no_sdk.html") 7 | 8 | @When /^I click the Facebook Share Button$/, () -> 9 | new @Widgets.ShareButton().click() 10 | 11 | @Then /^I should see the Facebook button$/, () -> 12 | new @Widgets 13 | .ShareButtonNetworks() 14 | .filter( (item) -> 15 | item.hasClass('facebook') 16 | .then (class1) -> 17 | return class1 18 | .then (class1) -> 19 | item.hasClass('enabled') 20 | .then (class2) -> 21 | return (class1 && class2) 22 | ) 23 | .should.eventually.have.length(1) 24 | 25 | @When /^the FB SDK is not loaded$/, () -> 26 | # Simulate FB didn't load. 27 | new @Widgets.ShareButton().removeFacebookSDK() 28 | 29 | @When /^I click the Facebook button$/, () -> 30 | new @Widgets 31 | .ShareButtonNetworks() 32 | .filter( (item) -> 33 | item.hasClass('facebook') 34 | ) 35 | .then (list) -> 36 | list[0].click('a') 37 | 38 | @Then /^I should have a PHP Facebook share url$/, () -> 39 | new @Widgets 40 | .ShareButtonNetworks() 41 | .filter( (item) -> 42 | item.hasClass('facebook') 43 | ) 44 | .then (list) -> 45 | list[0].getAttribute( 46 | selector: 'a', 47 | attribute: 'href' 48 | ).should.eventually.eq('https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fwww.example.com') 49 | 50 | @Then /^I should have no url$/, () -> 51 | new @Widgets 52 | .ShareButtonNetworks() 53 | .filter( (item) -> 54 | item.hasClass('facebook') 55 | ) 56 | .then (list) -> 57 | list[0].getAttribute( 58 | selector: 'a', 59 | attribute: 'href' 60 | ).should.eventually.eq(null) 61 | -------------------------------------------------------------------------------- /tests/steps/googleplus.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | @Given /^I create a Google Plus Share Button$/, () -> 3 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/googleplus.html") 4 | 5 | @When /^I click the Google Plus Share Button$/, () -> 6 | new @Widgets.ShareButton().click() 7 | 8 | @Then /^I should see the Google Plus button$/, () -> 9 | new @Widgets 10 | .ShareButtonNetworks() 11 | .filter( (item) -> 12 | item.hasClass('googlePlus') 13 | .then (class1) -> 14 | return class1 15 | .then (class1) -> 16 | item.hasClass('enabled') 17 | .then (class2) -> 18 | return (class1 && class2) 19 | ) 20 | .should.eventually.have.length(1) 21 | 22 | @When /^I click the Google Plus button$/, () -> 23 | new @Widgets 24 | .ShareButtonNetworks() 25 | .filter( (item) -> 26 | item.hasClass('googlePlus') 27 | ) 28 | .then (list) -> 29 | list[0].click('a') 30 | 31 | @Then /^I should have a correct Google Plus share url$/, () -> 32 | new @Widgets 33 | .ShareButtonNetworks() 34 | .filter( (item) -> 35 | item.hasClass('googlePlus') 36 | ) 37 | .then (list) -> 38 | list[0].getAttribute( 39 | selector: 'a', 40 | attribute: 'href' 41 | ).should.eventually.eq('https://plus.google.com/share?url=http%3A%2F%2Fwww.example.com%2F') 42 | -------------------------------------------------------------------------------- /tests/steps/linkedin.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | @Given /^I create a Linkedin Share Button$/, () -> 3 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/linkedin.html") 4 | 5 | @When /^I click the Linkedin Share Button$/, () -> 6 | new @Widgets.ShareButton().click() 7 | 8 | @Then /^I should see the Linkedin button$/, () -> 9 | new @Widgets 10 | .ShareButtonNetworks() 11 | .filter( (item) -> 12 | item.hasClass('linkedin') 13 | .then (class1) -> 14 | return class1 15 | .then (class1) -> 16 | item.hasClass('enabled') 17 | .then (class2) -> 18 | return (class1 && class2) 19 | ) 20 | .should.eventually.have.length(1) 21 | 22 | @When /^I click the Linkedin button$/, () -> 23 | new @Widgets 24 | .ShareButtonNetworks() 25 | .filter( (item) -> 26 | item.hasClass('linkedin') 27 | ) 28 | .then (list) -> 29 | list[0].click('a') 30 | 31 | @Then /^I should have a correct Linkedin share url$/, () -> 32 | new @Widgets 33 | .ShareButtonNetworks() 34 | .filter( (item) -> 35 | item.hasClass('linkedin') 36 | ) 37 | .then (list) -> 38 | list[0].getAttribute( 39 | selector: 'a', 40 | attribute: 'href' 41 | ).should.eventually.eq('https://www.linkedin.com/shareArticle?mini=true&url=http%3A%2F%2Fwww.example.com&title=linkedin%20title&summary=linkedin%20description') 42 | -------------------------------------------------------------------------------- /tests/steps/meta.coffee: -------------------------------------------------------------------------------- 1 | hrefs = [ 2 | 'https://www.pinterest.com/pin/create/button?url=http%3A%2F%2Flocalhost%3A8000%2Ftests%2Ffixtures%2Fmeta.html&media=http%3A%2F%2Fcarrot.is%2Fimg%2Ffb-share.jpg&description=test%20description' 3 | 'https://twitter.com/intent/tweet?text=test%20description&url=http%3A%2F%2Flocalhost%3A8000%2Ftests%2Ffixtures%2Fmeta.html' 4 | 'https://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Flocalhost%3A8000%2Ftests%2Ffixtures%2Fmeta.html' 5 | 'whatsapp://send?text=test%20description%20http%3A%2F%2Flocalhost%3A8000%2Ftests%2Ffixtures%2Fmeta.html' 6 | 'https://plus.google.com/share?url=http%3A%2F%2Flocalhost%3A8000%2Ftests%2Ffixtures%2Fmeta.html' 7 | 'http://www.reddit.com/submit?url=http%3A%2F%2Flocalhost%3A8000%2Ftests%2Ffixtures%2Fmeta.html&title=test%20title' 8 | 'https://www.linkedin.com/shareArticle?mini=true&url=http%3A%2F%2Flocalhost%3A8000%2Ftests%2Ffixtures%2Fmeta.html&title=test%20title&summary=test%20description' 9 | 'mailto:?subject=test%20title&body=test%20description' 10 | ] 11 | 12 | module.exports = -> 13 | @Given /^I create and click a meta tag Share Button$/, () -> 14 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/meta.html") 15 | new @Widgets.ShareButton().click() 16 | 17 | @When /^I click all the network buttons$/, () -> 18 | new @Widgets 19 | .ShareButtonNetworks() 20 | .each (item, index) -> 21 | unless (item.hasClass('whatsapp').then (tf) -> return tf) 22 | item.click('a') 23 | 24 | @Then /^All buttons should have valid URLs$/, () -> 25 | new @Widgets 26 | .ShareButtonNetworks() 27 | .each (item, index) -> 28 | unless (item.hasClass('whatsapp').then (tf) -> return tf) 29 | item.getAttribute( 30 | selector: 'a', 31 | attribute: 'href' 32 | ).should.eventually.eq(hrefs[index]) 33 | -------------------------------------------------------------------------------- /tests/steps/ordering.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | @Given /^I create a network ordering Share Button$/, () -> 3 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/ordering.html") 4 | 5 | @When /^I click the network ordering Share Button$/, () -> 6 | new @Widgets.ShareButton().click() 7 | 8 | @Then /^I should see the correct number of networks$/, () -> 9 | new @Widgets 10 | .ShareButtonNetworks() 11 | .filter( (item) -> 12 | item.hasClass('enabled') 13 | ) 14 | .should.eventually.have.length(3) 15 | 16 | @Then /^They should be in the correct order$/, () -> 17 | new @Widgets 18 | .ShareButtonNetworks() 19 | .filter( (item) -> 20 | item.hasClass('enabled') 21 | ) 22 | .then (list) -> 23 | list[0].hasClass('facebook').should.eventually.be.true 24 | list[1].hasClass('googlePlus').should.eventually.be.true 25 | list[2].hasClass('twitter').should.eventually.be.true 26 | -------------------------------------------------------------------------------- /tests/steps/pinterest.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | @Given /^I create a Pinterest Share Button$/, () -> 3 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/pinterest.html") 4 | 5 | @When /^I click the Pinterest Share Button$/, () -> 6 | new @Widgets.ShareButton().click() 7 | 8 | @Then /^I should see the Pinterest button$/, () -> 9 | new @Widgets 10 | .ShareButtonNetworks() 11 | .filter( (item) -> 12 | item.hasClass('pinterest') 13 | .then (class1) -> 14 | return class1 15 | .then (class1) -> 16 | item.hasClass('enabled') 17 | .then (class2) -> 18 | return (class1 && class2) 19 | ) 20 | .should.eventually.have.length(1) 21 | 22 | @When /^I click the Pinterest button$/, () -> 23 | new @Widgets 24 | .ShareButtonNetworks() 25 | .filter( (item) -> 26 | item.hasClass('pinterest') 27 | ) 28 | .then (list) -> 29 | list[0].click('a') 30 | 31 | @Then /^I should have a correct Pinterest share url$/, () -> 32 | new @Widgets 33 | .ShareButtonNetworks() 34 | .filter( (item) -> 35 | item.hasClass('pinterest') 36 | ) 37 | .then (list) -> 38 | list[0].getAttribute( 39 | selector: 'a', 40 | attribute: 'href' 41 | ).should.eventually.eq('https://www.pinterest.com/pin/create/button?url=http%3A%2F%2Fwww.example.com&media=http%3A%2F%2Fcarrot.is%2Fimg%2Ffb-share.jpg&description=pinterest%20discription') 42 | -------------------------------------------------------------------------------- /tests/steps/reddit.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | @Given /^I create a Reddit Share Button$/, () -> 3 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/reddit.html") 4 | 5 | @When /^I click the Reddit Share Button$/, () -> 6 | new @Widgets.ShareButton().click() 7 | 8 | @Then /^I should see the Reddit button$/, () -> 9 | new @Widgets 10 | .ShareButtonNetworks() 11 | .filter( (item) -> 12 | item.hasClass('reddit') 13 | .then (class1) -> 14 | return class1 15 | .then (class1) -> 16 | item.hasClass('enabled') 17 | .then (class2) -> 18 | return (class1 && class2) 19 | ) 20 | .should.eventually.have.length(1) 21 | 22 | @When /^I click the Reddit button$/, () -> 23 | new @Widgets 24 | .ShareButtonNetworks() 25 | .filter( (item) -> 26 | item.hasClass('reddit') 27 | ) 28 | .then (list) -> 29 | list[0].click('a') 30 | 31 | @Then /^I should have a correct Reddit share url$/, () -> 32 | new @Widgets 33 | .ShareButtonNetworks() 34 | .filter( (item) -> 35 | item.hasClass('reddit') 36 | ) 37 | .then (list) -> 38 | list[0].getAttribute( 39 | selector: 'a', 40 | attribute: 'href' 41 | ).should.eventually.eq('http://www.reddit.com/submit?url=http%3A%2F%2Fwww.example.com&title=reddit%20title') 42 | -------------------------------------------------------------------------------- /tests/steps/twitter.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | @Given /^I create a Twitter Share Button$/, () -> 3 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/twitter.html") 4 | 5 | @When /^I click the Twitter Share Button$/, () -> 6 | new @Widgets.ShareButton().click() 7 | 8 | @Then /^I should see the Twitter button$/, () -> 9 | new @Widgets 10 | .ShareButtonNetworks() 11 | .filter( (item) -> 12 | item.hasClass('twitter') 13 | .then (class1) -> 14 | return class1 15 | .then (class1) -> 16 | item.hasClass('enabled') 17 | .then (class2) -> 18 | return (class1 && class2) 19 | ) 20 | .should.eventually.have.length(1) 21 | 22 | @When /^I click the Twitter button$/, () -> 23 | new @Widgets 24 | .ShareButtonNetworks() 25 | .filter( (item) -> 26 | item.hasClass('twitter') 27 | ) 28 | .then (list) -> 29 | list[0].click('a') 30 | 31 | @Then /^I should have a correct Twitter share url$/, () -> 32 | new @Widgets 33 | .ShareButtonNetworks() 34 | .filter( (item) -> 35 | item.hasClass('twitter') 36 | ) 37 | .then (list) -> 38 | list[0].getAttribute( 39 | selector: 'a', 40 | attribute: 'href' 41 | ).should.eventually.eq('https://twitter.com/intent/tweet?text=twitter%20description&url=http%3A%2F%2Fwww.example.com') 42 | -------------------------------------------------------------------------------- /tests/steps/ui.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | @Given /^I create a UI Share Button$/, () -> 3 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/ui.html") 4 | 5 | @Then /^The Share Button should have the correct text$/, () -> 6 | new @Widgets 7 | .ShareButton().getText() 8 | .should.eventually.eq('TEST TEXT') 9 | 10 | @Then /^The buttons should have the correct classes$/, () -> 11 | social = new @Widgets.ShareButtonTestSocial() 12 | social.hasClass('test-bottom').should.eventually.be.true 13 | social.hasClass('test-left').should.eventually.be.true 14 | -------------------------------------------------------------------------------- /tests/steps/whatsapp.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | @Given /^I create a Whatsapp Share Button$/, () -> 3 | @driver.visit("file:///#{process.cwd()}/tests/fixtures/whatsapp.html") 4 | 5 | @When /^I click the Whatsapp Share Button$/, () -> 6 | new @Widgets.ShareButton().click() 7 | 8 | @Then /^I should see the Whatsapp button$/, () -> 9 | new @Widgets 10 | .ShareButtonNetworks() 11 | .filter( (item) -> 12 | item.hasClass('whatsapp') 13 | .then (class1) -> 14 | return class1 15 | .then (class1) -> 16 | item.hasClass('enabled') 17 | .then (class2) -> 18 | return (class1 && class2) 19 | ) 20 | .should.eventually.have.length(1) 21 | 22 | @When /^I click the Whatsapp button$/, () -> 23 | new @Widgets 24 | .ShareButtonNetworks() 25 | .filter( (item) -> 26 | item.hasClass('whatsapp') 27 | ) 28 | .then (list) -> 29 | list[0].click('a') 30 | 31 | @Then /^I should have a correct Whatsapp share url$/, () -> 32 | new @Widgets 33 | .ShareButtonNetworks() 34 | .filter( (item) -> 35 | item.hasClass('whatsapp') 36 | ) 37 | .then (list) -> 38 | list[0].getAttribute( 39 | selector: 'a', 40 | attribute: 'href' 41 | ).should.eventually.eq('whatsapp://send?text=whatsapp%20description%20http%3A%2F%2Fwww.example.com') 42 | -------------------------------------------------------------------------------- /tests/widgets/shareButton.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | 3 | module.exports = -> 4 | 5 | @Widgets = @Widgets || {} 6 | 7 | @Widgets.ShareButton = @Widget.extend 8 | root: 'share-button' 9 | 10 | switchToPopup: -> 11 | popUpWindowHandle = @driver.getAllWindowHandles() 12 | .then (res) -> return res[res.length - 1] 13 | @driver.switchTo().window(popUpWindowHandle) 14 | 15 | addAnimate: -> 16 | @addClass('animate') 17 | 18 | removeFacebookSDK: -> 19 | @driver.executeScript('window.FB = null') 20 | 21 | @Widgets.SBDiv = @Widget.extend 22 | root: 'div' 23 | 24 | @Widgets.ShareButtonSocial = @Widget.extend 25 | root: '.sb-social' 26 | 27 | @Widgets.ShareButtonTestSocial = @Widget.extend 28 | root: '.test-social' 29 | 30 | @Widgets.ShareButtonNetworks = @Widget.List.extend 31 | root: 'share-button ul' 32 | itemSelector: 'li' 33 | --------------------------------------------------------------------------------