From 83a0ff90731cc5b447d06e2c72008887afebd162 Mon Sep 17 00:00:00 2001 From: Murhaf Sousli Date: Tue, 27 Mar 2018 06:09:06 +0300 Subject: [PATCH] v5.2.0 - refactor(ShareButtonsService): Initialize meta tags from document head 'og' meta tags. - refactor(ShareButtonsService): Add `url` as a meta tag property to the global config. - refactor(ShareDirective): Initialize meta tags inputs from the global config. - refactor(Props): Add url as meta tag property. - refactor(Operators): Removes Pinterest operator since description and image meta tags are being set from the service. - refactor(Operators): Removes None operator since url became part of the meta tags. - refactor(Operators): Changes `EmailOperator` to `urlInMessageOperator` - refactor(Utils): Move `getOS()` and `getValidUrl` to utils file and remove encodeUriComponent. - refactor(Utils): Do not encode the url in `getValidUrl` function since the url becomes a meta tag. - fix(WhatsApp): Adds share URL to message body, closes #239 --- lib/core/src/share-button.directive.ts | 161 +++++++++---------------- lib/core/src/share.models.ts | 5 +- lib/core/src/share.operators.ts | 77 ++++-------- lib/core/src/share.prop.ts | 145 ++++++++++++++-------- lib/core/src/share.service.ts | 19 +-- lib/core/src/utils.ts | 34 ++++++ 6 files changed, 229 insertions(+), 212 deletions(-) diff --git a/lib/core/src/share-button.directive.ts b/lib/core/src/share-button.directive.ts index 3180bd6f..f344a5bb 100644 --- a/lib/core/src/share-button.directive.ts +++ b/lib/core/src/share-button.directive.ts @@ -8,7 +8,9 @@ import { Renderer2, ChangeDetectorRef, PLATFORM_ID, - Inject + Inject, + OnChanges, + SimpleChanges } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { HttpClient } from '@angular/common/http'; @@ -22,6 +24,7 @@ import { of } from 'rxjs/observable/of'; import { ShareButtons } from './share.service'; import { IShareButton, ShareButtonRef } from './share.models'; +import { getOS, getValidUrl } from './utils'; /** Google analytics ref */ declare const ga: Function; @@ -30,10 +33,10 @@ declare const window: any; @Directive({ selector: '[shareButton]' }) -export class ShareButtonDirective { +export class ShareButtonDirective implements OnChanges { /** A ref for window object that works on SSR */ - window: any; + window: Window; /** A ref for navigator object that works on SSR */ navigator: Navigator; @@ -41,66 +44,26 @@ export class ShareButtonDirective { /** Button properties */ prop: IShareButton; - /** The validated share URL */ - url: string; - - /** Button class - used to remove previous class when the button type is changed */ + /** A ref to button class - used to remove previous class when the button type is changed */ buttonClass: string; - /** Meta tags inputs - initialized from the global options */ - @Input() sbTitle = this.shareService.title; - @Input() sbDescription = this.shareService.description; - @Input() sbImage = this.shareService.image; - @Input() sbTags = this.shareService.tags; - - /** Create share button */ - @Input('shareButton') - set setButton(buttonName: string) { - - /** Create a new button of type */ - const button = {...this.shareService.prop[buttonName]}; - - if (button) { - - // Set share button - this.prop = button; + /** Share button type */ + @Input() shareButton: string; - // Remove previous button class - this.renderer.removeClass(this.el.nativeElement, `sb-${this.buttonClass}`); - - // Add new button class - this.renderer.addClass(this.el.nativeElement, `sb-${button.type}`); - - // Set button css color variable - this.el.nativeElement.style.setProperty(`--${this.prop.type}-color`, this.prop.color); - - // Keep a copy of the class for future replacement - this.buttonClass = button.type; - - this.getShareCount(); - } else { - throw new Error(`[ShareButtons]: The share button '${buttonName}' does not exist!`); - } - } - - /** Set share URL */ - @Input() - set sbUrl(newUrl: string) { - - /** Check if new URL is equal the current URL */ - if (newUrl !== this.url) { - this.url = this.getValidURL(newUrl); - this.getShareCount(); - } - } + /** Meta tags inputs - initialized from the global options */ + @Input() sbUrl: string; + @Input() sbTitle: string; + @Input() sbDescription: string; + @Input() sbImage: string; + @Input() sbTags: string; - /** Share count event */ + /** Stream that emits when share count is fetched */ @Output() sbCount = new EventEmitter(); - /** Share dialog opened event */ + /** Stream that emits when share dialog is opened */ @Output() sbOpened = new EventEmitter(); - /** Share dialog closed event */ + /** Stream that emits when share dialog is closed */ @Output() sbClosed = new EventEmitter(); constructor(private shareService: ShareButtons, @@ -115,34 +78,28 @@ export class ShareButtonDirective { } } - /** - * Share link on element click - */ + /** Share link on element click */ @HostListener('click') onClick() { - /** Set user did not set the url using [sbUrl], use window URL */ - if (!this.url) { - this.url = encodeURIComponent(this.window.location.href); - } const ref: ShareButtonRef = { - url: this.url, cd: this.cd, renderer: this.renderer, window: this.window, prop: this.prop, el: this.el.nativeElement, - os: this.getOS(), + os: getOS(this.window, this.navigator), metaTags: { - title: this.sbTitle, - description: this.sbDescription, - image: this.sbImage, + url: this.sbUrl, + title: this.sbTitle || this.shareService.title, + description: this.sbDescription || this.shareService.description, + image: this.sbImage || this.shareService.image, tags: this.sbTags, via: this.shareService.twitterAccount, } }; - /** Share the link */ + // Share the link of(ref).pipe( ...this.prop.share.operators, tap((sharerURL: string) => this.share(sharerURL)), @@ -150,10 +107,15 @@ export class ShareButtonDirective { ).subscribe(); } - getShareCount() { - // if count output has observers, emit the share count */ - if (this.url && this.sbCount.observers.length && this.prop.count) { - this.count(this.url).subscribe((count: number) => this.sbCount.emit(count)); + ngOnChanges(changes: SimpleChanges) { + + if (changes['shareButton'].firstChange || changes['shareButton'].previousValue !== this.shareButton) { + this.createShareButton(this.shareButton); + } + + if (changes['sbUrl'] && (changes['sbUrl'].firstChange || changes['url'].previousValue !== this.sbUrl)) { + this.sbUrl = getValidUrl(this.sbUrl || this.shareService.url, this.window.location.href); + this.getShareCount(this.sbUrl); } } @@ -166,7 +128,7 @@ export class ShareButtonDirective { // GA Tracking if (this.shareService.gaTracking && typeof ga !== 'undefined') { - ga('send', 'social', this.prop.type, 'click', this.url); + ga('send', 'social', this.prop.type, 'click', this.sbUrl); } // Emit when share dialog is opened @@ -208,41 +170,38 @@ export class ShareButtonDirective { } } - /** - * Get a valid URL for sharing - * @param url - URL to validate - * @returns Sharable URL - */ - private getValidURL(url: string) { - if (url) { - const r = /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; + private createShareButton(buttonsName: string) { + const button = {...this.shareService.prop[buttonsName]}; - if (r.test(url)) { - return encodeURIComponent(url); - } - console.warn(`[ShareButtons]: Sharing link '${url}' is invalid!`); - } - // fallback to page current URL - return encodeURIComponent(this.window.location.href); - } + if (button) { - /** - * Detect operating system. - * returns 'ios', 'android', or 'desktop'. - */ - private getOS() { - const userAgent = this.navigator.userAgent || this.navigator.vendor || this.window.opera; + // Set share button properties + this.prop = button; - if (/android/i.test(userAgent)) { - return 'android'; + // Remove previous button class + this.renderer.removeClass(this.el.nativeElement, `sb-${this.buttonClass}`); + + // Add new button class + this.renderer.addClass(this.el.nativeElement, `sb-${button.type}`); + + // Set button css color variable + this.el.nativeElement.style.setProperty(`--${this.prop.type}-color`, this.prop.color); + + // Keep a copy of the class for future replacement + this.buttonClass = button.type; + + this.getShareCount(this.sbUrl); + } else { + throw new Error(`[ShareButtons]: The share button '${buttonsName}' does not exist!`); } + } - // iOS detection from: http://stackoverflow.com/a/9039885/177710 - if (/iPad|iPhone|iPod/.test(userAgent) && !this.window.MSStream) { - return 'ios'; + private getShareCount(url: string) { + // if url is valid and button supports share count + if (url && this.prop.count && this.sbCount.observers.length) { + this.count(url).subscribe((count: number) => this.sbCount.emit(count)); } - return 'desktop'; } } diff --git a/lib/core/src/share.models.ts b/lib/core/src/share.models.ts index 14246c4a..ee28e9d9 100644 --- a/lib/core/src/share.models.ts +++ b/lib/core/src/share.models.ts @@ -16,6 +16,7 @@ export interface ShareButtonsOptions { include?: string[]; exclude?: string[]; size?: number; + url?: string; title?: string; description?: string; image?: string; @@ -77,7 +78,7 @@ export interface IShareButton { /** * Share button directive ref interface - * It has all the needed objects for the share operators + * This ref to be used in the share operators */ export interface ShareButtonRef { prop?: IShareButton; @@ -87,8 +88,8 @@ export interface ShareButtonRef { el?: HTMLElement; os?: string; temp?: any; - url?: string; metaTags: { + url?: string; title?: string; description?: string; image?: string; diff --git a/lib/core/src/share.operators.ts b/lib/core/src/share.operators.ts index 489b4f07..c7796bab 100644 --- a/lib/core/src/share.operators.ts +++ b/lib/core/src/share.operators.ts @@ -5,36 +5,30 @@ import { ShareButtonRef } from './share.models'; import { copyToClipboard, mergeDeep } from './utils'; /** - * None operator - just return the sharer URL - */ -export const noneOperator = map((ref: ShareButtonRef) => (ref.prop.share[ref.os]) ? ref.prop.share[ref.os] + ref.url : null); - -/** - * Meta tags operator - Serialize meta tags in the sharer URL + * Meta tags operator - Serialize meta tags into the sharer URL */ export const metaTagsOperator = map((ref: ShareButtonRef) => { - // object contains supported meta tags - const metaTags = ref.prop.share.metaTags; - - // object contains meta tags values */ - const metaTagsValues = ref.metaTags; - // Social network sharer URL */ const SharerURL = ref.prop.share[ref.os]; + if (SharerURL) { - // User share link - let link = ref.url; + // object contains supported meta tags + const metaTags = ref.prop.share.metaTags; - // Set each meta tag with user value - if (metaTags) { - Object.keys(metaTags).map((key) => { - if (metaTagsValues[key]) { - link += `&${metaTags[key]}=${encodeURIComponent(metaTagsValues[key])}`; - } - }); + // object contains meta tags values */ + const metaTagsValues = ref.metaTags; + + let link = ''; + // Set each meta tag with user value + if (metaTags) { + link = Object.entries(metaTags).map(([key, metaTag]) => + metaTagsValues[key] ? `${metaTag}=${encodeURIComponent(metaTagsValues[key])}` : '' + ).join('&'); + } + return SharerURL + link; } - return SharerURL + link; + return; }); /** @@ -43,33 +37,7 @@ export const metaTagsOperator = map((ref: ShareButtonRef) => { export const printOperator = map((ref: ShareButtonRef) => ref.window.print()); /** - * Pinterest operator - Since Pinterest requires the description and image meta tags, - * this function checks if the meta tags are presented, if not it falls back to page meta tags - * This should placed after the metaTagsOperator - */ -export const pinterestOperator = map((url: string) => { - if (!url.includes('&description')) { - /** If user didn't add description, get it from the OG meta tag */ - const ogDescription: Element = document.querySelector(`meta[property="og:description"]`); - if (ogDescription) { - url += '&description=' + ogDescription.getAttribute('content'); - } else { - console.warn(`[ShareButtons]: You didn't set the description text for Pinterest button`); - } - } - if (!url.includes('&media')) { - const ogImage: Element = document.querySelector(`meta[property="og:image"]`); - if (ogImage) { - url += '&media=' + ogImage.getAttribute('content'); - } else { - console.warn(`[ShareButtons]: You didn't set the image URL for Pinterest button`); - } - } - return url; -}); - -/** - * Copy button operator - to copy link to clipboard + * Copy link to clipboard, used for copy button */ export const copyOperators = [ map((ref: ShareButtonRef) => { @@ -78,7 +46,7 @@ export const copyOperators = [ ref.renderer.setStyle(ref.el, 'pointer-events', 'none'); ref.temp = {text: ref.prop.text, icon: ref.prop.icon}; - const link = decodeURIComponent(ref.url); + const link = decodeURIComponent(ref.metaTags.url); copyToClipboard(link, ref.os === 'ios') .then(() => { @@ -105,12 +73,15 @@ export const copyOperators = [ }) ]; -export const emailOperator = map((ref: ShareButtonRef) => { +/** + * Add the share URL to message body, used for WhatsApp and Email buttons + */ +export const urlInMessageOperator = map((ref: ShareButtonRef) => { const description = ref.metaTags.description; - const url = decodeURIComponent(ref.url); + const url = ref.metaTags.url; const newRef: ShareButtonRef = { metaTags: { - description: description ? `${description}\r\n\r\n${url}` : url + description: description ? `${description}\r\n${url}` : url } }; return mergeDeep(ref, newRef); diff --git a/lib/core/src/share.prop.ts b/lib/core/src/share.prop.ts index cd367bde..9144cdf6 100644 --- a/lib/core/src/share.prop.ts +++ b/lib/core/src/share.prop.ts @@ -1,5 +1,5 @@ import { map } from 'rxjs/operators/map'; -import { noneOperator, metaTagsOperator, printOperator, pinterestOperator, copyOperators, emailOperator } from './share.operators'; +import { metaTagsOperator, printOperator, copyOperators, urlInMessageOperator } from './share.operators'; import { IShareButtons } from './share.models'; export const shareButtonsProp: IShareButtons = { @@ -9,10 +9,15 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-facebook-f', color: '#4267B2', share: { - desktop: 'https://www.facebook.com/sharer/sharer.php?u=', - android: 'https://www.facebook.com/sharer/sharer.php?u=', - ios: 'https://www.facebook.com/sharer/sharer.php?u=', - operators: [noneOperator] + desktop: 'https://www.facebook.com/sharer/sharer.php?', + android: 'https://www.facebook.com/sharer/sharer.php?', + ios: 'https://www.facebook.com/sharer/sharer.php?', + metaTags: { + url: 'u' + }, + operators: [ + metaTagsOperator + ] }, count: { request: 'http', @@ -28,13 +33,14 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-twitter', color: '#00acee', share: { - desktop: 'https://twitter.com/intent/tweet?url=', - android: 'https://twitter.com/intent/tweet?url=', - ios: 'https://twitter.com/intent/tweet?url=', + desktop: 'https://twitter.com/intent/tweet?', + android: 'https://twitter.com/intent/tweet?', + ios: 'https://twitter.com/intent/tweet?', operators: [ metaTagsOperator ], metaTags: { + url: 'url', description: 'text', tags: 'hashtags', via: 'via' @@ -47,10 +53,15 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-google-plus-g', color: '#DB4437', share: { - desktop: 'https://plus.google.com/share?url=', - android: 'https://plus.google.com/share?url=', - ios: 'https://plus.google.com/share?url=', - operators: [noneOperator], + desktop: 'https://plus.google.com/share?', + android: 'https://plus.google.com/share?', + ios: 'https://plus.google.com/share?', + metaTags: { + url: 'url', + }, + operators: [ + metaTagsOperator + ] } }, linkedin: { @@ -59,11 +70,14 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-linkedin-in', color: '#006fa6', share: { - desktop: 'http://www.linkedin.com/shareArticle?url=', - android: 'http://www.linkedin.com/shareArticle?url=', - ios: 'http://www.linkedin.com/shareArticle?url=', - operators: [metaTagsOperator], + desktop: 'http://www.linkedin.com/shareArticle?', + android: 'http://www.linkedin.com/shareArticle?', + ios: 'http://www.linkedin.com/shareArticle?', + operators: [ + metaTagsOperator + ], metaTags: { + url: 'url', title: 'title', description: 'summary' }, @@ -75,14 +89,14 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-pinterest-p', color: '#BD091D', share: { - desktop: 'https://in.pinterest.com/pin/create/button/?url=', - android: 'https://in.pinterest.com/pin/create/button/?url=', - ios: 'https://in.pinterest.com/pin/create/button/?url=', + desktop: 'https://in.pinterest.com/pin/create/button/?', + android: 'https://in.pinterest.com/pin/create/button/?', + ios: 'https://in.pinterest.com/pin/create/button/?', operators: [ - metaTagsOperator, - pinterestOperator + metaTagsOperator ], metaTags: { + url: 'url', description: 'description', image: 'media' } @@ -103,13 +117,14 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-reddit-alien', color: '#FF4006', share: { - desktop: 'http://www.reddit.com/submit?url=', - android: 'http://www.reddit.com/submit?url=', - ios: 'http://www.reddit.com/submit?url=', + desktop: 'http://www.reddit.com/submit?', + android: 'http://www.reddit.com/submit?', + ios: 'http://www.reddit.com/submit?', operators: [ metaTagsOperator ], metaTags: { + url: 'url', title: 'title' }, }, @@ -127,13 +142,14 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-tumblr', color: '#36465D', share: { - desktop: 'http://tumblr.com/widgets/share/tool?canonicalUrl=', - android: 'http://tumblr.com/widgets/share/tool?canonicalUrl=', - ios: 'http://tumblr.com/widgets/share/tool?canonicalUrl=', + desktop: 'http://tumblr.com/widgets/share/tool?', + android: 'http://tumblr.com/widgets/share/tool?', + ios: 'http://tumblr.com/widgets/share/tool?', operators: [ metaTagsOperator ], metaTags: { + url: 'canonicalUrl', description: 'caption', tags: 'tags' } @@ -155,7 +171,10 @@ export const shareButtonsProp: IShareButtons = { desktop: 'https://web.whatsapp.com/send?', android: 'whatsapp://send?', ios: 'whatsapp://send?', - operators: [metaTagsOperator], + operators: [ + urlInMessageOperator, + metaTagsOperator + ], metaTags: { description: 'text' } @@ -167,9 +186,14 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-facebook-messenger', color: '#0080FF', share: { - android: 'fb-messenger://share/?link=', - ios: 'fb-messenger://share/?link=', - operators: [noneOperator] + android: 'fb-messenger://share/?', + ios: 'fb-messenger://share/?', + metaTags: { + url: 'link' + }, + operators: [ + metaTagsOperator + ] } }, telegram: { @@ -178,11 +202,14 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-telegram-plane', color: '#0088cc', share: { - desktop: 'https://t.me/share/url?url=', - android: 'https://t.me/share/url?url=', - ios: 'https://t.me/share/url?url=', - operators: [metaTagsOperator], + desktop: 'https://t.me/share/url?', + android: 'https://t.me/share/url?', + ios: 'https://t.me/share/url?', + operators: [ + metaTagsOperator + ], metaTags: { + url: 'url', description: 'text' } } @@ -193,10 +220,15 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-vk', color: '#4C75A3', share: { - desktop: 'http://vk.com/share.php?url=', - android: 'http://vk.com/share.php?url=', - ios: 'http://vk.com/share.php?url=', - operators: [noneOperator] + desktop: 'http://vk.com/share.php?', + android: 'http://vk.com/share.php?', + ios: 'http://vk.com/share.php?', + metaTags: { + url: 'url' + }, + operators: [ + metaTagsOperator + ] } }, stumble: { @@ -205,10 +237,15 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-stumbleupon', color: '#eb4924', share: { - desktop: 'http://www.stumbleupon.com/submit?url=', - android: 'http://www.stumbleupon.com/submit?url=', - ios: 'http://www.stumbleupon.com/submit?url=', - operators: [noneOperator] + desktop: 'http://www.stumbleupon.com/submit?', + android: 'http://www.stumbleupon.com/submit?', + ios: 'http://www.stumbleupon.com/submit?', + metaTags: { + url: 'url' + }, + operators: [ + metaTagsOperator + ] } }, xing: { @@ -217,10 +254,15 @@ export const shareButtonsProp: IShareButtons = { icon: 'fab fa-xing', color: '#006567', share: { - desktop: 'https://www.xing.com/app/user?op=share&url=', - android: 'https://www.xing.com/app/user?op=share&url=', - ios: 'https://www.xing.com/app/user?op=share&url=', - operators: [noneOperator] + desktop: 'https://www.xing.com/app/user?op=share&', + android: 'https://www.xing.com/app/user?op=share&', + ios: 'https://www.xing.com/app/user?op=share&', + metaTags: { + url: 'url' + }, + operators: [ + metaTagsOperator + ] } }, email: { @@ -232,7 +274,10 @@ export const shareButtonsProp: IShareButtons = { desktop: 'mailto:?', android: 'mailto:?', ios: 'mailto:?', - operators: [emailOperator, metaTagsOperator], + operators: [ + urlInMessageOperator, + metaTagsOperator + ], metaTags: { title: 'subject', description: 'body' @@ -258,7 +303,9 @@ export const shareButtonsProp: IShareButtons = { icon: 'fas fa-print', color: '#32a1a3', share: { - operators: [printOperator] + operators: [ + printOperator + ] } } }; diff --git a/lib/core/src/share.service.ts b/lib/core/src/share.service.ts index 5b2746e7..e4e39eca 100644 --- a/lib/core/src/share.service.ts +++ b/lib/core/src/share.service.ts @@ -3,24 +3,25 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { IShareButton, IShareButtons, ShareButtonsConfig } from './share.models'; import { CONFIG } from './share.tokens'; import { shareButtonsProp } from './share.prop'; -import { mergeDeep } from './utils'; +import { getMetaTagContent, mergeDeep } from './utils'; @Injectable() export class ShareButtons { - config = { + config: ShareButtonsConfig = { prop: shareButtonsProp, options: { theme: 'default', include: [], exclude: [], size: 0, - title: null, - image: null, - description: null, - tags: null, + url: getMetaTagContent('og:url'), + title: getMetaTagContent('og:title'), + description: getMetaTagContent('og:description'), + image: getMetaTagContent('og:image'), + tags: undefined, gaTracking: false, - twitterAccount: null, + twitterAccount: undefined, windowWidth: 800, windowHeight: 500 } @@ -49,6 +50,10 @@ export class ShareButtons { return `width=${this.config.options.windowWidth}, height=${this.config.options.windowHeight}`; } + get url() { + return this.config.options.url; + } + get title() { return this.config.options.title; } diff --git a/lib/core/src/utils.ts b/lib/core/src/utils.ts index 5c9e71a9..ae02bff0 100644 --- a/lib/core/src/utils.ts +++ b/lib/core/src/utils.ts @@ -69,3 +69,37 @@ export function copyToClipboard(text: string, ios: boolean) { resolve(); }); } + +/** Get meta tag content */ +export function getMetaTagContent(key: string) { + const metaTag: Element = document.querySelector(`meta[property="${key}"]`); + return metaTag ? metaTag.getAttribute('content') : undefined; +} + +/** Detect operating system 'ios', 'android', or 'desktop' */ +export function getOS(window: any, navigator: Navigator) { + const userAgent = navigator.userAgent || navigator.vendor || window.opera; + + if (/android/i.test(userAgent)) { + return 'android'; + } + + if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { + return 'ios'; + } + return 'desktop'; +} + + +/** Returns a valid URL or falls back to current URL */ +export function getValidUrl(url: string, fallbackUrl: string) { + + if (url) { + const r = /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; + if (r.test(url)) { + return url; + } + console.warn(`[ShareButtons]: Sharing link '${url}' is invalid!`); + } + return fallbackUrl; +}