Files
calendar/packages/embeds/embed-core/src/utils.ts
T
9bc703a437 feat: Embed - Show skeleton loader when pageType is provided (#20646)
* mvp done

* wip

* fix ts errors and other code improvements

* fix ts errors

* ensure mobile layout support

* Make skeleton responsive on screen resize

* refactor

* Add test for EmbedElement

* make skeleton closer to pixel perfect

* Address PR feedback

* Remove PR_TODO

---------

Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: amrit <iamamrit27@gmail.com>
2025-04-16 13:40:51 +00:00

101 lines
3.1 KiB
TypeScript

import type { KnownConfig } from "./types";
export const getErrorString = (errorCode: string | undefined) => {
if (errorCode === "404") {
return `Error Code: 404. Cal Link seems to be wrong.`;
} else {
return `Error Code: ${errorCode}. Something went wrong.`;
}
};
/**
* An alternative to Object.fromEntries that allows duplicate keys and converts the values corresponding to them in an array
*
* NOTE: This is a duplicate of the function in @calcom/lib/hooks/useRouterQuery.ts. It has to be here because embed is published to npm and shouldn't refer to any private package
*/
export function fromEntriesWithDuplicateKeys(entries: IterableIterator<[string, string]> | null) {
const result: Record<string, string | string[]> = {};
if (entries === null) {
return result;
}
// Consider setting atleast ES2015 as target
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
for (const [key, value] of entries) {
if (result.hasOwnProperty(key)) {
let currentValue = result[key];
if (!Array.isArray(currentValue)) {
currentValue = [currentValue];
}
currentValue.push(value);
result[key] = currentValue;
} else {
result[key] = value;
}
}
return result;
}
function listKnownConfigProps() {
const knownConfigProps: (keyof KnownConfig)[] = [
"flag.coep",
"layout",
"ui.color-scheme",
"theme",
"cal.embed.pageType",
];
return knownConfigProps;
}
export function getConfigProp<TProp extends keyof KnownConfig>(config: KnownConfig, prop: TProp) {
const knownConfigProps = listKnownConfigProps();
if (!knownConfigProps.includes(prop)) {
console.warn(`Not reading unknown config prop: ${prop}`);
return null;
}
return config[prop] ?? null;
}
export function getQueryParamProvidedByConfig<TProp extends keyof KnownConfig>(
queryParam: TProp
): NonNullable<KnownConfig[TProp]> | null {
const knownConfigProps = listKnownConfigProps();
if (!knownConfigProps.includes(queryParam)) {
console.warn(`Not reading unknown config prop from query: ${queryParam}`);
return null;
}
const url = new URL(document.URL);
const value = url.searchParams.get(queryParam);
if (typeof value === "string") {
return value as NonNullable<KnownConfig[TProp]>;
}
return value;
}
function hyphenate(str: string): string {
return str.charAt(0).toLowerCase() + str.slice(1).replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
}
/**
* Escapes special characters in HTML attribute values to prevent XSS
* @param value The string to escape
* @returns Escaped string safe for use in HTML attributes
*/
function escapeHtmlAttribute(value: string): string {
return value
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
export function generateDataAttributes(props: Record<string, string | null | undefined>): string {
return Object.entries(props)
.filter((pair): pair is [string, string] => !!pair[1])
.map(([key, value]) => `data-${hyphenate(key)}="${escapeHtmlAttribute(value)}"`)
.join(" ");
}