"],
@@ -9,29 +10,8 @@ export default defineContentScript({
return;
}
- // Initialize Gmail integration if on Gmail
- // Wrapped in try-catch to prevent breaking Gmail if anything fails
- if (window.location.hostname === "mail.google.com") {
- try {
- initGmailIntegration();
- console.log("Cal.com: Gmail integration initialized successfully");
- } catch (error) {
- // Fail silently - don't break Gmail UI
- console.error("Cal.com: Failed to initialize Gmail integration");
- }
- }
-
- // Initialize Google Calendar integration if on Google Calendar
- // Wrapped in try-catch to prevent breaking Google Calendar if anything fails
- if (window.location.hostname === "calendar.google.com") {
- try {
- initGoogleCalendarIntegration();
- console.log("Cal.com: Google Calendar integration initialized successfully");
- } catch (error) {
- // Fail silently - don't break Google Calendar UI
- console.error("Cal.com: Failed to initialize Google Calendar integration");
- }
- }
+ // NOTE: Integration initialization moved to end of main() to ensure functions are defined
+ // See initializeIntegrations() call at the bottom of main()
const sessionToken = generateSecureToken();
let iframeSessionValidated = false;
@@ -1436,6 +1416,637 @@ export default defineContentScript({
}, 1000);
// ========== Google Calendar Chip Integration ==========
+ // TODO: Implement Google Calendar chip integration
+
+ // Shared functions (reused from Gmail integration)
+ function createTooltip(text: string, buttonElement: HTMLElement) {
+ const tooltip = document.createElement("div");
+ tooltip.className = "cal-tooltip";
+ tooltip.style.cssText = `
+ position: fixed;
+ background-color: #1a1a1a;
+ color: white;
+ padding: 4px 8px;
+ border-radius: 6px;
+ font-size: 12px;
+ font-weight: 600;
+ white-space: nowrap;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ z-index: 10002;
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ display: none;
+ `;
+ tooltip.textContent = text;
+ document.body.appendChild(tooltip);
+
+ // Position tooltip on hover
+ const updatePosition = () => {
+ const rect = buttonElement.getBoundingClientRect();
+ tooltip.style.left = `${rect.left + rect.width / 2}px`;
+ tooltip.style.top = `${rect.top - 8}px`;
+ tooltip.style.transform = "translate(-50%, -100%)";
+ };
+
+ buttonElement.addEventListener("mouseenter", () => {
+ updatePosition();
+ tooltip.style.display = "block";
+ tooltip.style.opacity = "1";
+ });
+
+ buttonElement.addEventListener("mouseleave", () => {
+ tooltip.style.opacity = "0";
+ setTimeout(() => {
+ if (tooltip.style.opacity === "0") {
+ tooltip.style.display = "none";
+ }
+ }, 150);
+ });
+
+ return tooltip;
+ }
+
+ function openCalSidebar() {
+ // Open Cal.com sidebar or quick schedule flow
+ if (isClosed) {
+ // Trigger sidebar open
+ chrome.runtime.sendMessage({ action: "icon-clicked" });
+ } else {
+ // Toggle sidebar visibility
+ isVisible = !isVisible;
+ if (isVisible) {
+ sidebarContainer.style.transform = "translateX(0)";
+ } else {
+ sidebarContainer.style.transform = "translateX(100%)";
+ }
+ }
+ }
+
+ async function fetchEventTypes(menu: HTMLElement, tooltipsToCleanup: HTMLElement[]) {
+ try {
+ // Check cache first
+ const now = Date.now();
+ const isCacheValid =
+ eventTypesCache && cacheTimestamp && now - cacheTimestamp < CACHE_DURATION;
+
+ let eventTypes: any[] = [];
+
+ if (isCacheValid) {
+ // Use cached data
+ eventTypes = eventTypesCache!;
+ } else {
+ // Check if extension context is valid
+ if (!chrome.runtime?.id) {
+ throw new Error("Extension context invalidated. Please reload the page.");
+ }
+
+ // Fetch fresh data from background script
+ const response = await new Promise((resolve, reject) => {
+ try {
+ chrome.runtime.sendMessage({ action: "fetch-event-types" }, (response) => {
+ if (chrome.runtime.lastError) {
+ reject(new Error(chrome.runtime.lastError.message));
+ } else if (response && response.error) {
+ reject(new Error(response.error));
+ } else {
+ resolve(response);
+ }
+ });
+ } catch (err) {
+ reject(err);
+ }
+ });
+
+ if (response && (response as any).data) {
+ eventTypes = (response as any).data;
+ } else if (Array.isArray(response)) {
+ eventTypes = response;
+ } else {
+ eventTypes = [];
+ }
+
+ // Ensure eventTypes is an array
+ if (!Array.isArray(eventTypes)) {
+ eventTypes = [];
+ }
+
+ // Update cache
+ eventTypesCache = eventTypes;
+ cacheTimestamp = now;
+ }
+
+ // Clear loading state
+ menu.innerHTML = "";
+
+ if (eventTypes.length === 0) {
+ menu.innerHTML = `
+
+ No event types found
+
+ `;
+ return;
+ }
+
+ // Add event types - with additional safety checks
+ try {
+ eventTypes.forEach((eventType, index) => {
+ // Validate eventType object
+ if (!eventType || typeof eventType !== "object") {
+ return;
+ }
+
+ const title = eventType.title || "Untitled Event";
+ const length =
+ eventType.lengthInMinutes || eventType.length || eventType.duration || 30;
+ const description = eventType.description || "";
+
+ const menuItem = document.createElement("div");
+ menuItem.style.cssText = `
+ padding: 14px 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ transition: background-color 0.1s ease;
+ border-bottom: ${index < eventTypes.length - 1 ? "1px solid #E5E5EA" : "none"};
+ `;
+
+ // Create content wrapper
+ const contentWrapper = document.createElement("div");
+ contentWrapper.style.cssText = `
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ cursor: pointer;
+ margin-right: 12px;
+ position: relative;
+ min-width: 0;
+ overflow: hidden;
+ `;
+
+ contentWrapper.innerHTML = `
+
+ ${title}
+
+
+
+ ${length}min
+
+ ${
+ description
+ ? `${description}`
+ : ""
+ }
+
+ `;
+
+ // Create tooltip for content wrapper
+ const contentTooltip = document.createElement("div");
+ contentTooltip.className = "cal-tooltip";
+ contentTooltip.style.cssText = `
+ position: fixed;
+ background-color: #1a1a1a;
+ color: white;
+ padding: 4px 8px;
+ border-radius: 6px;
+ font-size: 12px;
+ font-weight: 600;
+ white-space: nowrap;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.15s ease;
+ z-index: 10002;
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ display: none;
+ `;
+ contentTooltip.textContent = "Insert link";
+ document.body.appendChild(contentTooltip);
+ tooltipsToCleanup.push(contentTooltip);
+
+ // Show/hide tooltip
+ contentWrapper.addEventListener("mouseenter", (e) => {
+ const rect = contentWrapper.getBoundingClientRect();
+ contentTooltip.style.left = `${rect.left + rect.width / 2 + 80}px`;
+ contentTooltip.style.top = `${rect.top + 35}px`;
+ contentTooltip.style.transform = "translate(-50%, -100%)";
+ contentTooltip.style.display = "block";
+ contentTooltip.style.opacity = "1";
+ });
+ contentWrapper.addEventListener("mouseleave", () => {
+ contentTooltip.style.opacity = "0";
+ setTimeout(() => {
+ if (contentTooltip.style.opacity === "0") {
+ contentTooltip.style.display = "none";
+ }
+ }, 150);
+ });
+
+ // Add click handler to content wrapper to insert link
+ contentWrapper.addEventListener("click", (e) => {
+ e.stopPropagation();
+ // Remove tooltip
+ contentTooltip.remove();
+ menu.remove();
+ // Insert link into message text
+ insertEventTypeLink(eventType);
+ });
+
+ // Create buttons container
+ const buttonsContainer = document.createElement("div");
+ buttonsContainer.style.cssText = `
+ display: flex;
+ align-items: center;
+ gap: 0;
+ flex-shrink: 0;
+ `;
+
+ // Preview button
+ const previewBtn = document.createElement("button");
+ previewBtn.innerHTML = `
+
+ `;
+ previewBtn.style.cssText = `
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid #e5e5ea;
+ border-right: none;
+ border-radius: 6px 0 0 6px;
+ background: white;
+ cursor: pointer;
+ transition: background-color 0.1s ease;
+ padding: 0;
+ position: relative;
+ `;
+
+ // Create tooltip for preview button
+ const previewTooltip = createTooltip("Preview", previewBtn);
+ tooltipsToCleanup.push(previewTooltip);
+
+ previewBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const bookingUrl = `https://cal.com/${
+ eventType.users?.[0]?.username || "user"
+ }/${eventType.slug}`;
+ window.open(bookingUrl, "_blank");
+ });
+ previewBtn.addEventListener("mouseenter", () => {
+ previewBtn.style.backgroundColor = "#f8f9fa";
+ });
+ previewBtn.addEventListener("mouseleave", () => {
+ previewBtn.style.backgroundColor = "white";
+ });
+
+ // Copy link button
+ const copyBtn = document.createElement("button");
+ copyBtn.innerHTML = `
+
+ `;
+ copyBtn.style.cssText = `
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid #e5e5ea;
+ border-right: none;
+ background: white;
+ cursor: pointer;
+ transition: background-color 0.1s ease;
+ padding: 0;
+ position: relative;
+ `;
+
+ // Create tooltip for copy button
+ const copyTooltip = createTooltip("Copy link", copyBtn);
+ tooltipsToCleanup.push(copyTooltip);
+
+ copyBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ // Copy to clipboard
+ const bookingUrl = `https://cal.com/${
+ eventType.users?.[0]?.username || "user"
+ }/${eventType.slug}`;
+ navigator.clipboard
+ .writeText(bookingUrl)
+ .then(() => {
+ showNotification("Link copied!", "success");
+ // Change icon to checkmark
+ copyBtn.innerHTML = `
+
+ `;
+ setTimeout(() => {
+ copyBtn.innerHTML = `
+
+ `;
+ }, 2000);
+ })
+ .catch(() => {
+ showNotification("Failed to copy link", "error");
+ });
+ });
+ copyBtn.addEventListener("mouseenter", () => {
+ copyBtn.style.backgroundColor = "#f8f9fa";
+ });
+ copyBtn.addEventListener("mouseleave", () => {
+ copyBtn.style.backgroundColor = "white";
+ });
+
+ // Edit button
+ const editBtn = document.createElement("button");
+ editBtn.innerHTML = `
+
+ `;
+ editBtn.style.cssText = `
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid #e5e5ea;
+ border-radius: 0 6px 6px 0;
+ background: white;
+ cursor: pointer;
+ transition: background-color 0.1s ease;
+ padding: 0;
+ position: relative;
+ `;
+
+ // Create tooltip for edit button
+ const editTooltip = createTooltip("Edit", editBtn);
+ tooltipsToCleanup.push(editTooltip);
+
+ editBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const editUrl = `https://app.cal.com/event-types/${eventType.id}`;
+ window.open(editUrl, "_blank");
+ });
+ editBtn.addEventListener("mouseenter", () => {
+ editBtn.style.backgroundColor = "#f8f9fa";
+ });
+ editBtn.addEventListener("mouseleave", () => {
+ editBtn.style.backgroundColor = "white";
+ });
+
+ // Assemble buttons
+ buttonsContainer.appendChild(previewBtn);
+ buttonsContainer.appendChild(copyBtn);
+ buttonsContainer.appendChild(editBtn);
+
+ // Hover effect for whole item
+ menuItem.addEventListener("mouseenter", () => {
+ menuItem.style.backgroundColor = "#f8f9fa";
+ });
+
+ menuItem.addEventListener("mouseleave", () => {
+ menuItem.style.backgroundColor = "transparent";
+ });
+
+ // Assemble menu item
+ menuItem.appendChild(contentWrapper);
+ menuItem.appendChild(buttonsContainer);
+
+ menu.appendChild(menuItem);
+ });
+ } catch (forEachError) {
+ menu.innerHTML = `
+
+ Error displaying event types
+
+ `;
+ }
+ } catch (error) {
+ const errorMessage = (error as Error).message || "";
+ const isAuthError =
+ errorMessage.includes("OAuth") ||
+ errorMessage.includes("access token") ||
+ errorMessage.includes("sign in") ||
+ errorMessage.includes("authentication");
+
+ if (isAuthError) {
+ // Not logged in - open sidebar directly
+ tooltipsToCleanup.forEach((tooltip) => tooltip.remove());
+ menu.remove();
+ openCalSidebar();
+ return;
+ } else {
+ // Show generic error for non-auth errors
+ menu.innerHTML = `
+
+
+
+
+
+ Something went wrong
+
+
+ ${errorMessage || "Failed to load event types"}
+
+
+
+ `;
+
+ const closeBtn = menu.querySelector(".cal-linkedin-close-btn") as HTMLButtonElement;
+ if (closeBtn) {
+ closeBtn.addEventListener("mouseenter", () => {
+ closeBtn.style.background = "#E5E7EB";
+ });
+ closeBtn.addEventListener("mouseleave", () => {
+ closeBtn.style.background = "#F3F4F6";
+ });
+ closeBtn.addEventListener("click", () => {
+ tooltipsToCleanup.forEach((tooltip) => tooltip.remove());
+ menu.remove();
+ });
+ }
+ }
+ }
+ }
+
+ function insertEventTypeLink(eventType: any) {
+ // Construct the Cal.com booking link
+ const bookingUrl = `https://cal.com/${eventType.users?.[0]?.username || "user"}/${
+ eventType.slug
+ }`;
+
+ // Try to insert at cursor position in the message field
+ const inserted = insertTextAtCursor(bookingUrl);
+
+ if (inserted) {
+ showNotification("Link inserted", "success");
+ } else {
+ // Fallback: copy to clipboard if insertion fails
+ navigator.clipboard
+ .writeText(bookingUrl)
+ .then(() => {
+ showNotification("Link copied!", "success");
+ })
+ .catch(() => {
+ showNotification("Failed to copy link", "error");
+ });
+ }
+ }
+
+ function insertTextAtCursor(text: string) {
+ // Find the active message input field
+ // LinkedIn uses contenteditable divs for message input
+ const messageInput =
+ document.querySelector(
+ 'div[contenteditable="true"][role="textbox"][aria-label*="message" i]'
+ ) ||
+ document.querySelector('div[contenteditable="true"][data-testid*="message"]') ||
+ document.querySelector('div[contenteditable="true"].msg-form__contenteditable');
+
+ if (!messageInput) {
+ return false;
+ }
+
+ // Focus the message field
+ (messageInput as HTMLElement).focus();
+
+ // Get the current selection
+ const selection = window.getSelection();
+ if (!selection || selection.rangeCount === 0) {
+ // If no selection, append to the end
+ const textNode = document.createTextNode(" " + text + " ");
+ messageInput.appendChild(textNode);
+
+ // Move cursor after inserted text
+ const range = document.createRange();
+ range.setStartAfter(textNode);
+ range.collapse(true);
+ selection?.removeAllRanges();
+ selection?.addRange(range);
+
+ return true;
+ }
+
+ // Insert at cursor position
+ const range = selection.getRangeAt(0);
+ range.deleteContents();
+
+ // Create a text node with the link (with spaces around it)
+ const textNode = document.createTextNode(" " + text + " ");
+ range.insertNode(textNode);
+
+ // Move cursor after inserted text
+ range.setStartAfter(textNode);
+ range.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ // Trigger input event so LinkedIn knows content changed
+ messageInput.dispatchEvent(new Event("input", { bubbles: true }));
+ messageInput.dispatchEvent(new Event("change", { bubbles: true }));
+
+ return true;
+ }
+
+ function showNotification(message: string, type: "success" | "error") {
+ const notification = document.createElement("div");
+ notification.style.cssText = `
+ position: fixed;
+ bottom: 80px;
+ right: 80px;
+ padding: 10px 12px;
+ background: ${type === "success" ? "#111827" : "#752522"};
+ color: white;
+ border: 1px solid #2b2b2b;
+ border-radius: 8px;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ z-index: 10000;
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.3), 0 2px 6px 2px rgba(0, 0, 0, 0.15);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ opacity: 0;
+ transform: translateY(10px);
+ transition: opacity 0.2s ease, transform 0.2s ease;
+ `;
+
+ // Add check icon for success
+ if (type === "success") {
+ const checkIcon = document.createElement("span");
+ checkIcon.innerHTML = `
+
+ `;
+ checkIcon.style.cssText = `
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ `;
+ notification.appendChild(checkIcon);
+ }
+
+ // Add message text
+ const messageText = document.createElement("span");
+ messageText.textContent = message;
+ notification.appendChild(messageText);
+
+ document.body.appendChild(notification);
+
+ // Trigger fade-in animation
+ requestAnimationFrame(() => {
+ notification.style.opacity = "1";
+ notification.style.transform = "translateY(0)";
+ });
+
+ // Fade out and remove after 3 seconds
+ setTimeout(() => {
+ notification.style.opacity = "0";
+ notification.style.transform = "translateY(10px)";
+ setTimeout(() => {
+ notification.remove();
+ }, 200);
+ }, 3000);
+ }
/**
* Generate HTML email embed for Cal.com booking
@@ -1536,8 +2147,8 @@ export default defineContentScript({
${datesHTML}
@@ -2458,8 +3069,8 @@ export default defineContentScript({
${
parsedData.slots.length
} time slot${parsedData.slots.length > 1 ? "s" : ""} • ${
- parsedData.detectedDuration
- }min each
+ parsedData.detectedDuration
+ }min each
`;
@@ -3177,6 +3788,39 @@ export default defineContentScript({
// Start watching for Google Calendar chips
watchForGoogleChips();
}
+
+ // ========== Initialize Integrations ==========
+ // These calls are at the end of main() to ensure all functions are defined before being called
+
+ // Initialize Gmail integration if on Gmail
+ if (window.location.hostname === "mail.google.com") {
+ try {
+ initGmailIntegration();
+ console.log("Cal.com: Gmail integration initialized successfully");
+ } catch (error) {
+ console.error("Cal.com: Failed to initialize Gmail integration", error);
+ }
+ }
+
+ // Initialize LinkedIn integration if on LinkedIn
+ if (window.location.hostname === "www.linkedin.com") {
+ try {
+ initLinkedInIntegration();
+ console.log("Cal.com: LinkedIn integration initialized successfully");
+ } catch (error) {
+ console.error("Cal.com: Failed to initialize LinkedIn integration", error);
+ }
+ }
+
+ // Initialize Google Calendar integration if on Google Calendar
+ if (window.location.hostname === "calendar.google.com") {
+ try {
+ initGoogleCalendarIntegration();
+ console.log("Cal.com: Google Calendar integration initialized successfully");
+ } catch (error) {
+ console.error("Cal.com: Failed to initialize Google Calendar integration", error);
+ }
+ }
},
});
diff --git a/companion/extension/lib/linkedin.ts b/companion/extension/lib/linkedin.ts
new file mode 100644
index 0000000000..007bc89246
--- /dev/null
+++ b/companion/extension/lib/linkedin.ts
@@ -0,0 +1,975 @@
+///
+
+// LinkedIn integration: inject a Cal.com scheduling button in LinkedIn messaging
+
+// ============================================================================
+// Constants
+// ============================================================================
+
+const CONSTANTS = {
+ CACHE_DURATION: 5 * 60 * 1000, // 5 minutes
+ BUTTON_SIZES: {
+ SMALL: { button: 24, icon: 14 },
+ LARGE: { button: 32, icon: 18 },
+ },
+ MENU: {
+ WIDTH: 400,
+ MAX_HEIGHT: 350,
+ MARGIN: 20,
+ GAP: 8,
+ },
+ ANIMATION: {
+ TOOLTIP_FADE_DELAY: 150,
+ NOTIFICATION_DURATION: 3000,
+ NOTIFICATION_FADE_OUT: 200,
+ },
+ COLORS: {
+ BUTTON_BG: "#000000",
+ BUTTON_HOVER: "#333333",
+ TOOLTIP_BG: "#1a1a1a",
+ NOTIFICATION_SUCCESS: "#111827",
+ NOTIFICATION_ERROR: "#752522",
+ HOVER_BG: "#f8f9fa",
+ },
+ SELECTORS: {
+ LEFT_ACTIONS: ".msg-form__left-actions",
+ EMOJI_BUTTON: "div:has(> span.artdeco-hoverable-trigger button.emoji-hoverable-trigger)",
+ EMOJI_BUTTON_FALLBACK: 'div:has(button[aria-label="Open Emoji Keyboard"])',
+ LARGE_BUTTON: ".artdeco-button--2",
+ CAL_BUTTON: ".cal-companion-linkedin-button",
+ CAL_MENU: ".cal-companion-linkedin-menu",
+ MESSAGE_INPUT: [
+ 'div[contenteditable="true"][role="textbox"][aria-label*="message" i]',
+ 'div[contenteditable="true"][data-testid*="message"]',
+ 'div[contenteditable="true"].msg-form__contenteditable',
+ ],
+ },
+ CLASSES: {
+ BASE_BUTTON:
+ "cal-companion-linkedin-button msg-form__footer-action artdeco-button artdeco-button--tertiary artdeco-button--circle artdeco-button--muted",
+ SMALL_BUTTON: "artdeco-button--1",
+ LARGE_BUTTON: "artdeco-button--2",
+ WRAPPER: "cal-companion-linkedin-wrapper inline-block",
+ MENU: "cal-companion-linkedin-menu",
+ TOOLTIP: "cal-tooltip",
+ },
+} as const;
+
+// SVG Icons
+const SVG_ICONS = {
+ CAL_LOGO: (size: number) => `
+
+ `,
+ PREVIEW: `
+
+ `,
+ COPY: `
+
+ `,
+ COPY_SUCCESS: `
+
+ `,
+ EDIT: `
+
+ `,
+ ERROR: `
+
+ `,
+ CHECK: `
+
+ `,
+} as const;
+
+// ============================================================================
+// Types
+// ============================================================================
+
+interface EventType {
+ id: string;
+ title?: string;
+ slug: string;
+ lengthInMinutes?: number;
+ length?: number;
+ duration?: number;
+ description?: string;
+ users?: Array<{ username?: string }>;
+}
+
+interface ButtonSize {
+ button: number;
+ icon: number;
+}
+
+// ============================================================================
+// Main Integration Function
+// ============================================================================
+
+export function initLinkedInIntegration() {
+ // Cache for event types (refreshed on page reload)
+ let eventTypesCache: EventType[] | null = null;
+ let cacheTimestamp: number | null = null;
+
+ console.log("[Cal.com] LinkedIn integration starting...");
+
+ injectStyles();
+
+ // ============================================================================
+ // Button Injection
+ // ============================================================================
+
+ function injectCalButton() {
+ const leftActionsContainers = document.querySelectorAll(
+ CONSTANTS.SELECTORS.LEFT_ACTIONS
+ );
+
+ console.log("[Cal.com] Found left-actions containers:", leftActionsContainers.length);
+
+ leftActionsContainers.forEach((leftActions) => {
+ if (leftActions.querySelector(CONSTANTS.SELECTORS.CAL_BUTTON)) {
+ return;
+ }
+
+ const emojiButtonContainer = findEmojiButtonContainer(leftActions);
+ const isLargeContext = detectButtonSizeContext(leftActions);
+ const sizes = isLargeContext ? CONSTANTS.BUTTON_SIZES.LARGE : CONSTANTS.BUTTON_SIZES.SMALL;
+
+ console.log(
+ "[Cal.com] Injecting Cal.com button into LinkedIn messaging",
+ isLargeContext ? "(large context)" : "(small context)"
+ );
+
+ const calWrapper = createButtonWrapper();
+ const calButton = createCalButton(isLargeContext, sizes);
+ calWrapper.appendChild(calButton);
+
+ insertButtonIntoDOM(leftActions, calWrapper, emojiButtonContainer);
+
+ console.log("[Cal.com] Successfully injected Cal.com button");
+ });
+ }
+
+ function findEmojiButtonContainer(container: HTMLElement): Element | null {
+ return (
+ container.querySelector(CONSTANTS.SELECTORS.EMOJI_BUTTON) ||
+ container.querySelector(CONSTANTS.SELECTORS.EMOJI_BUTTON_FALLBACK)
+ );
+ }
+
+ function detectButtonSizeContext(container: HTMLElement): boolean {
+ return !!container.querySelector(CONSTANTS.SELECTORS.LARGE_BUTTON);
+ }
+
+ function createButtonWrapper(): HTMLDivElement {
+ const wrapper = document.createElement("div");
+ wrapper.className = CONSTANTS.CLASSES.WRAPPER;
+ wrapper.style.cssText =
+ "display: flex; align-items: center; align-self: center; margin-left: 8px;";
+ return wrapper;
+ }
+
+ function createCalButton(isLargeContext: boolean, sizes: ButtonSize): HTMLButtonElement {
+ const button = document.createElement("button");
+ const buttonClass = `${CONSTANTS.CLASSES.BASE_BUTTON} ${
+ isLargeContext ? CONSTANTS.CLASSES.LARGE_BUTTON : CONSTANTS.CLASSES.SMALL_BUTTON
+ }`;
+ button.className = buttonClass;
+ button.type = "button";
+ button.title = "Schedule with Cal.com";
+ button.setAttribute("aria-label", "Schedule with Cal.com");
+
+ button.style.cssText = `
+ display: inline-flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ width: ${sizes.button}px !important;
+ height: ${sizes.button}px !important;
+ min-width: ${sizes.button}px !important;
+ min-height: ${sizes.button}px !important;
+ border-radius: 50% !important;
+ background-color: ${CONSTANTS.COLORS.BUTTON_BG} !important;
+ border: none !important;
+ cursor: pointer !important;
+ transition: all 0.2s ease !important;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1) !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ `;
+
+ button.innerHTML = SVG_ICONS.CAL_LOGO(sizes.icon);
+
+ // Add hover effects
+ button.addEventListener("mouseenter", () => {
+ button.style.backgroundColor = CONSTANTS.COLORS.BUTTON_HOVER;
+ button.style.transform = "scale(1.05)";
+ });
+
+ button.addEventListener("mouseleave", () => {
+ button.style.backgroundColor = CONSTANTS.COLORS.BUTTON_BG;
+ button.style.transform = "scale(1)";
+ });
+
+ // Add click handler
+ button.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const existingMenu = document.querySelector(CONSTANTS.SELECTORS.CAL_MENU);
+ if (existingMenu) {
+ existingMenu.remove();
+ return;
+ }
+
+ createEventTypesMenu(button);
+ });
+
+ return button;
+ }
+
+ function insertButtonIntoDOM(
+ leftActions: HTMLElement,
+ calWrapper: HTMLDivElement,
+ emojiButtonContainer: Element | null
+ ) {
+ if (emojiButtonContainer?.nextSibling) {
+ const nextElement = emojiButtonContainer.nextSibling;
+ if (
+ nextElement instanceof Element &&
+ (nextElement.classList.contains("calendly-button") || nextElement.tagName === "TD")
+ ) {
+ leftActions.insertBefore(calWrapper, nextElement);
+ } else {
+ leftActions.insertBefore(calWrapper, nextElement);
+ }
+ } else if (emojiButtonContainer) {
+ if (emojiButtonContainer.nextSibling) {
+ leftActions.insertBefore(calWrapper, emojiButtonContainer.nextSibling);
+ } else {
+ leftActions.appendChild(calWrapper);
+ }
+ } else {
+ leftActions.appendChild(calWrapper);
+ }
+ }
+
+ // ============================================================================
+ // Menu Creation
+ // ============================================================================
+
+ function createEventTypesMenu(button: HTMLButtonElement) {
+ const menu = document.createElement("div");
+ menu.className = CONSTANTS.CLASSES.MENU;
+ positionMenu(menu, button);
+ menu.innerHTML = `
+
+ Loading event types...
+
+ `;
+
+ const tooltipsToCleanup: HTMLElement[] = [];
+ fetchEventTypes(menu, tooltipsToCleanup);
+
+ // Prevent event bubbling
+ menu.addEventListener("wheel", (e) => e.stopPropagation(), { passive: true });
+ menu.addEventListener("scroll", (e) => e.stopPropagation(), { passive: true });
+
+ document.body.appendChild(menu);
+
+ // Close menu when clicking outside
+ setTimeout(() => {
+ const closeMenu = (evt: MouseEvent) => {
+ if (!menu.contains(evt.target as Node) && evt.target !== button) {
+ tooltipsToCleanup.forEach((tooltip) => tooltip.remove());
+ menu.remove();
+ document.removeEventListener("click", closeMenu);
+ }
+ };
+ document.addEventListener("click", closeMenu);
+ }, 0);
+ }
+
+ function positionMenu(menu: HTMLDivElement, button: HTMLButtonElement) {
+ const buttonRect = button.getBoundingClientRect();
+ const { WIDTH, MAX_HEIGHT, MARGIN, GAP } = CONSTANTS.MENU;
+
+ let menuLeft = buttonRect.left;
+ const menuBottom = window.innerHeight - buttonRect.top + GAP;
+
+ // Keep menu within screen bounds
+ if (menuLeft + WIDTH > window.innerWidth - MARGIN) {
+ menuLeft = window.innerWidth - WIDTH - MARGIN;
+ }
+ if (menuLeft < MARGIN) {
+ menuLeft = MARGIN;
+ }
+
+ // Determine vertical position
+ const spaceAbove = buttonRect.top;
+ const spaceBelow = window.innerHeight - buttonRect.bottom;
+ const positionStyle =
+ spaceAbove < MAX_HEIGHT && spaceBelow > spaceAbove
+ ? `top: ${buttonRect.bottom + GAP}px;`
+ : `bottom: ${menuBottom}px;`;
+
+ menu.style.cssText = `
+ position: fixed;
+ ${positionStyle}
+ left: ${menuLeft}px;
+ width: ${WIDTH}px;
+ max-height: ${Math.min(MAX_HEIGHT, Math.max(spaceAbove, spaceBelow) - MARGIN)}px;
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.15);
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ z-index: 2147483647;
+ overflow-y: auto;
+ overflow-x: hidden;
+ -webkit-overflow-scrolling: touch;
+ scrollbar-width: thin;
+ scrollbar-color: #c1c1c1 transparent;
+ `;
+ }
+
+ // ============================================================================
+ // Event Types Fetching & Display
+ // ============================================================================
+
+ async function fetchEventTypes(menu: HTMLElement, tooltipsToCleanup: HTMLElement[]) {
+ try {
+ const eventTypes = await getEventTypes();
+ renderEventTypes(menu, eventTypes, tooltipsToCleanup);
+ } catch (error) {
+ handleFetchError(error, menu, tooltipsToCleanup);
+ }
+ }
+
+ async function getEventTypes(): Promise {
+ const now = Date.now();
+ const isCacheValid =
+ eventTypesCache && cacheTimestamp && now - cacheTimestamp < CONSTANTS.CACHE_DURATION;
+
+ if (isCacheValid && eventTypesCache) {
+ return eventTypesCache;
+ }
+
+ if (!chrome.runtime?.id) {
+ throw new Error("Extension context invalidated. Please reload the page.");
+ }
+
+ const response = await new Promise((resolve, reject) => {
+ try {
+ chrome.runtime.sendMessage({ action: "fetch-event-types" }, (response) => {
+ if (chrome.runtime.lastError) {
+ reject(new Error(chrome.runtime.lastError.message));
+ } else if (response && (response as { error?: string }).error) {
+ reject(new Error((response as { error: string }).error));
+ } else {
+ resolve(response);
+ }
+ });
+ } catch (err) {
+ reject(err);
+ }
+ });
+
+ let eventTypes: EventType[] = [];
+ if (response && typeof response === "object" && "data" in response) {
+ eventTypes = Array.isArray((response as { data: unknown }).data)
+ ? ((response as { data: unknown }).data as EventType[])
+ : [];
+ } else if (Array.isArray(response)) {
+ eventTypes = response;
+ }
+
+ eventTypesCache = eventTypes;
+ cacheTimestamp = now;
+ return eventTypes;
+ }
+
+ function renderEventTypes(
+ menu: HTMLElement,
+ eventTypes: EventType[],
+ tooltipsToCleanup: HTMLElement[]
+ ) {
+ menu.innerHTML = "";
+
+ if (eventTypes.length === 0) {
+ menu.innerHTML = `
+
+ No event types found
+
+ `;
+ return;
+ }
+
+ try {
+ eventTypes.forEach((eventType, index) => {
+ if (!eventType || typeof eventType !== "object") {
+ return;
+ }
+
+ const menuItem = createEventTypeMenuItem(
+ eventType,
+ index,
+ eventTypes.length,
+ tooltipsToCleanup
+ );
+ menu.appendChild(menuItem);
+ });
+ } catch (error) {
+ menu.innerHTML = `
+
+ Error displaying event types
+
+ `;
+ }
+ }
+
+ function createEventTypeMenuItem(
+ eventType: EventType,
+ index: number,
+ totalItems: number,
+ tooltipsToCleanup: HTMLElement[]
+ ): HTMLElement {
+ const title = eventType.title || "Untitled Event";
+ const length = eventType.lengthInMinutes || eventType.length || eventType.duration || 30;
+ const description = eventType.description || "";
+
+ const menuItem = document.createElement("div");
+ menuItem.style.cssText = `
+ padding: 14px 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ transition: background-color 0.1s ease;
+ border-bottom: ${index < totalItems - 1 ? "1px solid #E5E5EA" : "none"};
+ `;
+
+ const contentWrapper = createEventTypeContent(
+ eventType,
+ title,
+ length,
+ description,
+ tooltipsToCleanup
+ );
+ const buttonsContainer = createEventTypeButtons(eventType, tooltipsToCleanup);
+
+ menuItem.appendChild(contentWrapper);
+ menuItem.appendChild(buttonsContainer);
+
+ // Hover effect
+ menuItem.addEventListener("mouseenter", () => {
+ menuItem.style.backgroundColor = CONSTANTS.COLORS.HOVER_BG;
+ });
+ menuItem.addEventListener("mouseleave", () => {
+ menuItem.style.backgroundColor = "transparent";
+ });
+
+ return menuItem;
+ }
+
+ function createEventTypeContent(
+ eventType: EventType,
+ title: string,
+ length: number,
+ description: string,
+ tooltipsToCleanup: HTMLElement[]
+ ): HTMLElement {
+ const contentWrapper = document.createElement("div");
+ contentWrapper.style.cssText = `
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ cursor: pointer;
+ margin-right: 12px;
+ position: relative;
+ min-width: 0;
+ overflow: hidden;
+ `;
+
+ contentWrapper.innerHTML = `
+
+ ${escapeHtml(title)}
+
+
+
+ ${length}min
+
+ ${
+ description
+ ? `${escapeHtml(description)}`
+ : ""
+ }
+
+ `;
+
+ const tooltip = createTooltip("Insert link", contentWrapper);
+ tooltipsToCleanup.push(tooltip);
+
+ contentWrapper.addEventListener("mouseenter", () => {
+ const rect = contentWrapper.getBoundingClientRect();
+ tooltip.style.left = `${rect.left + rect.width / 2 + 80}px`;
+ tooltip.style.top = `${rect.top + 35}px`;
+ tooltip.style.transform = "translate(-50%, -100%)";
+ tooltip.style.display = "block";
+ tooltip.style.opacity = "1";
+ });
+
+ contentWrapper.addEventListener("mouseleave", () => {
+ tooltip.style.opacity = "0";
+ setTimeout(() => {
+ if (tooltip.style.opacity === "0") {
+ tooltip.style.display = "none";
+ }
+ }, CONSTANTS.ANIMATION.TOOLTIP_FADE_DELAY);
+ });
+
+ contentWrapper.addEventListener("click", (e) => {
+ e.stopPropagation();
+ tooltip.remove();
+ const menu = document.querySelector(CONSTANTS.SELECTORS.CAL_MENU);
+ if (menu) menu.remove();
+ insertEventTypeLink(eventType);
+ });
+
+ return contentWrapper;
+ }
+
+ function createEventTypeButtons(
+ eventType: EventType,
+ tooltipsToCleanup: HTMLElement[]
+ ): HTMLElement {
+ const buttonsContainer = document.createElement("div");
+ buttonsContainer.style.cssText = `
+ display: flex;
+ align-items: center;
+ gap: 0;
+ flex-shrink: 0;
+ `;
+
+ const previewBtn = createActionButton(
+ SVG_ICONS.PREVIEW,
+ "Preview",
+ "6px 0 0 6px",
+ tooltipsToCleanup
+ );
+ previewBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const bookingUrl = buildBookingUrl(eventType);
+ window.open(bookingUrl, "_blank");
+ });
+
+ const copyBtn = createActionButton(SVG_ICONS.COPY, "Copy link", "none", tooltipsToCleanup);
+ copyBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const bookingUrl = buildBookingUrl(eventType);
+ navigator.clipboard
+ .writeText(bookingUrl)
+ .then(() => {
+ showNotification("Link copied!", "success");
+ copyBtn.innerHTML = SVG_ICONS.COPY_SUCCESS;
+ setTimeout(() => {
+ copyBtn.innerHTML = SVG_ICONS.COPY;
+ }, 2000);
+ })
+ .catch(() => {
+ showNotification("Failed to copy link", "error");
+ });
+ });
+
+ const editBtn = createActionButton(SVG_ICONS.EDIT, "Edit", "0 6px 6px 0", tooltipsToCleanup);
+ editBtn.addEventListener("click", (e) => {
+ e.stopPropagation();
+ const editUrl = `https://app.cal.com/event-types/${eventType.id}`;
+ window.open(editUrl, "_blank");
+ });
+
+ buttonsContainer.appendChild(previewBtn);
+ buttonsContainer.appendChild(copyBtn);
+ buttonsContainer.appendChild(editBtn);
+
+ return buttonsContainer;
+ }
+
+ function createActionButton(
+ icon: string,
+ tooltipText: string,
+ borderRadius: string,
+ tooltipsToCleanup: HTMLElement[]
+ ): HTMLButtonElement {
+ const button = document.createElement("button");
+ button.innerHTML = icon;
+ button.style.cssText = `
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid #e5e5ea;
+ border-radius: ${borderRadius};
+ ${borderRadius === "none" ? "border-right: none;" : ""}
+ background: white;
+ cursor: pointer;
+ transition: background-color 0.1s ease;
+ padding: 0;
+ position: relative;
+ `;
+
+ const tooltip = createTooltip(tooltipText, button);
+ tooltipsToCleanup.push(tooltip);
+
+ button.addEventListener("mouseenter", () => {
+ button.style.backgroundColor = CONSTANTS.COLORS.HOVER_BG;
+ });
+ button.addEventListener("mouseleave", () => {
+ button.style.backgroundColor = "white";
+ });
+
+ return button;
+ }
+
+ function buildBookingUrl(eventType: EventType): string {
+ return `https://cal.com/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`;
+ }
+
+ function handleFetchError(error: unknown, menu: HTMLElement, tooltipsToCleanup: HTMLElement[]) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ const isAuthError =
+ errorMessage.includes("OAuth") ||
+ errorMessage.includes("access token") ||
+ errorMessage.includes("sign in") ||
+ errorMessage.includes("authentication");
+
+ if (isAuthError) {
+ tooltipsToCleanup.forEach((tooltip) => tooltip.remove());
+ menu.remove();
+ openCalSidebar();
+ return;
+ }
+
+ menu.innerHTML = `
+
+
+ ${SVG_ICONS.ERROR}
+
+
+ Something went wrong
+
+
+ ${escapeHtml(errorMessage || "Failed to load event types")}
+
+
+
+ `;
+
+ const closeBtn = menu.querySelector(".cal-linkedin-close-btn");
+ if (closeBtn) {
+ closeBtn.addEventListener("mouseenter", () => {
+ closeBtn.style.background = "#E5E7EB";
+ });
+ closeBtn.addEventListener("mouseleave", () => {
+ closeBtn.style.background = "#F3F4F6";
+ });
+ closeBtn.addEventListener("click", () => {
+ tooltipsToCleanup.forEach((tooltip) => tooltip.remove());
+ menu.remove();
+ });
+ }
+ }
+
+ // ============================================================================
+ // Tooltip Helper
+ // ============================================================================
+
+ function createTooltip(text: string, element: HTMLElement): HTMLElement {
+ const tooltip = document.createElement("div");
+ tooltip.className = CONSTANTS.CLASSES.TOOLTIP;
+ tooltip.style.cssText = `
+ position: fixed;
+ background-color: ${CONSTANTS.COLORS.TOOLTIP_BG};
+ color: white;
+ padding: 4px 8px;
+ border-radius: 6px;
+ font-size: 12px;
+ font-weight: 600;
+ white-space: nowrap;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity ${CONSTANTS.ANIMATION.TOOLTIP_FADE_DELAY}ms ease;
+ z-index: 10002;
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ display: none;
+ `;
+ tooltip.textContent = text;
+ document.body.appendChild(tooltip);
+
+ const updatePosition = () => {
+ const rect = element.getBoundingClientRect();
+ tooltip.style.left = `${rect.left + rect.width / 2}px`;
+ tooltip.style.top = `${rect.top - 8}px`;
+ tooltip.style.transform = "translate(-50%, -100%)";
+ };
+
+ element.addEventListener("mouseenter", () => {
+ updatePosition();
+ tooltip.style.display = "block";
+ tooltip.style.opacity = "1";
+ });
+
+ element.addEventListener("mouseleave", () => {
+ tooltip.style.opacity = "0";
+ setTimeout(() => {
+ if (tooltip.style.opacity === "0") {
+ tooltip.style.display = "none";
+ }
+ }, CONSTANTS.ANIMATION.TOOLTIP_FADE_DELAY);
+ });
+
+ return tooltip;
+ }
+
+ // ============================================================================
+ // Link Insertion
+ // ============================================================================
+
+ function insertEventTypeLink(eventType: EventType) {
+ const bookingUrl = buildBookingUrl(eventType);
+ const inserted = insertTextAtCursor(bookingUrl);
+
+ if (inserted) {
+ showNotification("Link inserted", "success");
+ } else {
+ navigator.clipboard
+ .writeText(bookingUrl)
+ .then(() => {
+ showNotification("Link copied!", "success");
+ })
+ .catch(() => {
+ showNotification("Failed to copy link", "error");
+ });
+ }
+ }
+
+ function insertTextAtCursor(text: string): boolean {
+ const messageInput = findMessageInput();
+ if (!messageInput) {
+ return false;
+ }
+
+ messageInput.focus();
+ const selection = window.getSelection();
+
+ if (!selection || selection.rangeCount === 0) {
+ appendTextToEnd(messageInput, text, selection);
+ return true;
+ }
+
+ insertTextAtSelection(text, selection, messageInput);
+ return true;
+ }
+
+ function findMessageInput(): HTMLElement | null {
+ for (const selector of CONSTANTS.SELECTORS.MESSAGE_INPUT) {
+ const element = document.querySelector(selector);
+ if (element) return element;
+ }
+ return null;
+ }
+
+ function appendTextToEnd(messageInput: HTMLElement, text: string, selection: Selection | null) {
+ const textNode = document.createTextNode(` ${text} `);
+ messageInput.appendChild(textNode);
+
+ const range = document.createRange();
+ range.setStartAfter(textNode);
+ range.collapse(true);
+ selection?.removeAllRanges();
+ selection?.addRange(range);
+ }
+
+ function insertTextAtSelection(text: string, selection: Selection, messageInput: HTMLElement) {
+ const range = selection.getRangeAt(0);
+ range.deleteContents();
+
+ const textNode = document.createTextNode(` ${text} `);
+ range.insertNode(textNode);
+
+ range.setStartAfter(textNode);
+ range.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ messageInput.dispatchEvent(new Event("input", { bubbles: true }));
+ messageInput.dispatchEvent(new Event("change", { bubbles: true }));
+ }
+
+ // ============================================================================
+ // Notification & Sidebar
+ // ============================================================================
+
+ function showNotification(message: string, type: "success" | "error") {
+ const notification = document.createElement("div");
+ notification.style.cssText = `
+ position: fixed;
+ bottom: 80px;
+ right: 80px;
+ padding: 10px 12px;
+ background: ${type === "success" ? CONSTANTS.COLORS.NOTIFICATION_SUCCESS : CONSTANTS.COLORS.NOTIFICATION_ERROR};
+ color: white;
+ border: 1px solid #2b2b2b;
+ border-radius: 8px;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ z-index: 10000;
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.3), 0 2px 6px 2px rgba(0, 0, 0, 0.15);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ opacity: 0;
+ transform: translateY(10px);
+ transition: opacity 0.2s ease, transform 0.2s ease;
+ `;
+
+ if (type === "success") {
+ const checkIcon = document.createElement("span");
+ checkIcon.innerHTML = SVG_ICONS.CHECK;
+ checkIcon.style.cssText = `
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ `;
+ notification.appendChild(checkIcon);
+ }
+
+ const messageText = document.createElement("span");
+ messageText.textContent = message;
+ notification.appendChild(messageText);
+
+ document.body.appendChild(notification);
+
+ requestAnimationFrame(() => {
+ notification.style.opacity = "1";
+ notification.style.transform = "translateY(0)";
+ });
+
+ setTimeout(() => {
+ notification.style.opacity = "0";
+ notification.style.transform = "translateY(10px)";
+ setTimeout(() => {
+ notification.remove();
+ }, CONSTANTS.ANIMATION.NOTIFICATION_FADE_OUT);
+ }, CONSTANTS.ANIMATION.NOTIFICATION_DURATION);
+ }
+
+ function openCalSidebar() {
+ window.dispatchEvent(new CustomEvent("cal-companion-open-sidebar"));
+ }
+
+ // ============================================================================
+ // Styles
+ // ============================================================================
+
+ function injectStyles() {
+ if (document.getElementById("cal-linkedin-styles")) {
+ return;
+ }
+
+ const styleSheet = document.createElement("style");
+ styleSheet.id = "cal-linkedin-styles";
+ styleSheet.textContent = `
+ .cal-companion-linkedin-menu::-webkit-scrollbar {
+ width: 6px;
+ }
+ .cal-companion-linkedin-menu::-webkit-scrollbar-track {
+ background: transparent;
+ border-radius: 3px;
+ }
+ .cal-companion-linkedin-menu::-webkit-scrollbar-thumb {
+ background-color: #c1c1c1;
+ border-radius: 3px;
+ }
+ .cal-companion-linkedin-menu::-webkit-scrollbar-thumb:hover {
+ background-color: #a1a1a1;
+ }
+ `;
+ document.head.appendChild(styleSheet);
+ }
+
+ // ============================================================================
+ // Utility Functions
+ // ============================================================================
+
+ function escapeHtml(text: string): string {
+ const div = document.createElement("div");
+ div.textContent = text;
+ return div.innerHTML;
+ }
+
+ // ============================================================================
+ // Initialization
+ // ============================================================================
+
+ setTimeout(injectCalButton, 1000);
+
+ const observer = new MutationObserver(() => {
+ injectCalButton();
+ });
+
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ });
+
+ let currentUrl = window.location.href;
+ setInterval(() => {
+ if (window.location.href !== currentUrl) {
+ currentUrl = window.location.href;
+ setTimeout(injectCalButton, 500);
+ }
+ }, 1000);
+}
diff --git a/companion/tsconfig.json b/companion/tsconfig.json
index fddcdece9c..287f7c7f37 100644
--- a/companion/tsconfig.json
+++ b/companion/tsconfig.json
@@ -3,5 +3,6 @@
"types": ["nativewind/types"]
},
"extends": "expo/tsconfig.base",
- "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts", "nativewind-env.d.ts"]
+ "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts", "nativewind-env.d.ts"],
+ "exclude": ["extension/**/*", ".output/**/*"]
}