-
Notifications
You must be signed in to change notification settings - Fork 4
V2 whatsapp
#437
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
V2 whatsapp
#437
Changes from all commits
170f71d
c21a863
a17dce4
6179647
67a6da7
b833430
abb6a54
ef7322d
588fecd
184d3b9
808496a
a0fe601
092e4a3
717ff70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { type ElementHandle, expect, test } from '@playwright/test'; | ||
|
||
import { waitAttributeLoaded } from './utils'; | ||
|
||
let whatsappButtons: ElementHandle<HTMLAnchorElement>[] = []; | ||
|
||
test.beforeEach(async ({ page }) => { | ||
await page.goto('https://dev-attributes-whatsapp.webflow.io'); | ||
await waitAttributeLoaded(page, 'whatsapp'); | ||
|
||
// Get all whatsapp buttons | ||
whatsappButtons = (await page.$$('a[fs-whatsapp-element="button"]')) as ElementHandle<HTMLAnchorElement>[]; | ||
}); | ||
|
||
test.describe('whatsapp', () => { | ||
// Static attributes test | ||
test('Validates static attributes for whatsapp anchor tags', async () => { | ||
// Loop through all whatsapp buttons and validate the attributes | ||
for (const button of whatsappButtons) { | ||
const { phoneNumber, message } = await getAttributes(button); | ||
|
||
const expectedPhoneNumber = await button.getAttribute('fs-whatsapp-phone'); | ||
const expectedMessage = await button.getAttribute('fs-whatsapp-message'); | ||
|
||
// Assert that the attributes are not empty | ||
if (expectedPhoneNumber && expectedMessage) { | ||
expect(phoneNumber).toBe(expectedPhoneNumber); | ||
expect(message).toBe(expectedMessage); | ||
} | ||
} | ||
}); | ||
|
||
// Dynamic attributes test | ||
test('Validates dynamic attributes for whatsapp anchor tags', async () => { | ||
// Loop through all whatsapp buttons and validate the attributes | ||
for (const button of whatsappButtons) { | ||
// Get the attributes from the whatsapp button | ||
const { phoneNumber, message } = await getAttributes(button); | ||
|
||
const phoneElement: ElementHandle<Element> | null = await button.$('div[fs-whatsapp-element="phone"]'); | ||
const messageElement: ElementHandle<Element> | null = await button.$('div[fs-whatsapp-element="message"]'); | ||
|
||
// Assert that the attributes are not empty | ||
if (phoneElement && messageElement) { | ||
const dynamicMessage = (await messageElement.textContent()) ?? ''; | ||
expect(dynamicMessage).toBeTruthy(); | ||
|
||
const dynamicPhone = (await phoneElement.textContent()) ?? ''; | ||
expect(dynamicPhone).toBeTruthy(); | ||
|
||
expect(dynamicPhone).toBe(phoneNumber); | ||
expect(dynamicMessage).toBe(message); | ||
} | ||
} | ||
}); | ||
}); | ||
|
||
/** | ||
* Helper function that gets the attributes from a whatsapp button | ||
* @param button The whatsapp button | ||
* @returns An object containing the phone number and message | ||
*/ | ||
async function getAttributes(button: ElementHandle<HTMLAnchorElement>) { | ||
const href = (await button.getAttribute('href')) ?? ''; | ||
expect(href).toBeTruthy(); | ||
|
||
// assert that the href value is a valid whatsapp link | ||
expect(href).toContain('https://wa.me/'); | ||
|
||
const url = new URL(href); | ||
const [, phoneNumber] = url.pathname.split('/'); | ||
const message = url.searchParams.get('text'); | ||
|
||
return { phoneNumber, message }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# `whatsapp` Attribute | ||
|
||
Adds a simple url to an anchor element that creates a new whatsapp chat in a new tab when clicked. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"name": "@finsweet/attributes-whatsapp", | ||
"version": "1.0.0", | ||
"description": "Adds a simple url to an anchor element that creates a new whatsapp chat in a new tab when clicked.", | ||
"private": true, | ||
"type": "module", | ||
"types": "src/index.ts", | ||
"exports": { | ||
".": { | ||
"types": "./src/index.ts", | ||
"import": "./src/index.ts" | ||
} | ||
}, | ||
"dependencies": { | ||
"@finsweet/attributes-utils": "workspace:*" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { formatPhoneNumber, formatUrl, getAttribute, getInstanceIndex, queryElement, WHATSAPP_BASE_URL } from './utils'; | ||
|
||
/** | ||
* Initialize a WhatsApp button element instance | ||
* @param buttonElement The button element to initialize | ||
* @returns The initialized button element | ||
*/ | ||
export const initWhatsappInstance = (buttonElement: Element) => { | ||
// static | ||
let phone = getAttribute(buttonElement, 'phone'); | ||
let message = getAttribute(buttonElement, 'message'); | ||
|
||
// dynamic | ||
if (!message && !phone) { | ||
const instanceIndex = getInstanceIndex(buttonElement); | ||
// phone | ||
phone = queryElement('phone', { instanceIndex, scope: buttonElement })?.textContent ?? ''; | ||
// message | ||
message = queryElement('message', { instanceIndex, scope: buttonElement })?.textContent ?? ''; | ||
} | ||
|
||
if (!phone || !message) return; //todo: throw new Error('Missing phone or message attribute'); | ||
|
||
// format phone number and url | ||
phone = formatPhoneNumber(phone); | ||
const url = formatUrl(`${WHATSAPP_BASE_URL}/${phone}`, { text: message.trim() }); | ||
|
||
buttonElement.setAttribute('href', url); | ||
buttonElement.setAttribute('target', '_blank'); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { version } from '../package.json'; | ||
export { init } from './init'; | ||
export { ELEMENTS, SETTINGS } from './utils'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { type FsAttributeInit, isNotEmpty, waitAttributeLoaded, waitWebflowReady } from '@finsweet/attributes-utils'; | ||
|
||
import { initWhatsappInstance } from './factory'; | ||
import { queryAllElements } from './utils'; | ||
|
||
/** | ||
* Inits the attribute. | ||
*/ | ||
export const init: FsAttributeInit = async () => { | ||
await waitWebflowReady(); | ||
|
||
// wait for cms to load if it's present | ||
await waitAttributeLoaded('cmsload'); | ||
|
||
// Get all whatsapp buttons on the page | ||
const buttonElements = queryAllElements('button'); | ||
|
||
// Create instances of each whatsapp button | ||
buttonElements.map(initWhatsappInstance).filter(isNotEmpty); | ||
|
||
return { | ||
result: buttonElements, | ||
destroy() { | ||
return; | ||
}, | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { type AttributeElements, type AttributeSettings } from '@finsweet/attributes-utils'; | ||
|
||
export const ELEMENTS = [ | ||
/** | ||
* Defines a button element. | ||
*/ | ||
'button', | ||
/** | ||
* Defines a phone element | ||
*/ | ||
'phone', | ||
/** | ||
* Defines a message element. | ||
*/ | ||
'message', | ||
] as const satisfies AttributeElements; | ||
|
||
export const SETTINGS = { | ||
/** | ||
* Defines the WhatsApp phone number. | ||
*/ | ||
phone: { | ||
key: 'phone', | ||
}, | ||
/** | ||
* Defines the WhatsApp message. | ||
*/ | ||
message: { | ||
key: 'message', | ||
}, | ||
} as const satisfies AttributeSettings; | ||
|
||
/** | ||
* Defines the WhatsApp base URL. | ||
*/ | ||
export const WHATSAPP_BASE_URL = 'https://wa.me'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/** | ||
* Formats a url with the given params as query params | ||
* @param url The url to format | ||
* @param params The params to append to the url | ||
* @returns The formatted url | ||
*/ | ||
export const formatUrl = (url: string, params: { [key: string]: string }): string => { | ||
const urlObject = new URL(url); | ||
for (const [key, value] of Object.entries(params)) { | ||
urlObject.searchParams.append(key, value); | ||
} | ||
|
||
return urlObject.href; | ||
}; | ||
|
||
/** | ||
* Formats a phone number by removing all non-numeric characters and appending + | ||
* @param phoneNumber The phone number to format | ||
* @returns The formatted phone number | ||
*/ | ||
export const formatPhoneNumber = (phoneNumber: string): string => { | ||
return '+' + phoneNumber.replace(/[^0-9]/g, ''); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './constants'; | ||
export * from './helpers'; | ||
export * from './schema'; | ||
export * from './selectors'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import type { Schema, SchemaSettings } from '@finsweet/attributes-utils'; | ||
|
||
import { ELEMENTS, SETTINGS } from '.'; | ||
|
||
const SCHEMA_SETTINGS: SchemaSettings<typeof SETTINGS> = { | ||
phone: { | ||
...SETTINGS.phone, | ||
name: 'Phone', | ||
description: 'The phone number', | ||
type: 'text', | ||
}, | ||
message: { | ||
...SETTINGS.message, | ||
name: 'Message', | ||
description: 'The message to send', | ||
type: 'text', | ||
}, | ||
}; | ||
|
||
export const schema: Schema<typeof ELEMENTS, typeof SETTINGS> = { | ||
groups: [], | ||
elements: [ | ||
{ | ||
key: 'button', | ||
name: 'Button', | ||
description: 'A button to open WhatsApp', | ||
allowedTypes: ['Link'], | ||
settings: [SCHEMA_SETTINGS.phone, SCHEMA_SETTINGS.message], | ||
|
||
}, | ||
{ | ||
key: 'phone', | ||
name: 'Phone', | ||
description: 'A phone number to open WhatsApp', | ||
allowedTypes: ['Block'], | ||
}, | ||
{ | ||
key: 'message', | ||
name: 'Message', | ||
description: 'A message to open WhatsApp', | ||
allowedTypes: ['Block'], | ||
}, | ||
], | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { generateSelectors, WHATSAPP_ATTRIBUTE } from '@finsweet/attributes-utils'; | ||
|
||
import { ELEMENTS, SETTINGS } from '.'; | ||
|
||
export const { getAttribute, hasAttributeValue, queryAllElements, getInstanceIndex, queryElement } = generateSelectors( | ||
WHATSAPP_ATTRIBUTE, | ||
ELEMENTS, | ||
SETTINGS | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "../../tsconfig.json" | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.