UNPKG

@gravityforms/utils

Version:
266 lines (256 loc) 10 kB
const PLATFORM_CONFIGS = { calendly: { name: 'Calendly', urlRegexes: [ /^(?:https?:\/\/)?(?:www\.)?calendly\.com\/([a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)?)(?:\/?(?:\?.+)?)?$/i, ], handleValidationRegex: /^[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)?$/, urlTemplate: ( identifier ) => `https://calendly.com/${ identifier }`, normalizeIdentifier: ( id ) => id.toLowerCase(), }, youtube: { name: 'YouTube', urlRegexes: [ /^(?:https?:\/\/)?(?:www\.)?youtube\.com\/(@[a-zA-Z0-9_.-]+)(?:\/?(?:\?.+)?)?$/i, // Handles @username /^(?:https?:\/\/)?(?:www\.)?youtube\.com\/(channel\/UC[a-zA-Z0-9_-]+)(?:\/?(?:\?.+)?)?$/i, // Channel IDs UC... /^(?:https?:\/\/)?(?:www\.)?youtube\.com\/(c\/[a-zA-Z0-9_.-]+)(?:\/?(?:\?.+)?)?$/i, // Custom URLs c/something /^(?:https?:\/\/)?(?:www\.)?youtube\.com\/(user\/[a-zA-Z0-9_.-]+)(?:\/?(?:\?.+)?)?$/i, // Legacy user URLs user/something ], // For direct handle input, we'll primarily support the @handle format or a plain username which we'll prefix with @ handleValidationRegex: /^@?[a-zA-Z0-9_.-]+$/, urlTemplate: ( identifier ) => `https://youtube.com/${ identifier }`, normalizeIdentifier: ( id ) => { if ( id.startsWith( '@' ) || id.startsWith( 'channel/' ) || id.startsWith( 'c/' ) || id.startsWith( 'user/' ) || /^UC/.test( id ) ) { return id; } return '@' + id; }, }, wordpress: { name: 'WordPress', urlRegexes: [ /^(?:https?:\/\/)?profiles\.wordpress\.org\/([a-zA-Z0-9_.-]+)\/?(?:\/.*)?$/i, ], handleValidationRegex: /^[a-zA-Z0-9_.-]+$/, urlTemplate: ( identifier ) => `https://profiles.wordpress.org/${ identifier.toLowerCase() }/`, normalizeIdentifier: ( id ) => id.toLowerCase(), }, xitter: { name: 'X', urlRegexes: [ /^(?:https?:\/\/)?(?:www\.)?(?:twitter|x)\.com\/([a-zA-Z0-9_]{1,15})(?:\/?(?:\?.+)?)?$/i, ], handleValidationRegex: /^[a-zA-Z0-9_]{1,15}$/, urlTemplate: ( identifier ) => `https://x.com/${ identifier }`, normalizeIdentifier: ( id ) => id.replace( /^@/, '' ), }, facebook: { name: 'Facebook', urlRegexes: [ /^(?:https?:\/\/)?(?:www\.)?(?:facebook|fb)\.com\/(?:profile\.php\?id=)?(\d+)(?:&.+|\/?)$/i, /^(?:https?:\/\/)?(?:www\.)?(?:facebook|fb)\.com\/(?!pages\/|groups\/|events\/|photo(?:s|\.php)?|permalink\.php|story\.php|watch\/?|live\/?|video(?:s|\.php)?|media\/?|messages\/|gaming\/|notes\/|sharer(?:\.php)?|login\.php|help\/|legal\/|marketplace\/|ads\/|posts\/|hashtag\/)([a-zA-Z0-9._-]+)(?:\/?(?:\?.*)?)?$/i, ], handleValidationRegex: /^(?:[a-zA-Z0-9._-]+|\d+)$/, urlTemplate: ( identifier, originalInputUrl ) => { if ( /^\d+$/.test( identifier ) && originalInputUrl && /profile\.php\?id=/.test( originalInputUrl ) ) { return `https://facebook.com/profile.php?id=${ identifier }`; } return `https://facebook.com/${ identifier }`; }, normalizeIdentifier: ( id ) => id.replace( /^@/, '' ), }, bluesky: { name: 'Bluesky', urlRegexes: [ /^(?:https?:\/\/)?(?:www\.)?bsky\.app\/profile\/([a-zA-Z0-9.-]+[a-zA-Z0-9])(?:\/?(?:\?.+)?)?$/i, ], handleValidationRegex: /^[a-zA-Z0-9.-]+[a-zA-Z0-9]$/, urlTemplate: ( identifier ) => `https://bsky.app/profile/${ identifier }`, normalizeIdentifier: ( id ) => id.replace( /^@/, '' ), finalizeIdentifier: ( id, wasHandleInputOnly ) => { if ( wasHandleInputOnly && id && ! id.includes( '.' ) ) { return `${ id }.bsky.social`; } return id; }, }, tiktok: { name: 'TikTok', urlRegexes: [ /^(?:https?:\/\/)?(?:www\.)?tiktok\.com\/@([a-zA-Z0-9_.]+)(?:\/?(?:\?.+)?)?$/i, ], handleValidationRegex: /^[a-zA-Z0-9_.]+$/, urlTemplate: ( identifier ) => `https://tiktok.com/@${ identifier }`, normalizeIdentifier: ( id ) => id.replace( /^@/, '' ), }, whatsapp: { name: 'WhatsApp', urlRegexes: [ /^(?:https?:\/\/)?(?:wa\.me\/|api\.whatsapp\.com\/send\/?\?phone=)(\+?\d+[\d\s()-]*\d)(?:\/?(?:\?.+)?)?$/i, ], handleValidationRegex: /^\+?\d+[\d\s()-]*\d$/, urlTemplate: ( identifier ) => `https://wa.me/${ identifier.replace( /\D/g, '' ) }`, normalizeIdentifier: ( id ) => id.replace( /\D/g, '' ), }, threads: { name: 'Threads', urlRegexes: [ /^(?:https?:\/\/)?(?:www\.)?threads\.net\/@([a-zA-Z0-9_.]+)(?:\/?(?:\?.+)?)?$/i, ], handleValidationRegex: /^[a-zA-Z0-9_.]+$/, urlTemplate: ( identifier ) => `https://threads.net/@${ identifier }`, normalizeIdentifier: ( id ) => id.replace( /^@/, '' ), }, linkedin: { name: 'LinkedIn', urlRegexes: [ /^(?:https?:\/\/)?(?:www\.)?linkedin\.com\/in\/([a-zA-Z0-9_-]+)(?:\/?(?:\?.+)?)?$/i, /^(?:https?:\/\/)?(?:www\.)?linkedin\.com\/company\/([a-zA-Z0-9_-]+)(?:\/?(?:\?.+)?)?$/i, /^(?:https?:\/\/)?(?:www\.)?linkedin\.com\/school\/([a-zA-Z0-9_-]+)(?:\/?(?:\?.+)?)?$/i, /^(?:https?:\/\/)?(?:www\.)?linkedin\.com\/showcase\/([a-zA-Z0-9_-]+)(?:\/?(?:\?.+)?)?$/i, /^(?:https?:\/\/)?(?:www\.)?linkedin\.com\/pub\/([a-zA-Z0-9_-]+(?:-[a-zA-Z0-9_-]+)*)(?:\/[a-zA-Z0-9]+){0,3}\/?(?:\?.+)?$/i, ], handleValidationRegex: /^[a-zA-Z0-9_-]+$/, urlTemplate: ( identifier, originalInputUrl ) => { const cleanId = identifier.split( '/' )[ 0 ]; if ( originalInputUrl ) { if ( originalInputUrl.includes( '/company/' ) ) { return `https://linkedin.com/company/${ cleanId }`; } if ( originalInputUrl.includes( '/school/' ) ) { return `https://linkedin.com/school/${ cleanId }`; } if ( originalInputUrl.includes( '/showcase/' ) ) { return `https://linkedin.com/showcase/${ cleanId }`; } if ( originalInputUrl.includes( '/pub/' ) ) { return `https://linkedin.com/pub/${ cleanId }`; } } return `https://linkedin.com/in/${ cleanId }`; }, normalizeIdentifier: ( id ) => id.replace( /^@/, '' ), }, savvycal: { name: 'SavvyCal', urlRegexes: [ /^(?:https?:\/\/)?(?:www\.)?savvycal\.com\/([a-zA-Z0-9_-]+)(?:\/[a-zA-Z0-9_-]+)?(?:\/?(?:\?.+)?)?$/i, ], handleValidationRegex: /^[a-zA-Z0-9_-]+$/, urlTemplate: ( identifier ) => `https://savvycal.com/${ identifier.split( '/' )[ 0 ] }`, normalizeIdentifier: ( id ) => id.replace( /^@/, '' ), }, github: { name: 'GitHub', urlRegexes: [ /^(?:https?:\/\/)?(?:www\.)?github\.com\/([a-zA-Z0-9_-]+)(?:\/?(?:\?.+)?)?$/i, ], handleValidationRegex: /^[a-zA-Z0-9_-]+$/, urlTemplate: ( identifier ) => `https://github.com/${ identifier }`, normalizeIdentifier: ( id ) => id.replace( /^@/, '' ), }, instagram: { name: 'Instagram', urlRegexes: [ /^(?:https?:\/\/)?(?:www\.)?instagram\.com\/([a-zA-Z0-9_.]+)(?:\/?(?:\?.+)?)?$/i, ], handleValidationRegex: /^[a-zA-Z0-9_.]+$/, urlTemplate: ( identifier ) => `https://instagram.com/${ identifier }`, normalizeIdentifier: ( id ) => id.replace( /^@/, '' ), }, }; const SUPPORTED_PLATFORM_KEYS = Object.freeze( [ 'calendly', 'youtube', 'wordpress', 'xitter', 'facebook', 'bluesky', 'tiktok', 'whatsapp', 'threads', 'linkedin', 'savvycal', 'github', 'instagram', ] ); /** * @module parseSocial * @description A module that parses a string that may be a URL or a social media handle. * It returns a url, platform key, and an identifier, plus a valid key that is true or false. * * @since 4.0.6 * * @param {string} input The input string that may be a URL or a social media handle. Defaults to an empty string. * @param {string} [platformHint] Optional. The key of the platform (e.g., 'xitter', 'facebook') to help identify the input if it's a handle. * * @return {object} An object with the following properties, url, identifier, platform, and valid. * * @example * import { parseSocial } from '@gravityforms/utils'; * * // Example 1: Parsing a URL * const twitterProfile = parseSocial('https://twitter.com/elonmusk'); * // twitterProfile: { url: 'https://x.com/elonmusk', identifier: 'elonmusk', platform: 'xitter', valid: true } * * // Example 2: Parsing a handle with a hint * const facebookHandle = parseSocial('zuck', 'facebook'); * // facebookHandle: { url: 'https://facebook.com/zuck', identifier: 'zuck', platform: 'facebook', valid: true } * * // Example 3: Parsing an ambiguous handle without a hint (likely invalid) * const ambiguousHandle = parseSocial('username123'); * // ambiguousHandle: { url: '', identifier: '', platform: '', valid: false } * * // Example 4: Invalid input * const invalidInput = parseSocial('not a url or handle', 'randomPlatform'); * // invalidInput: { url: '', identifier: '', platform: '', valid: false } * */ export default function parseSocial( input = '', platformHint = '' ) { const result = { url: '', identifier: '', platform: '', valid: false, }; if ( ! input || typeof input !== 'string' ) { return result; } const trimmedInput = input.trim(); if ( ! trimmedInput ) { return result; } const hintKey = platformHint ? platformHint.toLowerCase() : ''; for ( const key of SUPPORTED_PLATFORM_KEYS ) { const config = PLATFORM_CONFIGS[ key ]; for ( const regex of config.urlRegexes ) { const match = trimmedInput.match( regex ); if ( match && match[ 1 ] ) { const rawIdentifier = match[ 1 ]; result.identifier = config.normalizeIdentifier( rawIdentifier ); if ( config.finalizeIdentifier ) { result.identifier = config.finalizeIdentifier( result.identifier, false ); } result.url = config.urlTemplate( result.identifier, trimmedInput ); result.platform = key; result.valid = true; return result; } } } if ( hintKey && SUPPORTED_PLATFORM_KEYS.includes( hintKey ) ) { const config = PLATFORM_CONFIGS[ hintKey ]; const normalizedInputAsHandle = config.normalizeIdentifier( trimmedInput ); if ( config.handleValidationRegex && config.handleValidationRegex.test( normalizedInputAsHandle ) ) { result.identifier = normalizedInputAsHandle; if ( config.finalizeIdentifier ) { result.identifier = config.finalizeIdentifier( result.identifier, true ); } result.url = config.urlTemplate( result.identifier, null ); result.platform = hintKey; result.valid = true; return result; } } return result; }