├── .eslintignore
├── .gitignore
├── bower.json
├── src
├── modules
│ ├── events.js
│ ├── data-attr.js
│ ├── count-api.js
│ ├── open-share.js
│ ├── share-api.js
│ ├── count-transforms.js
│ ├── count.js
│ └── share-transforms.js
├── index.js
├── browser.js
└── test.js
├── lib
├── dashToCamel.js
├── initializeWatcher.js
├── share.js
├── initializeCountNode.js
├── init.js
├── countReduce.js
├── storeCount.js
├── initializeShareNode.js
├── initializeNodes.js
└── setData.js
├── count.js
├── share.js
├── .eslintrc.json
├── package.json
├── README.md
├── analytics.js
├── async-test.html
├── dist
├── openshare.min.js
├── test.js
└── openshare.js
└── test.html
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .jscsrc
3 | .jshintrc
4 | .vhost
5 | .sass-cache
6 | npm-debug.log
7 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "open-share",
3 | "version": "0.8.1",
4 | "authors": [
5 | "Digital Surgeons",
6 | "Adam Chambers"
7 | ],
8 | "main": "dist/open-share.js",
9 | "license": "MIT"
10 | }
11 |
--------------------------------------------------------------------------------
/src/modules/events.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Trigger custom OpenShare namespaced event
3 | */
4 | export default {
5 | trigger(element, event) {
6 | const ev = document.createEvent('Event');
7 | ev.initEvent(`OpenShare.${event}`, true, true);
8 | element.dispatchEvent(ev);
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import attr from './modules/data-attr';
2 | import ShareAPI from './modules/share-api';
3 | import CountAPI from './modules/count-api';
4 | import analyticsAPI from '../analytics';
5 |
6 | const DataAttr = attr(); // eslint-disable-line
7 |
8 | export default {
9 | share: ShareAPI(),
10 | count: CountAPI(),
11 | analytics: analyticsAPI,
12 | };
13 |
--------------------------------------------------------------------------------
/lib/dashToCamel.js:
--------------------------------------------------------------------------------
1 | // type contains a dash
2 | // transform to camelcase for function reference
3 | // TODO: only supports single dash, should should support multiple
4 | export default (dash, type) => {
5 | const nextChar = type.substr(dash + 1, 1);
6 | const group = type.substr(dash, 2);
7 |
8 | type = type.replace(group, nextChar.toUpperCase());
9 | return type;
10 | };
11 |
--------------------------------------------------------------------------------
/lib/initializeWatcher.js:
--------------------------------------------------------------------------------
1 | export default function initializeWatcher(watcher, fn) {
2 | [].forEach.call(watcher, (w) => {
3 | const observer = new MutationObserver((mutations) => {
4 | // target will match between all mutations so just use first
5 | fn(mutations[0].target);
6 | });
7 |
8 | observer.observe(w, {
9 | childList: true,
10 | });
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/lib/share.js:
--------------------------------------------------------------------------------
1 | import Events from '../src/modules/events';
2 | import setData from './setData';
3 |
4 | export default function share(e, os, openShare) {
5 | // if dynamic instance then fetch attributes again in case of updates
6 | if (openShare.dynamic) {
7 | setData(openShare, os);
8 | }
9 |
10 | openShare.share(e);
11 |
12 | // trigger shared event
13 | Events.trigger(os, 'shared');
14 | }
15 |
--------------------------------------------------------------------------------
/lib/initializeCountNode.js:
--------------------------------------------------------------------------------
1 | import Count from '../src/modules/count';
2 |
3 | export default function initializeCountNode(os) {
4 | // initialize open share object with type attribute
5 | const type = os.getAttribute('data-open-share-count');
6 | const url = os.getAttribute('data-open-share-count-repo') ||
7 | os.getAttribute('data-open-share-count-shot') ||
8 | os.getAttribute('data-open-share-count-url');
9 | const count = new Count(type, url);
10 |
11 | count.count(os);
12 | os.setAttribute('data-open-share-node', type);
13 | }
14 |
--------------------------------------------------------------------------------
/count.js:
--------------------------------------------------------------------------------
1 | import Init from './lib/init';
2 | import cb from './lib/initializeCountNode';
3 |
4 | function init() {
5 | Init({
6 | api: 'count',
7 | selector: '[data-open-share-count]:not([data-open-share-node])',
8 | cb,
9 | })();
10 | }
11 | export default () => {
12 | if (document.readyState === 'complete') {
13 | init();
14 | }
15 | document.addEventListener('readystatechange', () => {
16 | if (document.readyState === 'complete') {
17 | init();
18 | }
19 | }, false);
20 | return require('./src/modules/count-api')();
21 | };
22 |
--------------------------------------------------------------------------------
/share.js:
--------------------------------------------------------------------------------
1 | import Init from './lib/init';
2 | import cb from './lib/initializeShareNode';
3 | import countAPI from './src/modules/count-api';
4 |
5 | function init() {
6 | Init({
7 | api: 'share',
8 | selector: '[data-open-share]:not([data-open-share-node])',
9 | cb,
10 | })();
11 | }
12 | export default () => {
13 | if (document.readyState === 'complete') {
14 | init();
15 | }
16 | document.addEventListener('readystatechange', () => {
17 | if (document.readyState === 'complete') {
18 | init();
19 | }
20 | }, false);
21 | return countAPI();
22 | };
23 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "rules": {
4 | "new-cap": [0, { "capIsNew": true }],
5 | "max-len": [2, 200],
6 | "no-plusplus": 0,
7 | "no-mixed-operators": 0,
8 | "no-alert": 0,
9 | "no-restricted-syntax": 0,
10 | "no-param-reassign": 0,
11 | "no-new": 0,
12 | "no-console": 0,
13 | "global-require": 0,
14 | "one-var": 0,
15 | "consistent-return": 0,
16 | "no-underscore-dangle": 0,
17 | "no-use-before-define": 0,
18 | "no-shadow": 0
19 | },
20 | "env": {
21 | "browser": true,
22 | "node": true
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/init.js:
--------------------------------------------------------------------------------
1 | import initializeNodes from './initializeNodes';
2 | import initializeWatcher from './initializeWatcher';
3 |
4 | export default function init(opts) {
5 | return () => {
6 | const initNodes = initializeNodes({
7 | api: opts.api || null,
8 | container: opts.container || document,
9 | selector: opts.selector,
10 | cb: opts.cb,
11 | });
12 |
13 | initNodes();
14 |
15 | // check for mutation observers before using, IE11 only
16 | if (window.MutationObserver !== undefined) {
17 | initializeWatcher(document.querySelectorAll('[data-open-share-watch]'), initNodes);
18 | }
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/src/browser.js:
--------------------------------------------------------------------------------
1 | import DataAttr from './modules/data-attr';
2 | import ShareAPI from './modules/share-api';
3 | import Events from './modules/events';
4 | import OpenShare from './modules/open-share';
5 | import ShareTransforms from './modules/share-transforms';
6 | import Count from './modules/count';
7 | import CountAPI from './modules/count-api';
8 | import analyticsAPI from '../analytics';
9 |
10 | const browser = () => {
11 | DataAttr(OpenShare, Count, ShareTransforms, Events);
12 | window.OpenShare = {
13 | share: ShareAPI(OpenShare, ShareTransforms, Events),
14 | count: CountAPI(),
15 | analytics: analyticsAPI,
16 | };
17 | };
18 | export default browser();
19 |
--------------------------------------------------------------------------------
/src/modules/data-attr.js:
--------------------------------------------------------------------------------
1 | import Init from '../../lib/init';
2 | import share from '../../lib/initializeShareNode';
3 | import count from '../../lib/initializeCountNode';
4 |
5 | function init() {
6 | Init({
7 | selector: {
8 | share: '[data-open-share]:not([data-open-share-node])',
9 | count: '[data-open-share-count]:not([data-open-share-node])',
10 | },
11 | cb: {
12 | share,
13 | count,
14 | },
15 | })();
16 | }
17 | export default () => {
18 | if (document.readyState === 'complete') {
19 | return init();
20 | }
21 | document.addEventListener('readystatechange', () => {
22 | if (document.readyState === 'complete') {
23 | init();
24 | }
25 | }, false);
26 | };
27 |
--------------------------------------------------------------------------------
/lib/countReduce.js:
--------------------------------------------------------------------------------
1 | function round(x, precision) {
2 | if (typeof x !== 'number') {
3 | throw new TypeError('Expected value to be a number');
4 | }
5 |
6 | const exponent = precision > 0 ? 'e' : 'e-';
7 | const exponentNeg = precision > 0 ? 'e-' : 'e';
8 | precision = Math.abs(precision);
9 |
10 | return Number(Math.round(x + exponent + precision) + exponentNeg + precision);
11 | }
12 |
13 | function thousandify(num) {
14 | return `${round(num / 1000, 1)}K`;
15 | }
16 |
17 | function millionify(num) {
18 | return `${round(num / 1000000, 1)}M`;
19 | }
20 |
21 | export default function countReduce(el, count, cb) {
22 | if (count > 999999) {
23 | el.innerHTML = millionify(count);
24 | if (cb && typeof cb === 'function') cb(el);
25 | } else if (count > 999) {
26 | el.innerHTML = thousandify(count);
27 | if (cb && typeof cb === 'function') cb(el);
28 | } else {
29 | el.innerHTML = count;
30 | if (cb && typeof cb === 'function') cb(el);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/storeCount.js:
--------------------------------------------------------------------------------
1 | /*
2 | Sometimes social platforms get confused and drop share counts.
3 | In this module we check if the returned count is less than the count in
4 | localstorage.
5 | If the local count is greater than the returned count,
6 | we store the local count + the returned count.
7 | Otherwise, store the returned count.
8 | */
9 |
10 | export default (t, count) => {
11 | const isArr = t.type.indexOf(',') > -1;
12 | const local = Number(t.storeGet(`${t.type}-${t.shared}`));
13 |
14 | if (local > count && !isArr) {
15 | const latestCount = Number(t.storeGet(`${t.type}-${t.shared}-latestCount`));
16 | t.storeSet(`${t.type}-${t.shared}-latestCount`, count);
17 |
18 | count = isNumeric(latestCount) && latestCount > 0 ?
19 | count += local - latestCount :
20 | count += local;
21 | }
22 |
23 | if (!isArr) t.storeSet(`${t.type}-${t.shared}`, count);
24 | return count;
25 | };
26 |
27 | function isNumeric(n) {
28 | return !isNaN(parseFloat(n)) && isFinite(n);
29 | }
30 |
--------------------------------------------------------------------------------
/src/modules/count-api.js:
--------------------------------------------------------------------------------
1 | /**
2 | * count API
3 | */
4 |
5 | import count from './count';
6 |
7 | export default () => { //eslint-disable-line
8 | // global OpenShare referencing internal class for instance generation
9 | class Count {
10 |
11 | constructor({
12 | type,
13 | url,
14 | appendTo = false,
15 | element,
16 | classes,
17 | key = null,
18 | }, cb) {
19 | const countNode = document.createElement(element || 'span');
20 |
21 | countNode.setAttribute('data-open-share-count', type);
22 | countNode.setAttribute('data-open-share-count-url', url);
23 | if (key) countNode.setAttribute('data-open-share-key', key);
24 |
25 | countNode.classList.add('open-share-count');
26 |
27 | if (classes && Array.isArray(classes)) {
28 | classes.forEach((cssCLass) => {
29 | countNode.classList.add(cssCLass);
30 | });
31 | }
32 |
33 | if (appendTo) {
34 | return new count(type, url).count(countNode, cb, appendTo);
35 | }
36 |
37 | return new count(type, url).count(countNode, cb);
38 | }
39 | }
40 |
41 | return Count;
42 | };
43 |
--------------------------------------------------------------------------------
/lib/initializeShareNode.js:
--------------------------------------------------------------------------------
1 | import ShareTransforms from '../src/modules/share-transforms';
2 | import OpenShare from '../src/modules/open-share';
3 | import setData from './setData';
4 | import share from './share';
5 | import dashToCamel from './dashToCamel';
6 |
7 | export default function initializeShareNode(os) {
8 | // initialize open share object with type attribute
9 | let type = os.getAttribute('data-open-share');
10 | const dash = type.indexOf('-');
11 |
12 | if (dash > -1) {
13 | type = dashToCamel(dash, type);
14 | }
15 |
16 | const transform = ShareTransforms[type];
17 |
18 | if (!transform) {
19 | throw new Error(`Open Share: ${type} is an invalid type`);
20 | }
21 |
22 | const openShare = new OpenShare(type, transform);
23 |
24 | // specify if this is a dynamic instance
25 | if (os.getAttribute('data-open-share-dynamic')) {
26 | openShare.dynamic = true;
27 | }
28 |
29 | // specify if this is a popup instance
30 | if (os.getAttribute('data-open-share-popup')) {
31 | openShare.popup = true;
32 | }
33 |
34 | // set all optional attributes on open share instance
35 | setData(openShare, os);
36 |
37 | // open share dialog on click
38 | os.addEventListener('click', (e) => {
39 | share(e, os, openShare);
40 | });
41 |
42 | os.addEventListener('OpenShare.trigger', (e) => {
43 | share(e, os, openShare);
44 | });
45 |
46 | os.setAttribute('data-open-share-node', type);
47 | }
48 |
--------------------------------------------------------------------------------
/lib/initializeNodes.js:
--------------------------------------------------------------------------------
1 | import Events from '../src/modules/events';
2 | import analytics from '../analytics';
3 |
4 | export default function initializeNodes(opts) {
5 | // loop through open share node collection
6 | return () => {
7 | // check for analytics
8 | checkAnalytics();
9 |
10 | if (opts.api) {
11 | const nodes = opts.container.querySelectorAll(opts.selector);
12 | [].forEach.call(nodes, opts.cb);
13 |
14 | // trigger completed event
15 | Events.trigger(document, `${opts.api}-loaded`);
16 | } else {
17 | // loop through open share node collection
18 | const shareNodes = opts.container.querySelectorAll(opts.selector.share);
19 | [].forEach.call(shareNodes, opts.cb.share);
20 |
21 | // trigger completed event
22 | Events.trigger(document, 'share-loaded');
23 |
24 | // loop through count node collection
25 | const countNodes = opts.container.querySelectorAll(opts.selector.count);
26 | [].forEach.call(countNodes, opts.cb.count);
27 |
28 | // trigger completed event
29 | Events.trigger(document, 'count-loaded');
30 | }
31 | };
32 | }
33 |
34 | function checkAnalytics() {
35 | // check for analytics
36 | if (document.querySelector('[data-open-share-analytics]')) {
37 | const provider = document.querySelector('[data-open-share-analytics]')
38 | .getAttribute('data-open-share-analytics');
39 |
40 | if (provider.indexOf(',') > -1) {
41 | const providers = provider.split(',');
42 | providers.forEach(p => analytics(p));
43 | } else analytics(provider);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "openshare",
3 | "version": "1.4.11",
4 | "description": "Social sharing for developers",
5 | "main": "dist/openshare.js",
6 | "browserify": {
7 | "transform": [
8 | "rollupify",
9 | [
10 | "babelify",
11 | {
12 | "presets": [
13 | "es2015"
14 | ]
15 | }
16 | ]
17 | ]
18 | },
19 | "scripts": {
20 | "watch:js": "watchify src/browser.js -o dist/openshare.js -dv",
21 | "watch:test": "watchify src/test.js -o dist/test.js -dv",
22 | "build:js": "browserify src/browser.js > dist/openshare.js",
23 | "build:min:js": "browserify src/browser.js -g uglifyify | uglifyjs -cm > dist/openshare.min.js",
24 | "build:test": "browserify src/test.js -g uglifyify | uglifyjs -cm > dist/test.js",
25 | "watch": "npm run watch:js & npm run watch:test",
26 | "dev": "npm run watch & npm run test",
27 | "build": "npm run build:js & npm run build:test",
28 | "production": "npm run build & npm run test",
29 | "test": "opn http://0.0.0.0:8000/test.html && ecstatic .",
30 | "lint": "eslint src/",
31 | "prepublish": "npm run build:js"
32 | },
33 | "repository": {
34 | "type": "git",
35 | "url": "git+https://github.com/OpenShare/openshare.git"
36 | },
37 | "keywords": [
38 | "social",
39 | "share",
40 | "sharing"
41 | ],
42 | "author": "Digital Surgeons",
43 | "license": "MIT",
44 | "devDependencies": {
45 | "babel": "^5.8.23",
46 | "babel-eslint": "^7.0.0",
47 | "babel-preset-es2015": "^6.9.0",
48 | "babelify": "^7.3.0",
49 | "browserify": "^11.2.0",
50 | "ecstatic": "^1.4.1",
51 | "eslint": "^3.7.1",
52 | "eslint-config-airbnb": "^12.0.0",
53 | "eslint-plugin-import": "^1.16.0",
54 | "eslint-plugin-jsx-a11y": "^2.2.3",
55 | "eslint-plugin-react": "^6.3.0",
56 | "node-sass": "^3.8.0",
57 | "opn-cli": "^3.1.0",
58 | "rollupify": "^0.3.5",
59 | "uglify-js": "^2.7.0",
60 | "uglifyify": "^3.0.2",
61 | "watchify": "^3.7.0"
62 | },
63 | "bugs": {
64 | "url": "https://github.com/OpenShare/openshare/issues"
65 | },
66 | "homepage": "https://github.com/OpenShare/openshare#readme",
67 | "dependencies": {
68 | "eslint": "^3.7.1",
69 | "eslint-config-airbnb": "^12.0.0",
70 | "eslint-plugin-import": "^1.16.0",
71 | "eslint-plugin-jsx-a11y": "^2.2.3",
72 | "eslint-plugin-react": "^6.3.0",
73 | "rollupify": "^0.3.5"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/lib/setData.js:
--------------------------------------------------------------------------------
1 | export default function setData(osInstance, osElement) {
2 | osInstance.setData({
3 | url: osElement.getAttribute('data-open-share-url'),
4 | text: osElement.getAttribute('data-open-share-text'),
5 | via: osElement.getAttribute('data-open-share-via'),
6 | hashtags: osElement.getAttribute('data-open-share-hashtags'),
7 | tweetId: osElement.getAttribute('data-open-share-tweet-id'),
8 | related: osElement.getAttribute('data-open-share-related'),
9 | screenName: osElement.getAttribute('data-open-share-screen-name'),
10 | userId: osElement.getAttribute('data-open-share-user-id'),
11 | link: osElement.getAttribute('data-open-share-link'),
12 | picture: osElement.getAttribute('data-open-share-picture'),
13 | caption: osElement.getAttribute('data-open-share-caption'),
14 | description: osElement.getAttribute('data-open-share-description'),
15 | user: osElement.getAttribute('data-open-share-user'),
16 | video: osElement.getAttribute('data-open-share-video'),
17 | username: osElement.getAttribute('data-open-share-username'),
18 | title: osElement.getAttribute('data-open-share-title'),
19 | media: osElement.getAttribute('data-open-share-media'),
20 | to: osElement.getAttribute('data-open-share-to'),
21 | subject: osElement.getAttribute('data-open-share-subject'),
22 | body: osElement.getAttribute('data-open-share-body'),
23 | ios: osElement.getAttribute('data-open-share-ios'),
24 | type: osElement.getAttribute('data-open-share-type'),
25 | center: osElement.getAttribute('data-open-share-center'),
26 | views: osElement.getAttribute('data-open-share-views'),
27 | zoom: osElement.getAttribute('data-open-share-zoom'),
28 | search: osElement.getAttribute('data-open-share-search'),
29 | saddr: osElement.getAttribute('data-open-share-saddr'),
30 | daddr: osElement.getAttribute('data-open-share-daddr'),
31 | directionsmode: osElement.getAttribute('data-open-share-directions-mode'),
32 | repo: osElement.getAttribute('data-open-share-repo'),
33 | shot: osElement.getAttribute('data-open-share-shot'),
34 | pen: osElement.getAttribute('data-open-share-pen'),
35 | view: osElement.getAttribute('data-open-share-view'),
36 | issue: osElement.getAttribute('data-open-share-issue'),
37 | buttonId: osElement.getAttribute('data-open-share-buttonId'),
38 | popUp: osElement.getAttribute('data-open-share-popup'),
39 | key: osElement.getAttribute('data-open-share-key'),
40 | });
41 | }
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OpenShare
2 |
3 | OpenShare provides straightforward, declarative, and completely customizable API wrappers for sharing and counting on major social networks and platforms. Zero styling, maximum flexibility. But, unlike other social sharing tools, we don't retarget and profit from your users. Your data is your data — this is OpenShare.
4 |
5 | [Check out the examples](http://openshare.social/examples) to see OpenShare in action or [dive right into the documentation](https://github.com/dsurgeons/OpenShare/wiki).
6 |
7 | * [Getting Started](https://github.com/dsurgeons/OpenShare/wiki/1.-Getting-Started)
8 | * [Data Attribute API](https://github.com/dsurgeons/OpenShare/wiki/2.-Data-Attribute-API)
9 | * [JavaScript API](https://github.com/dsurgeons/OpenShare/wiki/3.-JavaScript-API)
10 | * [Automated Analytics Integrations](https://github.com/dsurgeons/OpenShare/wiki/4.-Automated-Analytics-Integrations)
11 | * [Contributing](https://github.com/dsurgeons/OpenShare/wiki/5.-Contributing)
12 | * [Examples](http://openshare.social/examples)
13 |
14 |
15 | ---
16 | **Quick Start**
17 |
18 | ```
19 | $ npm install openshare --save
20 | ```
21 |
22 | ```html
23 |
27 | Share
28 |
29 |
31 |
32 | ```
33 |
34 | ---
35 | **MIT License (MIT)**
36 |
37 | Copyright (c) 2015 Digital Surgeons
38 |
39 | 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:
40 |
41 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
42 |
43 | 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.
44 |
--------------------------------------------------------------------------------
/analytics.js:
--------------------------------------------------------------------------------
1 | export default function (type, cb) {// eslint-disable-line
2 | const isGA = type === 'event' || type === 'social';
3 | const isTagManager = type === 'tagManager';
4 |
5 | if (isGA) checkIfAnalyticsLoaded(type, cb);
6 | if (isTagManager) setTagManager(cb);
7 | }
8 |
9 | function checkIfAnalyticsLoaded(type, cb) {
10 | if (window.ga) {
11 | if (cb) cb();
12 | // bind to shared event on each individual node
13 | listen((e) => {
14 | const platform = e.target.getAttribute('data-open-share');
15 | const target = e.target.getAttribute('data-open-share-link') ||
16 | e.target.getAttribute('data-open-share-url') ||
17 | e.target.getAttribute('data-open-share-username') ||
18 | e.target.getAttribute('data-open-share-center') ||
19 | e.target.getAttribute('data-open-share-search') ||
20 | e.target.getAttribute('data-open-share-body');
21 |
22 | if (type === 'event') {
23 | ga('send', 'event', { // eslint-disable-line no-undef
24 | eventCategory: 'OpenShare Click',
25 | eventAction: platform,
26 | eventLabel: target,
27 | transport: 'beacon',
28 | });
29 | }
30 |
31 | if (type === 'social') {
32 | ga('send', { // eslint-disable-line no-undef
33 | hitType: 'social',
34 | socialNetwork: platform,
35 | socialAction: 'share',
36 | socialTarget: target,
37 | });
38 | }
39 | });
40 | } else {
41 | setTimeout(() => {
42 | checkIfAnalyticsLoaded(type, cb);
43 | }, 1000);
44 | }
45 | }
46 |
47 | function setTagManager(cb) {
48 | if (window.dataLayer && window.dataLayer[0]['gtm.start']) {
49 | if (cb) cb();
50 |
51 | listen(onShareTagManger);
52 |
53 | getCounts((e) => {
54 | const count = e.target ?
55 | e.target.innerHTML :
56 | e.innerHTML;
57 |
58 | const platform = e.target ?
59 | e.target.getAttribute('data-open-share-count-url') :
60 | e.getAttribute('data-open-share-count-url');
61 |
62 | window.dataLayer.push({
63 | event: 'OpenShare Count',
64 | platform,
65 | resource: count,
66 | activity: 'count',
67 | });
68 | });
69 | } else {
70 | setTimeout(() => {
71 | setTagManager(cb);
72 | }, 1000);
73 | }
74 | }
75 |
76 | function listen(cb) {
77 | // bind to shared event on each individual node
78 | [].forEach.call(document.querySelectorAll('[data-open-share]'), (node) => {
79 | node.addEventListener('OpenShare.shared', cb);
80 | });
81 | }
82 |
83 | function getCounts(cb) {
84 | const countNode = document.querySelectorAll('[data-open-share-count]');
85 |
86 | [].forEach.call(countNode, (node) => {
87 | if (node.textContent) cb(node);
88 | else node.addEventListener(`OpenShare.counted-${node.getAttribute('data-open-share-count-url')}`, cb);
89 | });
90 | }
91 |
92 | function onShareTagManger(e) {
93 | const platform = e.target.getAttribute('data-open-share');
94 | const target = e.target.getAttribute('data-open-share-link') ||
95 | e.target.getAttribute('data-open-share-url') ||
96 | e.target.getAttribute('data-open-share-username') ||
97 | e.target.getAttribute('data-open-share-center') ||
98 | e.target.getAttribute('data-open-share-search') ||
99 | e.target.getAttribute('data-open-share-body');
100 |
101 | window.dataLayer.push({
102 | event: 'OpenShare Share',
103 | platform,
104 | resource: target,
105 | activity: 'share',
106 | });
107 | }
108 |
--------------------------------------------------------------------------------
/src/modules/open-share.js:
--------------------------------------------------------------------------------
1 | /**
2 | * OpenShare generates a single share link
3 | */
4 | export default class OpenShare {
5 |
6 | constructor(type, transform) {
7 | this.ios = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
8 | this.type = type;
9 | this.dynamic = false;
10 | this.transform = transform;
11 |
12 | // capitalized type
13 | this.typeCaps = type.charAt(0).toUpperCase() + type.slice(1);
14 | }
15 |
16 | // returns function named as type set in constructor
17 | // e.g twitter()
18 | setData(data) {
19 | // if iOS user and ios data attribute defined
20 | // build iOS URL scheme as single string
21 | if (this.ios) {
22 | this.transformData = this.transform(data, true);
23 | this.mobileShareUrl = this.template(this.transformData.url, this.transformData.data);
24 | }
25 |
26 | this.transformData = this.transform(data);
27 | this.shareUrl = this.template(this.transformData.url, this.transformData.data);
28 | }
29 |
30 | // open share URL defined in individual platform functions
31 | share() {
32 | // if iOS share URL has been set then use timeout hack
33 | // test for native app and fall back to web
34 | if (this.mobileShareUrl) {
35 | const start = (new Date()).valueOf();
36 |
37 | setTimeout(() => {
38 | const end = (new Date()).valueOf();
39 |
40 | // if the user is still here, fall back to web
41 | if (end - start > 1600) {
42 | return;
43 | }
44 |
45 | window.location = this.shareUrl;
46 | }, 1500);
47 |
48 | window.location = this.mobileShareUrl;
49 |
50 | // open mailto links in same window
51 | } else if (this.type === 'email') {
52 | window.location = this.shareUrl;
53 |
54 | // open social share URLs in new window
55 | } else {
56 | // if popup object present then set window dimensions / position
57 | if (this.popup && this.transformData.popup) {
58 | return this.openWindow(this.shareUrl, this.transformData.popup);
59 | }
60 |
61 | window.open(this.shareUrl);
62 | }
63 | }
64 |
65 | // create share URL with GET params
66 | // appending valid properties to query string
67 | template(url, data) {//eslint-disable-line
68 | const nonURLProps = [
69 | 'appendTo',
70 | 'innerHTML',
71 | 'classes',
72 | ];
73 |
74 | let shareUrl = url,
75 | i;
76 |
77 | for (i in data) {
78 | // only append valid properties
79 | if (!data[i] || nonURLProps.indexOf(i) > -1) {
80 | continue; //eslint-disable-line
81 | }
82 |
83 | // append URL encoded GET param to share URL
84 | data[i] = encodeURIComponent(data[i]);
85 | shareUrl += `${i}=${data[i]}&`;
86 | }
87 |
88 | return shareUrl.substr(0, shareUrl.length - 1);
89 | }
90 |
91 | // center popup window supporting dual screens
92 | openWindow(url, options) {//eslint-disable-line
93 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left,
94 | dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top,
95 | width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width,//eslint-disable-line
96 | height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height,//eslint-disable-line
97 | left = ((width / 2) - (options.width / 2)) + dualScreenLeft,
98 | top = ((height / 2) - (options.height / 2)) + dualScreenTop,
99 | newWindow = window.open(url, 'OpenShare', `width=${options.width}, height=${options.height}, top=${top}, left=${left}`);
100 |
101 | // Puts focus on the newWindow
102 | if (window.focus) {
103 | newWindow.focus();
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/modules/share-api.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Global OpenShare API to generate instances programmatically
3 | */
4 | import OS from './open-share';
5 | import ShareTransforms from './share-transforms';
6 | import Events from './events';
7 | import dashToCamel from '../../lib/dashToCamel';
8 |
9 | export default () => {
10 | // global OpenShare referencing internal class for instance generation
11 | class OpenShare {
12 |
13 | constructor(data, element) {
14 | if (!data.bindClick) data.bindClick = true;
15 |
16 | const dash = data.type.indexOf('-');
17 |
18 | if (dash > -1) {
19 | data.type = dashToCamel(dash, data.type);
20 | }
21 |
22 | let node;
23 | this.element = element;
24 | this.data = data;
25 |
26 | this.os = new OS(data.type, ShareTransforms[data.type]);
27 | this.os.setData(data);
28 |
29 | if (!element || data.element) {
30 | element = data.element;
31 | node = document.createElement(element || 'a');
32 | if (data.type) {
33 | node.classList.add('open-share-link', data.type);
34 | node.setAttribute('data-open-share', data.type);
35 | node.setAttribute('data-open-share-node', data.type);
36 | }
37 | if (data.innerHTML) node.innerHTML = data.innerHTML;
38 | }
39 | if (node) element = node;
40 |
41 | if (data.bindClick) {
42 | element.addEventListener('click', () => {
43 | this.share();
44 | });
45 | }
46 |
47 | if (data.appendTo) {
48 | data.appendTo.appendChild(element);
49 | }
50 |
51 | if (data.classes && Array.isArray(data.classes)) {
52 | data.classes.forEach((cssClass) => {
53 | element.classList.add(cssClass);
54 | });
55 | }
56 |
57 | if (data.type.toLowerCase() === 'paypal') {
58 | const action = data.sandbox ?
59 | 'https://www.sandbox.paypal.com/cgi-bin/webscr' :
60 | 'https://www.paypal.com/cgi-bin/webscr';
61 |
62 | const buyGIF = data.sandbox ?
63 | 'https://www.sandbox.paypal.com/en_US/i/btn/btn_buynow_LG.gif' :
64 | 'https://www.paypalobjects.com/en_US/i/btn/btn_buynow_LG.gif';
65 |
66 | const pixelGIF = data.sandbox ?
67 | 'https://www.sandbox.paypal.com/en_US/i/scr/pixel.gif' :
68 | 'https://www.paypalobjects.com/en_US/i/scr/pixel.gif';
69 |
70 |
71 | const paypalButton = `
`;
87 |
88 | const hiddenDiv = document.createElement('div');
89 | hiddenDiv.style.display = 'none';
90 | hiddenDiv.innerHTML = paypalButton;
91 | document.body.appendChild(hiddenDiv);
92 |
93 | this.paypal = hiddenDiv.querySelector('form');
94 | }
95 |
96 | this.element = element;
97 | return element;
98 | }
99 |
100 | // public share method to trigger share programmatically
101 | share(e) {
102 | // if dynamic instance then fetch attributes again in case of updates
103 | if (this.data.dynamic) {
104 | //eslint-disable-next-line
105 | this.os.setData(data);// data is not defined
106 | }
107 |
108 | if (this.data.type.toLowerCase() === 'paypal') {
109 | this.paypal.submit();
110 | } else this.os.share(e);
111 |
112 | Events.trigger(this.element, 'shared');
113 | }
114 | }
115 |
116 | return OpenShare;
117 | };
118 |
--------------------------------------------------------------------------------
/async-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Open Share
9 |
10 |
11 |
12 |
13 |
14 |
15 |
168 |
169 |
170 |
171 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
--------------------------------------------------------------------------------
/src/modules/count-transforms.js:
--------------------------------------------------------------------------------
1 | import countReduce from '../../lib/countReduce';
2 | import storeCount from '../../lib/storeCount';
3 | /**
4 | * Object of transform functions for each openshare api
5 | * Transform functions passed into OpenShare instance when instantiated
6 | * Return object containing URL and key/value args
7 | */
8 | export default {
9 |
10 | // facebook count data
11 | facebook(url) {
12 | return {
13 | type: 'get',
14 | url: `https://graph.facebook.com/?id=${url}`,
15 | transform(xhr) {
16 | const fb = JSON.parse(xhr.responseText);
17 |
18 | const count = (fb.share && fb.share.share_count) || 0;
19 |
20 | return storeCount(this, count);
21 | },
22 | };
23 | },
24 |
25 | // pinterest count data
26 | pinterest(url) {
27 | return {
28 | type: 'jsonp',
29 | url: `https://api.pinterest.com/v1/urls/count.json?callback=?&url=${url}`,
30 | transform(data) {
31 | const count = data.count || 0;
32 | return storeCount(this, count);
33 | },
34 | };
35 | },
36 |
37 | // linkedin count data
38 | linkedin(url) {
39 | return {
40 | type: 'jsonp',
41 | url: `https://www.linkedin.com/countserv/count/share?url=${url}&format=jsonp&callback=?`,
42 | transform(data) {
43 | const count = data.count || 0;
44 | return storeCount(this, count);
45 | },
46 | };
47 | },
48 |
49 | // reddit count data
50 | reddit(url) {
51 | return {
52 | type: 'get',
53 | url: `https://www.reddit.com/api/info.json?url=${url}`,
54 | transform(xhr) {
55 | const reddit = JSON.parse(xhr.responseText);
56 | const posts = (reddit.data && reddit.data.children) || null;
57 | let ups = 0;
58 |
59 | if (posts) {
60 | posts.forEach((post) => {
61 | ups += Number(post.data.ups);
62 | });
63 | }
64 |
65 | return storeCount(this, ups);
66 | },
67 | };
68 | },
69 |
70 | // google count data
71 | google(url) {
72 | return {
73 | type: 'post',
74 | data: {
75 | method: 'pos.plusones.get',
76 | id: 'p',
77 | params: {
78 | nolog: true,
79 | id: url,
80 | source: 'widget',
81 | userId: '@viewer',
82 | groupId: '@self',
83 | },
84 | jsonrpc: '2.0',
85 | key: 'p',
86 | apiVersion: 'v1',
87 | },
88 | url: 'https://clients6.google.com/rpc',
89 | transform(xhr) {
90 | const google = JSON.parse(xhr.responseText);
91 | const count = (google.result
92 | && google.result.metadata
93 | && google.result.metadata.globalCounts
94 | && google.result.metadata.globalCounts.count) || 0;
95 | return storeCount(this, count);
96 | },
97 | };
98 | },
99 |
100 | // github star count
101 | githubStars(repo) {
102 | repo = repo.indexOf('github.com/') > -1 ?
103 | repo.split('github.com/')[1] :
104 | repo;
105 | return {
106 | type: 'get',
107 | url: `https://api.github.com/repos/${repo}`,
108 | transform(xhr) {
109 | const count = JSON.parse(xhr.responseText).stargazers_count || 0;
110 | return storeCount(this, count);
111 | },
112 | };
113 | },
114 |
115 | // github forks count
116 | githubForks(repo) {
117 | repo = repo.indexOf('github.com/') > -1 ?
118 | repo.split('github.com/')[1] :
119 | repo;
120 | return {
121 | type: 'get',
122 | url: `https://api.github.com/repos/${repo}`,
123 | transform(xhr) {
124 | const count = JSON.parse(xhr.responseText).forks_count || 0;
125 | return storeCount(this, count);
126 | },
127 | };
128 | },
129 |
130 | // github watchers count
131 | githubWatchers(repo) {
132 | repo = repo.indexOf('github.com/') > -1 ?
133 | repo.split('github.com/')[1] :
134 | repo;
135 | return {
136 | type: 'get',
137 | url: `https://api.github.com/repos/${repo}`,
138 | transform(xhr) {
139 | const count = JSON.parse(xhr.responseText).watchers_count || 0;
140 | return storeCount(this, count);
141 | },
142 | };
143 | },
144 |
145 | // dribbble likes count
146 | dribbble(shot) {
147 | shot = shot.indexOf('dribbble.com/shots') > -1 ?
148 | shot.split('shots/')[1] :
149 | shot;
150 | const url = `https://api.dribbble.com/v1/shots/${shot}/likes`;
151 | return {
152 | type: 'get',
153 | url,
154 | transform(xhr, Events) {
155 | const count = JSON.parse(xhr.responseText).length;
156 |
157 | // at this time dribbble limits a response of 12 likes per page
158 | if (count === 12) {
159 | const page = 2;
160 | recursiveCount(url, page, count, (finalCount) => {
161 | if (this.appendTo && typeof this.appendTo !== 'function') {
162 | this.appendTo.appendChild(this.os);
163 | }
164 | countReduce(this.os, finalCount, this.cb);
165 | Events.trigger(this.os, `counted-${this.url}`);
166 | return storeCount(this, finalCount);
167 | });
168 | } else {
169 | return storeCount(this, count);
170 | }
171 | },
172 | };
173 | },
174 |
175 | twitter(url) {
176 | return {
177 | type: 'get',
178 | url: `https://api.openshare.social/job?url=${url}&key=`,
179 | transform(xhr) {
180 | const count = JSON.parse(xhr.responseText).count || 0;
181 | return storeCount(this, count);
182 | },
183 | };
184 | },
185 | };
186 |
187 | function recursiveCount(url, page, count, cb) {
188 | const xhr = new XMLHttpRequest();
189 | xhr.open('GET', `${url}?page=${page}`);
190 | xhr.addEventListener('load', function () { //eslint-disable-line
191 | const likes = JSON.parse(this.response);
192 | count += likes.length;
193 |
194 | // dribbble like per page is 12
195 | if (likes.length === 12) {
196 | page++;
197 | recursiveCount(url, page, count, cb);
198 | } else {
199 | cb(count);
200 | }
201 | });
202 | xhr.send();
203 | }
204 |
--------------------------------------------------------------------------------
/src/test.js:
--------------------------------------------------------------------------------
1 | const OpenShare = {
2 | share: require('../share.js'),
3 | count: require('../count.js'),
4 | analytics: require('../analytics.js'),
5 | };
6 |
7 | OpenShare.analytics('tagManager', () => {
8 | console.log('tag manager loaded');
9 | });
10 |
11 | OpenShare.analytics('event', () => {
12 | console.log('google analytics events loaded');
13 | });
14 |
15 | OpenShare.analytics('social', () => {
16 | console.log('google analytics social loaded');
17 | });
18 |
19 | const dynamicNodeData = {
20 | url: 'http://www.digitalsurgeons.com',
21 | via: 'digitalsurgeons',
22 | text: 'Forward Obsessed',
23 | hashtags: 'forwardobsessed',
24 | button: 'Open Share Watcher!',
25 | };
26 |
27 | function createOpenShareNode(data) {
28 | const openShare = document.createElement('a');
29 |
30 | openShare.classList.add('open-share-link', 'twitter');
31 | openShare.setAttribute('data-open-share', 'twitter');
32 | openShare.setAttribute('data-open-share-url', data.url);
33 | openShare.setAttribute('data-open-share-via', data.via);
34 | openShare.setAttribute('data-open-share-text', data.text);
35 | openShare.setAttribute('data-open-share-hashtags', data.hashtags);
36 | openShare.innerHTML = `${data.button}`;
37 |
38 | const node = new OpenShare.share({ //eslint-disable-line
39 | type: 'twitter',
40 | url: 'http://www.digitalsurgeons.com',
41 | via: 'digitalsurgeons',
42 | hashtags: 'forwardobsessed',
43 | appendTo: document.querySelector('.open-share-watch'),
44 | innerHTML: 'Created via OpenShareAPI',
45 | element: 'div',
46 | classes: ['wow', 'such', 'classes'],
47 | });
48 |
49 | return openShare;
50 | }
51 |
52 | function addNode() {
53 | const data = dynamicNodeData;
54 | document.querySelector('.open-share-watch')
55 | .appendChild(createOpenShareNode(data));
56 | }
57 |
58 | window.addNode = addNode;
59 |
60 | function addNodeWithCount() {
61 | const data = dynamicNodeData; // eslint-disable-line no-unused-vars
62 | new OpenShare.count({ // eslint-disable-line
63 | type: 'facebook',
64 | url: 'https://www.digitalsurgeons.com/',
65 | }, (node) => {
66 | const os = new OpenShare.share({ // eslint-disable-line
67 | type: 'twitter',
68 | url: 'http://www.digitalsurgeons.com',
69 | via: 'digitalsurgeons',
70 | hashtags: 'forwardobsessed',
71 | innerHTML: 'Created via OpenShareAPI',
72 | element: 'div',
73 | classes: ['wow', 'such', 'classes'],
74 | });
75 | document.querySelector('.create-node.w-count')
76 | .appendChild(os);
77 | os.appendChild(node);
78 | });
79 | }
80 |
81 | window.addNodeWithCount = addNodeWithCount;
82 |
83 | function createCountNode() {
84 | const container = document.querySelector('.create-node.count-nodes');
85 | const type = container.querySelector('input.count-type').value;
86 | const url = container.querySelector('input.count-url').value;
87 |
88 | new OpenShare.count({ //eslint-disable-line
89 | type: type, //eslint-disable-line
90 | url: url, //eslint-disable-line
91 | appendTo: container,
92 | classes: ['test'],
93 | }, (node) => {
94 | node.style.position = 'relative';
95 | });
96 |
97 |
98 | container.querySelector('input.count-type').value = '';
99 | container.querySelector('input.count-url').value = '';
100 | }
101 |
102 | window.createCountNode = createCountNode;
103 |
104 | // test JS OpenShare API with dashes
105 | new OpenShare.share({ //eslint-disable-line
106 | type: 'googleMaps',
107 | center: '40.765819,-73.975866',
108 | view: 'traffic',
109 | zoom: 14,
110 | appendTo: document.body,
111 | innerHTML: 'Maps',
112 | });
113 |
114 | new OpenShare.share({ //eslint-disable-line
115 | type: 'twitter-follow',
116 | screenName: 'digitalsurgeons',
117 | userId: '18189130',
118 | appendTo: document.body,
119 | innerHTML: 'Follow Test',
120 | });
121 |
122 | // test PayPal
123 | new OpenShare.share({ //eslint-disable-line
124 | type: 'paypal',
125 | buttonId: '2P3RJYEFL7Z62',
126 | sandbox: true,
127 | appendTo: document.body,
128 | innerHTML: 'PayPal Test',
129 | });
130 |
131 | // bind to count loaded event
132 | document.addEventListener('OpenShare.count-loaded', () => {
133 | console.log('OpenShare (count) loaded');
134 | });
135 |
136 | // bind to share loaded event
137 | document.addEventListener('OpenShare.share-loaded', () => {
138 | console.log('OpenShare (share) loaded');
139 |
140 | // bind to shared event on each individual node
141 | [].forEach.call(document.querySelectorAll('[data-open-share]'), (node) => {
142 | node.addEventListener('OpenShare.shared', (e) => {
143 | console.log('Open Share Shared', e);
144 | });
145 | });
146 |
147 | const examples = { // eslint-disable-line no-unused-vars
148 | twitter: new OpenShare.share({ //eslint-disable-line
149 | type: 'twitter',
150 | bindClick: true,
151 | url: 'http://digitalsurgeons.com',
152 | via: 'digitalsurgeons',
153 | text: 'Digital Surgeons',
154 | hashtags: 'forwardobsessed',
155 | }, document.querySelector('[data-api-example="twitter"]')),
156 |
157 | facebook: new OpenShare.share({ //eslint-disable-line
158 | type: 'facebook',
159 | bindClick: true,
160 | link: 'http://digitalsurgeons.com',
161 | picture: 'http://www.digitalsurgeons.com/img/about/bg_office_team.jpg',
162 | caption: 'Digital Surgeons',
163 | description: 'forwardobsessed',
164 | }, document.querySelector('[data-api-example="facebook"]')),
165 |
166 | pinterest: new OpenShare.share({ //eslint-disable-line
167 | type: 'pinterest',
168 | bindClick: true,
169 | url: 'http://digitalsurgeons.com',
170 | media: 'http://www.digitalsurgeons.com/img/about/bg_office_team.jpg',
171 | description: 'Digital Surgeons',
172 | appendTo: document.body,
173 | }, document.querySelector('[data-api-example="pinterest"]')),
174 |
175 | email: new OpenShare.share({ //eslint-disable-line
176 | type: 'email',
177 | bindClick: true,
178 | to: 'techroom@digitalsurgeons.com',
179 | subject: 'Digital Surgeons',
180 | body: 'Forward Obsessed',
181 | }, document.querySelector('[data-api-example="email"]')),
182 | };
183 | });
184 |
185 | // Example of listening for counted events on individual urls or arrays of urls
186 | const urls = [
187 | 'facebook',
188 | 'google',
189 | 'linkedin',
190 | 'reddit',
191 | 'pinterest',
192 | [
193 | 'google',
194 | 'linkedin',
195 | 'reddit',
196 | 'pinterest',
197 | ],
198 | ];
199 |
200 | urls.forEach((url) => {
201 | if (Array.isArray(url)) {
202 | url = url.join(',');
203 | }
204 | const countNode = document.querySelectorAll(`[data-open-share-count="${url}"]`);
205 |
206 | [].forEach.call(countNode, (node) => {
207 | node.addEventListener(`OpenShare.counted-${url}`, () => {
208 | const counts = node.innerHTML;
209 | if (counts) console.log(url, 'shares: ', counts);
210 | });
211 | });
212 | });
213 |
214 | // test twitter count js api
215 | new OpenShare.count({ //eslint-disable-line
216 | type: 'twitter',
217 | url: 'https://www.digitalsurgeons.com/thoughts/technology/the-blockchain-revolution',
218 | key: 'dstweets',
219 | }, (node) => {
220 | const os = new OpenShare.share({ //eslint-disable-line
221 | type: 'twitter',
222 | url: 'https://www.digitalsurgeons.com/thoughts/technology/the-blockchain-revolution',
223 | via: 'digitalsurgeons',
224 | hashtags: 'forwardobsessed, blockchain',
225 | appendTo: document.body,
226 | innerHTML: 'BLOCKCHAIN',
227 | });
228 | os.appendChild(node);
229 | });
230 |
--------------------------------------------------------------------------------
/src/modules/count.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generate share count instance from one to many networks
3 | */
4 |
5 | import CountTransforms from './count-transforms';
6 | import Events from './events';
7 | import countReduce from '../../lib/countReduce';
8 | import storeCount from '../../lib/storeCount'; // eslint-disable-line no-unused-vars
9 |
10 | function isNumeric(n) {
11 | return !isNaN(parseFloat(n)) && isFinite(n);
12 | }
13 |
14 | class Count {
15 | constructor(type, url) {
16 | // throw error if no url provided
17 | if (!url) {
18 | throw new Error('Open Share: no url provided for count');
19 | }
20 |
21 | // check for Github counts
22 | if (type.indexOf('github') === 0) {
23 | if (type === 'github-stars') {
24 | type = 'githubStars';
25 | } else if (type === 'github-forks') {
26 | type = 'githubForks';
27 | } else if (type === 'github-watchers') {
28 | type = 'githubWatchers';
29 | } else {
30 | console.error('Invalid Github count type. Try github-stars, github-forks, or github-watchers.');
31 | }
32 | }
33 |
34 | // if type is comma separate list create array
35 | if (type.indexOf(',') > -1) {
36 | this.type = type;
37 | this.typeArr = this.type.split(',');
38 | this.countData = [];
39 |
40 | // check each type supplied is valid
41 | this.typeArr.forEach((t) => {
42 | if (!CountTransforms[t]) {
43 | throw new Error(`Open Share: ${type} is an invalid count type`);
44 | }
45 |
46 | this.countData.push(CountTransforms[t](url));
47 | });
48 |
49 | const count = this.storeGet(`${this.type}-${this.shared}`);
50 |
51 | if (count) {
52 | if (this.appendTo && typeof this.appendTo !== 'function') {
53 | this.appendTo.appendChild(this.os);
54 | }
55 | countReduce(this.os, count);
56 | }
57 |
58 | // throw error if invalid type provided
59 | } else if (!CountTransforms[type]) {
60 | throw new Error(`Open Share: ${type} is an invalid count type`);
61 |
62 | // single count
63 | // store count URL and transform function
64 | } else {
65 | this.type = type;
66 | this.countData = CountTransforms[type](url);
67 | }
68 | }
69 |
70 | // handle calling getCount / getCounts
71 | // depending on number of types
72 | count(os, cb, appendTo) {
73 | this.os = os;
74 | this.appendTo = appendTo;
75 | this.cb = cb;
76 | this.url = this.os.getAttribute('data-open-share-count');
77 | this.shared = this.os.getAttribute('data-open-share-count-url');
78 | this.key = this.os.getAttribute('data-open-share-key');
79 |
80 | if (!Array.isArray(this.countData)) {
81 | this.getCount();
82 | } else {
83 | this.getCounts();
84 | }
85 | }
86 |
87 | // fetch count either AJAX or JSONP
88 | getCount() {
89 | const count = this.storeGet(`${this.type}-${this.shared}`);
90 |
91 | if (count) {
92 | if (this.appendTo && typeof this.appendTo !== 'function') {
93 | this.appendTo.appendChild(this.os);
94 | }
95 | countReduce(this.os, count);
96 | }
97 | this[this.countData.type](this.countData);
98 | }
99 |
100 | // fetch multiple counts and aggregate
101 | getCounts() {
102 | this.total = [];
103 |
104 | const count = this.storeGet(`${this.type}-${this.shared}`);
105 |
106 | if (count) {
107 | if (this.appendTo && typeof this.appendTo !== 'function') {
108 | this.appendTo.appendChild(this.os);
109 | }
110 | countReduce(this.os, count);
111 | }
112 |
113 | this.countData.forEach((countData) => {
114 | this[countData.type](countData, (num) => {
115 | this.total.push(num);
116 |
117 | // total counts length now equals type array length
118 | // so aggregate, store and insert into DOM
119 | if (this.total.length === this.typeArr.length) {
120 | let tot = 0;
121 |
122 | this.total.forEach((t) => {
123 | tot += t;
124 | });
125 |
126 | if (this.appendTo && typeof this.appendTo !== 'function') {
127 | this.appendTo.appendChild(this.os);
128 | }
129 |
130 | const local = Number(this.storeGet(`${this.type}-${this.shared}`));
131 | if (local > tot) {
132 | // const latestCount = Number(this.storeGet(`${this.type}-${this.shared}-latestCount`));
133 | // this.storeSet(`${this.type}-${this.shared}-latestCount`, tot);
134 | //
135 | // tot = isNumeric(latestCount) && latestCount > 0 ?
136 | // tot += local - latestCount :
137 | // tot += local;
138 | tot = local;
139 | }
140 | this.storeSet(`${this.type}-${this.shared}`, tot);
141 |
142 | countReduce(this.os, tot);
143 | }
144 | });
145 | });
146 |
147 | if (this.appendTo && typeof this.appendTo !== 'function') {
148 | this.appendTo.appendChild(this.os);
149 | }
150 | }
151 |
152 | // handle JSONP requests
153 | jsonp(countData, cb) {
154 | // define random callback and assign transform function
155 | const callback = Math.random().toString(36).substring(7).replace(/[^a-zA-Z]/g, '');
156 | window[callback] = (data) => {
157 | const count = countData.transform.apply(this, [data]) || 0;
158 |
159 | if (cb && typeof cb === 'function') {
160 | cb(count);
161 | } else {
162 | if (this.appendTo && typeof this.appendTo !== 'function') {
163 | this.appendTo.appendChild(this.os);
164 | }
165 | countReduce(this.os, count, this.cb);
166 | }
167 |
168 | Events.trigger(this.os, `counted-${this.url}`);
169 | };
170 |
171 | // append JSONP script tag to page
172 | const script = document.createElement('script');
173 | script.src = countData.url.replace('callback=?', `callback=${callback}`);
174 | document.getElementsByTagName('head')[0].appendChild(script);
175 |
176 | return;
177 | }
178 |
179 | // handle AJAX GET request
180 | get(countData, cb) {
181 | const xhr = new XMLHttpRequest();
182 |
183 | // on success pass response to transform function
184 | xhr.onreadystatechange = () => {
185 | if (xhr.readyState === 4) {
186 | if (xhr.status === 200) {
187 | const count = countData.transform.apply(this, [xhr, Events]) || 0;
188 |
189 | if (cb && typeof cb === 'function') {
190 | cb(count);
191 | } else {
192 | if (this.appendTo && typeof this.appendTo !== 'function') {
193 | this.appendTo.appendChild(this.os);
194 | }
195 | countReduce(this.os, count, this.cb);
196 | }
197 |
198 | Events.trigger(this.os, `counted-${this.url}`);
199 | return;
200 | } else if (countData.url.toLowerCase().indexOf('https://api.openshare.social/job?') === 0) {
201 | console.warn('Please sign up for Twitter counts at https://openshare.social/twitter/auth');
202 | const count = 0;
203 |
204 | if (cb && typeof cb === 'function') {
205 | cb(count);
206 | } else {
207 | if (this.appendTo && typeof this.appendTo !== 'function') {
208 | this.appendTo.appendChild(this.os);
209 | }
210 | countReduce(this.os, count, this.cb);
211 | }
212 |
213 | Events.trigger(this.os, `counted-${this.url}`);
214 | } else {
215 | console.warn('Failed to get API data from', countData.url, '. Please use the latest version of OpenShare.');
216 | const count = 0;
217 |
218 | if (cb && typeof cb === 'function') {
219 | cb(count);
220 | } else {
221 | if (this.appendTo && typeof this.appendTo !== 'function') {
222 | this.appendTo.appendChild(this.os);
223 | }
224 | countReduce(this.os, count, this.cb);
225 | }
226 |
227 | Events.trigger(this.os, `counted-${this.url}`);
228 | }
229 | }
230 | };
231 |
232 | countData.url = countData.url.startsWith('https://api.openshare.social/job?') && this.key ?
233 | countData.url + this.key :
234 | countData.url;
235 |
236 | xhr.open('GET', countData.url);
237 | xhr.send();
238 | }
239 |
240 | // handle AJAX POST request
241 | post(countData, cb) {
242 | const xhr = new XMLHttpRequest();
243 |
244 | // on success pass response to transform function
245 | xhr.onreadystatechange = () => {
246 | if (xhr.readyState !== XMLHttpRequest.DONE ||
247 | xhr.status !== 200) {
248 | return;
249 | }
250 |
251 | const count = countData.transform.apply(this, [xhr]) || 0;
252 |
253 | if (cb && typeof cb === 'function') {
254 | cb(count);
255 | } else {
256 | if (this.appendTo && typeof this.appendTo !== 'function') {
257 | this.appendTo.appendChild(this.os);
258 | }
259 | countReduce(this.os, count, this.cb);
260 | }
261 | Events.trigger(this.os, `counted-${this.url}`);
262 | };
263 |
264 | xhr.open('POST', countData.url);
265 | xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
266 | xhr.send(JSON.stringify(countData.data));
267 | }
268 |
269 | storeSet(type, count = 0) {//eslint-disable-line
270 | if (!window.localStorage || !type) {
271 | return;
272 | }
273 |
274 | localStorage.setItem(`OpenShare-${type}`, count);
275 | }
276 |
277 | storeGet(type) {//eslint-disable-line
278 | if (!window.localStorage || !type) {
279 | return;
280 | }
281 |
282 | return localStorage.getItem(`OpenShare-${type}`);
283 | }
284 |
285 | }
286 |
287 | export default Count;
288 |
--------------------------------------------------------------------------------
/src/modules/share-transforms.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Object of transform functions for each openshare api
3 | * Transform functions passed into OpenShare instance when instantiated
4 | * Return object containing URL and key/value args
5 | */
6 | export default {
7 |
8 | // set Twitter share URL
9 | twitter(data, ios = false) {
10 | // if iOS user and ios data attribute defined
11 | // build iOS URL scheme as single string
12 | if (ios && data.ios) {
13 | let message = '';
14 |
15 | if (data.text) {
16 | message += data.text;
17 | }
18 |
19 | if (data.url) {
20 | message += ` - ${data.url}`;
21 | }
22 |
23 | if (data.hashtags) {
24 | const tags = data.hashtags.split(',');
25 | tags.forEach((tag) => {
26 | message += ` #${tag}`;
27 | });
28 | }
29 |
30 | if (data.via) {
31 | message += ` via ${data.via}`;
32 | }
33 |
34 | return {
35 | url: 'twitter://post?',
36 | data: {
37 | message,
38 | },
39 | };
40 | }
41 |
42 | return {
43 | url: 'https://twitter.com/share?',
44 | data,
45 | popup: {
46 | width: 700,
47 | height: 296,
48 | },
49 | };
50 | },
51 |
52 | // set Twitter retweet URL
53 | twitterRetweet(data, ios = false) {
54 | // if iOS user and ios data attribute defined
55 | if (ios && data.ios) {
56 | return {
57 | url: 'twitter://status?',
58 | data: {
59 | id: data.tweetId,
60 | },
61 | };
62 | }
63 |
64 | return {
65 | url: 'https://twitter.com/intent/retweet?',
66 | data: {
67 | tweet_id: data.tweetId,
68 | related: data.related,
69 | },
70 | popup: {
71 | width: 700,
72 | height: 296,
73 | },
74 | };
75 | },
76 |
77 | // set Twitter like URL
78 | twitterLike(data, ios = false) {
79 | // if iOS user and ios data attribute defined
80 | if (ios && data.ios) {
81 | return {
82 | url: 'twitter://status?',
83 | data: {
84 | id: data.tweetId,
85 | },
86 | };
87 | }
88 |
89 | return {
90 | url: 'https://twitter.com/intent/favorite?',
91 | data: {
92 | tweet_id: data.tweetId,
93 | related: data.related,
94 | },
95 | popup: {
96 | width: 700,
97 | height: 296,
98 | },
99 | };
100 | },
101 |
102 | // set Twitter follow URL
103 | twitterFollow(data, ios = false) {
104 | // if iOS user and ios data attribute defined
105 | if (ios && data.ios) {
106 | const iosData = data.screenName ? {
107 | screen_name: data.screenName,
108 | } : {
109 | id: data.userId,
110 | };
111 |
112 | return {
113 | url: 'twitter://user?',
114 | data: iosData,
115 | };
116 | }
117 |
118 | return {
119 | url: 'https://twitter.com/intent/user?',
120 | data: {
121 | screen_name: data.screenName,
122 | user_id: data.userId,
123 | },
124 | popup: {
125 | width: 700,
126 | height: 296,
127 | },
128 | };
129 | },
130 |
131 | // set Facebook share URL
132 | facebook(data) {
133 | return {
134 | url: 'https://www.facebook.com/dialog/feed?app_id=961342543922322&redirect_uri=http://facebook.com&',
135 | data,
136 | popup: {
137 | width: 560,
138 | height: 593,
139 | },
140 | };
141 | },
142 |
143 | // set Facebook send URL
144 | facebookSend(data) {
145 | return {
146 | url: 'https://www.facebook.com/dialog/send?app_id=961342543922322&redirect_uri=http://facebook.com&',
147 | data,
148 | popup: {
149 | width: 980,
150 | height: 596,
151 | },
152 | };
153 | },
154 |
155 | // set YouTube play URL
156 | youtube(data, ios = false) {
157 | // if iOS user
158 | if (ios && data.ios) {
159 | return {
160 | url: `youtube:${data.video}?`,
161 | };
162 | }
163 |
164 | return {
165 | url: `https://www.youtube.com/watch?v=${data.video}?`,
166 | popup: {
167 | width: 1086,
168 | height: 608,
169 | },
170 | };
171 | },
172 |
173 | // set YouTube subcribe URL
174 | youtubeSubscribe(data, ios = false) {
175 | // if iOS user
176 | if (ios && data.ios) {
177 | return {
178 | url: `youtube://www.youtube.com/user/${data.user}?`,
179 | };
180 | }
181 |
182 | return {
183 | url: `https://www.youtube.com/user/${data.user}?`,
184 | popup: {
185 | width: 880,
186 | height: 350,
187 | },
188 | };
189 | },
190 |
191 | // set Instagram follow URL
192 | instagram() {
193 | return {
194 | url: 'instagram://camera?',
195 | };
196 | },
197 |
198 | // set Instagram follow URL
199 | instagramFollow(data, ios = false) {
200 | // if iOS user
201 | if (ios && data.ios) {
202 | return {
203 | url: 'instagram://user?',
204 | data,
205 | };
206 | }
207 |
208 | return {
209 | url: `http://www.instagram.com/${data.username}?`,
210 | popup: {
211 | width: 980,
212 | height: 655,
213 | },
214 | };
215 | },
216 |
217 | // set Snapchat follow URL
218 | snapchat(data) {
219 | return {
220 | url: `snapchat://add/${data.username}?`,
221 | };
222 | },
223 |
224 | // set Google share URL
225 | google(data) {
226 | return {
227 | url: 'https://plus.google.com/share?',
228 | data,
229 | popup: {
230 | width: 495,
231 | height: 815,
232 | },
233 | };
234 | },
235 |
236 | // set Google maps URL
237 | googleMaps(data, ios = false) {
238 | if (data.search) {
239 | data.q = data.search;
240 | delete data.search;
241 | }
242 |
243 | // if iOS user and ios data attribute defined
244 | if (ios && data.ios) {
245 | return {
246 | url: 'comgooglemaps://?',
247 | data: ios,
248 | };
249 | }
250 |
251 | if (!ios && data.ios) {
252 | delete data.ios;
253 | }
254 |
255 | return {
256 | url: 'https://maps.google.com/?',
257 | data,
258 | popup: {
259 | width: 800,
260 | height: 600,
261 | },
262 | };
263 | },
264 |
265 | // set Pinterest share URL
266 | pinterest(data) {
267 | return {
268 | url: 'https://pinterest.com/pin/create/bookmarklet/?',
269 | data,
270 | popup: {
271 | width: 745,
272 | height: 620,
273 | },
274 | };
275 | },
276 |
277 | // set LinkedIn share URL
278 | linkedin(data) {
279 | return {
280 | url: 'http://www.linkedin.com/shareArticle?',
281 | data,
282 | popup: {
283 | width: 780,
284 | height: 492,
285 | },
286 | };
287 | },
288 |
289 | // set Buffer share URL
290 | buffer(data) {
291 | return {
292 | url: 'http://bufferapp.com/add?',
293 | data,
294 | popup: {
295 | width: 745,
296 | height: 345,
297 | },
298 | };
299 | },
300 |
301 | // set Tumblr share URL
302 | tumblr(data) {
303 | return {
304 | url: 'https://www.tumblr.com/widgets/share/tool?',
305 | data,
306 | popup: {
307 | width: 540,
308 | height: 940,
309 | },
310 | };
311 | },
312 |
313 | // set Reddit share URL
314 | reddit(data) {
315 | return {
316 | url: 'http://reddit.com/submit?',
317 | data,
318 | popup: {
319 | width: 860,
320 | height: 880,
321 | },
322 | };
323 | },
324 |
325 | // set Flickr follow URL
326 | flickr(data, ios = false) {
327 | // if iOS user
328 | if (ios && data.ios) {
329 | return {
330 | url: `flickr://photos/${data.username}?`,
331 | };
332 | }
333 | return {
334 | url: `http://www.flickr.com/photos/${data.username}?`,
335 | popup: {
336 | width: 600,
337 | height: 650,
338 | },
339 | };
340 | },
341 |
342 | // set WhatsApp share URL
343 | whatsapp(data) {
344 | return {
345 | url: 'whatsapp://send?',
346 | data,
347 | };
348 | },
349 |
350 | // set sms share URL
351 | sms(data, ios = false) {
352 | return {
353 | url: ios ? 'sms:&' : 'sms:?',
354 | data,
355 | };
356 | },
357 |
358 | // set Email share URL
359 | email(data) {
360 | let url = 'mailto:';
361 |
362 | // if to address specified then add to URL
363 | if (data.to !== null) {
364 | url += `${data.to}`;
365 | }
366 |
367 | url += '?';
368 |
369 | return {
370 | url,
371 | data: {
372 | subject: data.subject,
373 | body: data.body,
374 | },
375 | };
376 | },
377 |
378 | // set Github fork URL
379 | github(data, ios = false) { // eslint-disable-line no-unused-vars
380 | let url = data.repo ? `https://github.com/${data.repo}` : data.url;
381 |
382 | if (data.issue) {
383 | url += `/issues/new?title=${data.issue}&body=${data.body}`;
384 | }
385 |
386 | return {
387 | url: `${url}?`,
388 | popup: {
389 | width: 1020,
390 | height: 323,
391 | },
392 | };
393 | },
394 |
395 | // set Dribbble share URL
396 | dribbble(data, ios = false) { // eslint-disable-line no-unused-vars
397 | const url = data.shot ? `https://dribbble.com/shots/${data.shot}?` : `${data.url}?`;
398 | return {
399 | url,
400 | popup: {
401 | width: 440,
402 | height: 640,
403 | },
404 | };
405 | },
406 |
407 | codepen(data) {
408 | const url = (data.pen && data.username && data.view) ? `https://codepen.io/${data.username}/${data.view}/${data.pen}?` : `${data.url}?`;
409 | return {
410 | url,
411 | popup: {
412 | width: 1200,
413 | height: 800,
414 | },
415 | };
416 | },
417 |
418 | paypal(data) {
419 | return {
420 | data,
421 | };
422 | },
423 | };
424 |
--------------------------------------------------------------------------------
/dist/openshare.min.js:
--------------------------------------------------------------------------------
1 | !function t(e,n,r){function a(i,s){if(!n[i]){if(!e[i]){var u="function"==typeof require&&require;if(!s&&u)return u(i,!0);if(o)return o(i,!0);var p=new Error("Cannot find module '"+i+"'");throw p.code="MODULE_NOT_FOUND",p}var h=n[i]={exports:{}};e[i][0].call(h.exports,function(t){var n=e[i][1][t];return a(n?n:t)},h,h.exports,t,e,n,r)}return n[i].exports}for(var o="function"==typeof require&&require,i=0;i-1){var e=t.split(",");e.forEach(function(t){return E(t)})}else E(t)}}function d(t,e){[].forEach.call(t,function(t){var n=new MutationObserver(function(t){e(t[0].target)});n.observe(t,{childList:!0})})}function c(t){return function(){var e=p({api:t.api||null,container:t.container||document,selector:t.selector,cb:t.cb});e(),void 0!==window.MutationObserver&&d(document.querySelectorAll("[data-open-share-watch]"),e)}}function l(t,e){t.setData({url:e.getAttribute("data-open-share-url"),text:e.getAttribute("data-open-share-text"),via:e.getAttribute("data-open-share-via"),hashtags:e.getAttribute("data-open-share-hashtags"),tweetId:e.getAttribute("data-open-share-tweet-id"),related:e.getAttribute("data-open-share-related"),screenName:e.getAttribute("data-open-share-screen-name"),userId:e.getAttribute("data-open-share-user-id"),link:e.getAttribute("data-open-share-link"),picture:e.getAttribute("data-open-share-picture"),caption:e.getAttribute("data-open-share-caption"),description:e.getAttribute("data-open-share-description"),user:e.getAttribute("data-open-share-user"),video:e.getAttribute("data-open-share-video"),username:e.getAttribute("data-open-share-username"),title:e.getAttribute("data-open-share-title"),media:e.getAttribute("data-open-share-media"),to:e.getAttribute("data-open-share-to"),subject:e.getAttribute("data-open-share-subject"),body:e.getAttribute("data-open-share-body"),ios:e.getAttribute("data-open-share-ios"),type:e.getAttribute("data-open-share-type"),center:e.getAttribute("data-open-share-center"),views:e.getAttribute("data-open-share-views"),zoom:e.getAttribute("data-open-share-zoom"),search:e.getAttribute("data-open-share-search"),saddr:e.getAttribute("data-open-share-saddr"),daddr:e.getAttribute("data-open-share-daddr"),directionsmode:e.getAttribute("data-open-share-directions-mode"),repo:e.getAttribute("data-open-share-repo"),shot:e.getAttribute("data-open-share-shot"),pen:e.getAttribute("data-open-share-pen"),view:e.getAttribute("data-open-share-view"),issue:e.getAttribute("data-open-share-issue"),buttonId:e.getAttribute("data-open-share-buttonId"),popUp:e.getAttribute("data-open-share-popup"),key:e.getAttribute("data-open-share-key")})}function f(t,e,n){n.dynamic&&l(n,e),n.share(t),O.trigger(e,"shared")}function g(t){var e=t.getAttribute("data-open-share"),n=e.indexOf("-");n>-1&&(e=C(n,e));var r=x[e];if(!r)throw new Error("Open Share: "+e+" is an invalid type");var a=new L(e,r);t.getAttribute("data-open-share-dynamic")&&(a.dynamic=!0),t.getAttribute("data-open-share-popup")&&(a.popup=!0),l(a,t),t.addEventListener("click",function(e){f(e,t,a)}),t.addEventListener("OpenShare.trigger",function(e){f(e,t,a)}),t.setAttribute("data-open-share-node",e)}function b(t,e){if("number"!=typeof t)throw new TypeError("Expected value to be a number");var n=e>0?"e":"e-",r=e>0?"e-":"e";return e=Math.abs(e),Number(Math.round(t+n+e)+r+e)}function m(t){return b(t/1e3,1)+"K"}function w(t){return b(t/1e6,1)+"M"}function v(t,e,n){e>999999?(t.innerHTML=w(e),n&&"function"==typeof n&&n(t)):e>999?(t.innerHTML=m(e),n&&"function"==typeof n&&n(t)):(t.innerHTML=e,n&&"function"==typeof n&&n(t))}function y(t){return!isNaN(parseFloat(t))&&isFinite(t)}function A(t,e,n,r){var a=new XMLHttpRequest;a.open("GET",t+"?page="+e),a.addEventListener("load",function(){var a=JSON.parse(this.response);n+=a.length,12===a.length?(e++,A(t,e,n,r)):r(n)}),a.send()}function T(t){var e=t.getAttribute("data-open-share-count"),n=t.getAttribute("data-open-share-count-repo")||t.getAttribute("data-open-share-count-shot")||t.getAttribute("data-open-share-count-url"),r=new D(e,n);r.count(t),t.setAttribute("data-open-share-node",e)}function k(){c({selector:{share:"[data-open-share]:not([data-open-share-node])",count:"[data-open-share-count]:not([data-open-share-node])"},cb:{share:g,count:T}})()}var S=function(){function t(t,e){for(var n=0;n1600||(window.location=t.shareUrl)},1500),window.location=t.mobileShareUrl}();else if("email"===this.type)window.location=this.shareUrl;else{if(this.popup&&this.transformData.popup)return this.openWindow(this.shareUrl,this.transformData.popup);window.open(this.shareUrl)}}},{key:"template",value:function(t,e){var n=["appendTo","innerHTML","classes"],r=t,a=void 0;for(a in e)!e[a]||n.indexOf(a)>-1||(e[a]=encodeURIComponent(e[a]),r+=a+"="+e[a]+"&");return r.substr(0,r.length-1)}},{key:"openWindow",value:function(t,e){var n=void 0!==window.screenLeft?window.screenLeft:screen.left,r=void 0!==window.screenTop?window.screenTop:screen.top,a=window.innerWidth?window.innerWidth:document.documentElement.clientWidth?document.documentElement.clientWidth:screen.width,o=window.innerHeight?window.innerHeight:document.documentElement.clientHeight?document.documentElement.clientHeight:screen.height,i=a/2-e.width/2+n,s=o/2-e.height/2+r,u=window.open(t,"OpenShare","width="+e.width+", height="+e.height+", top="+s+", left="+i);window.focus&&u.focus()}}]),t}(),C=function(t,e){var n=e.substr(t+1,1),r=e.substr(t,2);return e=e.replace(r,n.toUpperCase())},_=function(t,e){var n=t.type.indexOf(",")>-1,r=Number(t.storeGet(t.type+"-"+t.shared));if(r>e&&!n){var a=Number(t.storeGet(t.type+"-"+t.shared+"-latestCount"));t.storeSet(t.type+"-"+t.shared+"-latestCount",e),e=e+=y(a)&&a>0?r-a:r}return n||t.storeSet(t.type+"-"+t.shared,e),e},N={facebook:function(t){return{type:"get",url:"https://graph.facebook.com/?id="+t,transform:function(t){var e=JSON.parse(t.responseText),n=e.share&&e.share.share_count||0;return _(this,n)}}},pinterest:function(t){return{type:"jsonp",url:"https://api.pinterest.com/v1/urls/count.json?callback=?&url="+t,transform:function(t){var e=t.count||0;return _(this,e)}}},linkedin:function(t){return{type:"jsonp",url:"https://www.linkedin.com/countserv/count/share?url="+t+"&format=jsonp&callback=?",transform:function(t){var e=t.count||0;return _(this,e)}}},reddit:function(t){return{type:"get",url:"https://www.reddit.com/api/info.json?url="+t,transform:function(t){var e=JSON.parse(t.responseText),n=e.data&&e.data.children||null,r=0;return n&&n.forEach(function(t){r+=Number(t.data.ups)}),_(this,r)}}},google:function(t){return{type:"post",data:{method:"pos.plusones.get",id:"p",params:{nolog:!0,id:t,source:"widget",userId:"@viewer",groupId:"@self"},jsonrpc:"2.0",key:"p",apiVersion:"v1"},url:"https://clients6.google.com/rpc",transform:function(t){var e=JSON.parse(t.responseText),n=e.result&&e.result.metadata&&e.result.metadata.globalCounts&&e.result.metadata.globalCounts.count||0;return _(this,n)}}},githubStars:function(t){return t=t.indexOf("github.com/")>-1?t.split("github.com/")[1]:t,{type:"get",url:"https://api.github.com/repos/"+t,transform:function(t){var e=JSON.parse(t.responseText).stargazers_count||0;return _(this,e)}}},githubForks:function(t){return t=t.indexOf("github.com/")>-1?t.split("github.com/")[1]:t,{type:"get",url:"https://api.github.com/repos/"+t,transform:function(t){var e=JSON.parse(t.responseText).forks_count||0;return _(this,e)}}},githubWatchers:function(t){return t=t.indexOf("github.com/")>-1?t.split("github.com/")[1]:t,{type:"get",url:"https://api.github.com/repos/"+t,transform:function(t){var e=JSON.parse(t.responseText).watchers_count||0;return _(this,e)}}},dribbble:function(t){t=t.indexOf("dribbble.com/shots")>-1?t.split("shots/")[1]:t;var e="https://api.dribbble.com/v1/shots/"+t+"/likes";return{type:"get",url:e,transform:function(t,n){var r=this,a=JSON.parse(t.responseText).length;if(12!==a)return _(this,a);var o=2;A(e,o,a,function(t){return r.appendTo&&"function"!=typeof r.appendTo&&r.appendTo.appendChild(r.os),v(r.os,t,r.cb),n.trigger(r.os,"counted-"+r.url),_(r,t)})}}},twitter:function(t){return{type:"get",url:"https://api.openshare.social/job?url="+t+"&key=",transform:function(t){var e=JSON.parse(t.responseText).count||0;return _(this,e)}}}},D=function(){function t(e,n){var a=this;if(r(this,t),!n)throw new Error("Open Share: no url provided for count");if(0===e.indexOf("github")&&("github-stars"===e?e="githubStars":"github-forks"===e?e="githubForks":"github-watchers"===e?e="githubWatchers":console.error("Invalid Github count type. Try github-stars, github-forks, or github-watchers.")),e.indexOf(",")>-1){this.type=e,this.typeArr=this.type.split(","),this.countData=[],this.typeArr.forEach(function(t){if(!N[t])throw new Error("Open Share: "+e+" is an invalid count type");a.countData.push(N[t](n))});var o=this.storeGet(this.type+"-"+this.shared);o&&(this.appendTo&&"function"!=typeof this.appendTo&&this.appendTo.appendChild(this.os),v(this.os,o))}else{if(!N[e])throw new Error("Open Share: "+e+" is an invalid count type");this.type=e,this.countData=N[e](n)}}return S(t,[{key:"count",value:function(t,e,n){this.os=t,this.appendTo=n,this.cb=e,this.url=this.os.getAttribute("data-open-share-count"),this.shared=this.os.getAttribute("data-open-share-count-url"),this.key=this.os.getAttribute("data-open-share-key"),Array.isArray(this.countData)?this.getCounts():this.getCount()}},{key:"getCount",value:function(){var t=this.storeGet(this.type+"-"+this.shared);t&&(this.appendTo&&"function"!=typeof this.appendTo&&this.appendTo.appendChild(this.os),v(this.os,t)),this[this.countData.type](this.countData)}},{key:"getCounts",value:function(){var t=this;this.total=[];var e=this.storeGet(this.type+"-"+this.shared);e&&(this.appendTo&&"function"!=typeof this.appendTo&&this.appendTo.appendChild(this.os),v(this.os,e)),this.countData.forEach(function(e){t[e.type](e,function(e){if(t.total.push(e),t.total.length===t.typeArr.length){var n=0;t.total.forEach(function(t){n+=t}),t.appendTo&&"function"!=typeof t.appendTo&&t.appendTo.appendChild(t.os);var r=Number(t.storeGet(t.type+"-"+t.shared));r>n&&(n=r),t.storeSet(t.type+"-"+t.shared,n),v(t.os,n)}})}),this.appendTo&&"function"!=typeof this.appendTo&&this.appendTo.appendChild(this.os)}},{key:"jsonp",value:function(t,e){var n=this,r=Math.random().toString(36).substring(7).replace(/[^a-zA-Z]/g,"");window[r]=function(r){var a=t.transform.apply(n,[r])||0;e&&"function"==typeof e?e(a):(n.appendTo&&"function"!=typeof n.appendTo&&n.appendTo.appendChild(n.os),v(n.os,a,n.cb)),O.trigger(n.os,"counted-"+n.url)};var a=document.createElement("script");a.src=t.url.replace("callback=?","callback="+r),document.getElementsByTagName("head")[0].appendChild(a)}},{key:"get",value:function(t,e){var n=this,r=new XMLHttpRequest;r.onreadystatechange=function(){if(4===r.readyState){if(200===r.status){var a=t.transform.apply(n,[r,O])||0;return e&&"function"==typeof e?e(a):(n.appendTo&&"function"!=typeof n.appendTo&&n.appendTo.appendChild(n.os),v(n.os,a,n.cb)),void O.trigger(n.os,"counted-"+n.url)}if(0===t.url.toLowerCase().indexOf("https://api.openshare.social/job?")){console.warn("Please sign up for Twitter counts at https://openshare.social/twitter/auth");var o=0;e&&"function"==typeof e?e(o):(n.appendTo&&"function"!=typeof n.appendTo&&n.appendTo.appendChild(n.os),v(n.os,o,n.cb)),O.trigger(n.os,"counted-"+n.url)}else{console.warn("Failed to get API data from",t.url,". Please use the latest version of OpenShare.");var i=0;e&&"function"==typeof e?e(i):(n.appendTo&&"function"!=typeof n.appendTo&&n.appendTo.appendChild(n.os),v(n.os,i,n.cb)),O.trigger(n.os,"counted-"+n.url)}}},t.url=t.url.startsWith("https://api.openshare.social/job?")&&this.key?t.url+this.key:t.url,r.open("GET",t.url),r.send()}},{key:"post",value:function(t,e){var n=this,r=new XMLHttpRequest;r.onreadystatechange=function(){if(r.readyState===XMLHttpRequest.DONE&&200===r.status){var a=t.transform.apply(n,[r])||0;e&&"function"==typeof e?e(a):(n.appendTo&&"function"!=typeof n.appendTo&&n.appendTo.appendChild(n.os),v(n.os,a,n.cb)),O.trigger(n.os,"counted-"+n.url)}},r.open("POST",t.url),r.setRequestHeader("Content-Type","application/json;charset=UTF-8"),r.send(JSON.stringify(t.data))}},{key:"storeSet",value:function(t){var e=arguments.length<=1||void 0===arguments[1]?0:arguments[1];window.localStorage&&t&&localStorage.setItem("OpenShare-"+t,e)}},{key:"storeGet",value:function(t){if(window.localStorage&&t)return localStorage.getItem("OpenShare-"+t)}}]),t}(),M=function(){return"complete"===document.readyState?k():void document.addEventListener("readystatechange",function(){"complete"===document.readyState&&k()},!1)},q=function(){var t=function(){function t(e,n){var a=this;r(this,t),e.bindClick||(e.bindClick=!0);var o=e.type.indexOf("-");o>-1&&(e.type=C(o,e.type));var i=void 0;if(this.element=n,this.data=e,this.os=new L(e.type,x[e.type]),this.os.setData(e),n&&!e.element||(n=e.element,i=document.createElement(n||"a"),e.type&&(i.classList.add("open-share-link",e.type),i.setAttribute("data-open-share",e.type),i.setAttribute("data-open-share-node",e.type)),e.innerHTML&&(i.innerHTML=e.innerHTML)),i&&(n=i),e.bindClick&&n.addEventListener("click",function(){a.share()}),e.appendTo&&e.appendTo.appendChild(n),e.classes&&Array.isArray(e.classes)&&e.classes.forEach(function(t){n.classList.add(t)}),"paypal"===e.type.toLowerCase()){var s=e.sandbox?"https://www.sandbox.paypal.com/cgi-bin/webscr":"https://www.paypal.com/cgi-bin/webscr",u=e.sandbox?"https://www.sandbox.paypal.com/en_US/i/btn/btn_buynow_LG.gif":"https://www.paypalobjects.com/en_US/i/btn/btn_buynow_LG.gif",p=e.sandbox?"https://www.sandbox.paypal.com/en_US/i/scr/pixel.gif":"https://www.paypalobjects.com/en_US/i/scr/pixel.gif",h="