article/SocialShare.js

import delegateEvent from '../utils/delegateEvent';
import dynamicElement from '../utils/dynamicElement';

/**
 * Sets social share elements in article, in main nav and in submenu popup
 *
 * @module SocialShare
 * @prop {Object} socialShare - DOM reference to the navigation bar
 * @prop {String} selector - DOM selector for social share attributes
 * @prop {String} commentsSelector - DOM selector for comments section
 * @prop {String} articleUrl - Article URL from meta tags
 * @prop {String} articleUrlEncoded - encoded article URL from meta tags
 * @prop {String} articleTitle - Article title from meta tags
 * @prop {String} articleTitleEncoded - encoded article title from meta tags
 * @prop {String} navbarLongformClass - DOM selector for navbar on longform template
 * @prop {String} longformHeaderClass - DOM selector for header on longform template
 * @prop {String} interactiveHeaderClass - DOM selector for header on interactive template
 * @prop {Object} $notificationContainer - DOM reference to notification container
 * @prop {String} copiedLinkSelector - DOM selector for 'Link Copied!' popup
 */

const SocialShare = {
	selector: 'data-socialshare',
	commentsSelector: '.l-main__comments',
	articleUrl: '',
	articleUrlEncoded: '',
	articleTitle: '',
	articleTitleEncoded: '',
	navbarLongformClass: 'l-longform-navbar',
	longformHeaderClass: '.l-longform-header',
	interactiveHeaderClass: '.l-interactive-header',
	$notificationContainer: document.querySelector( '#notification' ),
	copiedLinkSelector: '.c-socialShare__copiedLink',

	/* global gn_analytics */

	/**
	 * Initializes social share elements on the page
	 *
	 * @method init
	 */
	init() {
		const $elems = document.querySelectorAll( '.c-socialShare' );

		// Set article url
		const articleUrl = document.querySelector( 'link[rel="canonical"]' );
		this.articleUrl = articleUrl ? articleUrl.getAttribute( 'href' ) : null;
		this.articleUrlEncoded = encodeURIComponent( this.articleUrl );

		// Set article title
		this.articleTitle = '';
		const $title = document.querySelector( 'meta[property="og:title"]' );
		if ( $title && $title.getAttribute( 'content' ) ) {
			// Use openGraph Title
			this.articleTitle = $title.getAttribute( 'content' );
		} else {
			// Use page title
			this.articleTitle = document.title;
		}
		this.articleTitleEncoded = encodeURIComponent( this.articleTitle );

		// If there is no 'Social Share' elements on the page, don't run
		if ( 0 === $elems.length ) {
			return;
		}

		// Create each social share block
		[].forEach.call( $elems, ( $item, $index ) => {
			this.setup( $item );
			this.setId( $item, $index );
		});

		// watch for dynamically added social share
		delegateEvent( dynamicElement.selector, 'added', '.c-socialShare', ( event, $socials ) => {
			event.preventDefault();
			event.stopPropagation();
			[].forEach.call( $socials, ( $social ) => {
				this.setup( $social );
				this.setId( $social, document.querySelectorAll( '.c-socialShare' ).length );
			});
		}, false, true );
	},

	/**
	 * Sets up functionality for each social share container.
	 * Assigns 'title' and 'url' attributes to container, bind click event
	 *
	 * @method setup
	 * @param {Element} $item - Social share container element
	 */
	setup( $item ) {
		// Loop through all 'Social Share' containers
		const $providerButtons = $item.querySelectorAll( '.c-socialShare__item' );
		[].forEach.call( $providerButtons, ( $button ) => {
			const provider = $button.getAttribute( `${this.selector}-provider` );

			// Add href attribute to <a> elements, skip <button> elements
			const $buttonLink = $button.querySelector( 'a' );
			if ( $buttonLink ) {
				const url = this.getLink( provider );
				$buttonLink.setAttribute( 'href', url );

				// Add target attribute
				const target = 'print' === provider || 'more' === provider ? '_self' : '_blank';
				if ( 'comments' !== provider && 'copy' !== provider ) {
					$buttonLink.setAttribute( 'target', target );
				}

				if ( 'comments' !== provider && 'copy' !== provider && 'print' !== provider && 'more' !== provider ) {
					$buttonLink.setAttribute( 'rel', 'noreferrer' );
				}
			}

			// Add event listener for clicking the buttons
			$button.addEventListener( 'click', ( event ) => {
				SocialShare.handleClick( event );
			});
		});
	},

	/**
	 * Sets id attribute for each  social share container
	 *
	 * @method setId
	 * @param {Element} $item - Social share container element
	 * @param {Integer} index - Element index
	 */
	setId( $item, index ) {
		$item.setAttribute( `${this.selector}-id`, index );
	},

	/**
	 * Handles click event when social share button is clicked
	 *
	 * @method handleClick
	 * @param {Object} ev - Social share button click event
	 */
	handleClick( ev ) {
		const $target = ev.currentTarget;
		const provider = $target.getAttribute( `${this.selector}-provider` );

		// Always blur :)
		$target.blur();

		if ( '' === $target.getAttribute( 'href' ) ) {
			ev.preventDefault();
		}

		// Some providers need you to do some stuff
		if ( 'print' === provider ) { // 'Print' button is clicked
			window.print();
		} else if ( 'copy' === provider ) { // 'Copy' button is clicked
			this.handleCopyClick( ev );
		} else if ( 'linkedin' === provider ) { // 'Linkedin' button is clicked
			this.handleLinkedinClick( ev, this.articleUrl );
		}

		this.trackClick( $target );
	},

	/**
	 * Handles click event for 'Copy' button in social share
	 *
	 * @method handleCopyClick
	 * @param {Object} ev - Social share 'Comments' button click event
	 */
	handleCopyClick( ev ) {
		ev.preventDefault();
		// create textarea and add article url as value
		const hiddenTextArea = document.createElement( 'textarea' );
		hiddenTextArea.value = this.articleUrl;
		// hide textarea (note: can't use display:none, otherwise copying does not work)
		hiddenTextArea.style.height = '1px';
		hiddenTextArea.style.width = '1px';
		hiddenTextArea.style.opacity = 0;
		hiddenTextArea.setAttribute( 'readonly', '' );
		document.body.appendChild( hiddenTextArea );
		// copy textarea value
		hiddenTextArea.select();
		document.execCommand( 'copy' );
		// remove textarea after link is copied
		document.body.removeChild( hiddenTextArea );

		// Only add "Link copied!" notification when one is not already in place
		if ( ! document.querySelector( this.copiedLinkSelector ) ) {
			// Add 'Link copied!' popup briefly below sticky notification bar
			let $textCopiedDiv = document.createElement( 'div' );
			const copiedText = document.createTextNode( 'Link copied!' );
			$textCopiedDiv.appendChild( copiedText );
			$textCopiedDiv.classList.add( 'c-socialShare__copiedLink' );

			// Create top offset for 'Link Copied!' based on admin bar, navbar and notification height
			let anchorElement = null;
			const templateType = ev.currentTarget.parentNode.parentNode.dataset.socialshareTemplateType;

			switch ( templateType ) {
			case 'longform':
				anchorElement = document.querySelector( this.longformHeaderClass );
				break;
			case 'interactive':
				anchorElement = document.querySelector( this.interactiveHeaderClass );
				break;
			case 'gallery':
				anchorElement = document.querySelector( '.c-gallery' );
				break;
			default:
				anchorElement = this.$notificationContainer;
				break;
			}

			$textCopiedDiv.classList.add( `c-socialShare__copiedLink--${templateType}` );

			if ( anchorElement && $textCopiedDiv ) {
				anchorElement.insertAdjacentElement( 'afterend', $textCopiedDiv );
				$textCopiedDiv = document.querySelector( this.copiedLinkSelector );
				$textCopiedDiv.classList.add( 'is-faded-in' );
				$textCopiedDiv.classList.add( 'is-animated' );

				// After 4 seconds start fading out the popup (transition lasts .3s)
				setTimeout( () => {
					$textCopiedDiv.classList.remove( 'is-faded-in' );
					$textCopiedDiv.classList.remove( 'is-animated' );
					$textCopiedDiv.classList.add( 'is-faded-out' );
					$textCopiedDiv.classList.add( 'is-animated' );
				}, 4000 );

				// Then after 4.3 seconds remove it from the page
				setTimeout( () => {
					$textCopiedDiv.remove();
				}, 4300 );
			}
		}
	},

	handleLinkedinClick( ev, url ) {
		ev.preventDefault();
		window.open( `https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent( url )}`, '_blank', 'toolbar=no,scrollbars=0,resizable=no,fullscreen=no,top=50,left=50,width=550,height=600' );
	},

	/**
	 * Gets href attribute for each social share button
	 *
	 * @method getLink
	 * @param {String} provider - Social share provider name
	 */
	getLink( provider ) {
		const keyEncode = this.articleUrlEncoded.replace( '.', '%2E' );
		const media = this.getMedia();

		let link = '';
		switch ( provider ) {
		case 'facebook':
			link = `https://www.facebook.com/sharer/sharer.php?u=${keyEncode}`;
			break;
		case 'print':
			link = keyEncode.replace( '%2Fnews%2F', '%2Fprint%2F' );
			break;
		case 'twitter':
			link = `https://twitter.com/intent/tweet?text=${this.articleTitleEncoded}%20${keyEncode}`;
			break;
		case 'email':
			link = `mailto:?Subject=${this.articleTitleEncoded}&body=%0D%0A${this.articleTitleEncoded}%0D%0A${keyEncode}%0D%0A%0D%0A%0D%0A`;
			break;
		case 'whatsapp':
			link = `whatsapp://send?text=${this.articleTitleEncoded}%20-%20${keyEncode}`;
			break;
		case 'reddit':
			link = `https://www.reddit.com/submit?url=${keyEncode}&title=${this.articleTitleEncoded}`;
			break;
		case 'pinterest':
			link = `https://pinterest.com/pin/create/button/?url=${keyEncode}&media=${media}&description=${this.articleTitleEncoded}`;
			break;
		case 'flipboard':
			link = `https://share.flipboard.com/bookmarklet/popout?v=2&title=%22${this.articleTitleEncoded}%22&url=${keyEncode}`;
			break;
		default:
			link = '';
			break;
		}

		return link;
	},

	/**
	 * Gets social media image from <meta property="og:image">
	 *
	 * @method getMedia
	 * @return {String} - URL of the og:image.
	 */
	getMedia() {
		const $ogImg = document.querySelector( 'meta[property="og:image"]' );
		if ( ! $ogImg ) {
			return '';
		}

		return encodeURIComponent( $ogImg.getAttribute( 'content' ) );
	},

	/**
	 * Analytics tracking for social share.
	 *
	 * @param {HTMLElement} $elem - HTML Element that triggered the click.
	 *
	 * @method trackClick
	 */
	trackClick( $elem ) {
		/* eslint-disable camelcase */
		if ( 'undefined' !== typeof ( gn_analytics ) ) {
			// Set a small timeout to make sure the correct aria-expand state is read.
			setTimeout( () => {
				let action = $elem.dataset.socialshareProvider;
				if ( 'more' === action && 'false' === $elem.getAttribute( 'aria-expanded' ) ) {
					action = 'close';
				}

				const trackingData = {
					'content.share': `share|news|${action}`,
				};

				gn_analytics.Analytics.track(['adobe'], {
					eventType: 'click',
					action: `social share | ${action}`,
					data: trackingData,
					target: $elem,
				});
			}, 500 );
		}
		/* eslint-enable camelcase */
	},
};

export default SocialShare;