feat: replace iframe with chrome sidepanel (#5197)
fixes - #5201 https://github.com/twentyhq/twenty/assets/13139771/871019c6-6456-46b4-95dd-07ffb33eb4fd --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -1,13 +1,16 @@
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
interface CustomDiv extends HTMLDivElement {
|
||||
onClickHandler: (newHandler: () => void) => void;
|
||||
}
|
||||
|
||||
export const createDefaultButton = (
|
||||
buttonId: string,
|
||||
onClickHandler?: () => void,
|
||||
buttonText = '',
|
||||
) => {
|
||||
const btn = document.getElementById(buttonId);
|
||||
): CustomDiv => {
|
||||
const btn = document.getElementById(buttonId) as CustomDiv;
|
||||
if (isDefined(btn)) return btn;
|
||||
const div = document.createElement('div');
|
||||
const div = document.createElement('div') as CustomDiv;
|
||||
const img = document.createElement('img');
|
||||
const span = document.createElement('span');
|
||||
|
||||
@ -52,19 +55,18 @@ export const createDefaultButton = (
|
||||
Object.assign(div.style, divStyles);
|
||||
});
|
||||
|
||||
// Handle the click event.
|
||||
div.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
const store = await chrome.storage.local.get();
|
||||
div.onClickHandler = (newHandler) => {
|
||||
div.onclick = async () => {
|
||||
const store = await chrome.storage.local.get();
|
||||
|
||||
// If an api key is not set, the options page opens up to allow the user to configure an api key.
|
||||
if (!store.accessToken) {
|
||||
chrome.runtime.sendMessage({ action: 'openOptionsPage' });
|
||||
return;
|
||||
}
|
||||
|
||||
onClickHandler?.();
|
||||
});
|
||||
// If an api key is not set, the options page opens up to allow the user to configure an api key.
|
||||
if (!store.accessToken) {
|
||||
chrome.runtime.sendMessage({ action: 'openSidepanel' });
|
||||
return;
|
||||
}
|
||||
newHandler();
|
||||
};
|
||||
};
|
||||
|
||||
div.id = buttonId;
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createDefaultButton } from '~/contentScript/createButton';
|
||||
import changeSidePanelUrl from '~/contentScript/utils/changeSidepanelUrl';
|
||||
import extractCompanyLinkedinLink from '~/contentScript/utils/extractCompanyLinkedinLink';
|
||||
import extractDomain from '~/contentScript/utils/extractDomain';
|
||||
import { createCompany, fetchCompany } from '~/db/company.db';
|
||||
@ -71,27 +72,19 @@ export const addCompany = async () => {
|
||||
const companyURL = extractCompanyLinkedinLink(activeTab.url);
|
||||
companyInputData.linkedinLink = { url: companyURL, label: companyURL };
|
||||
|
||||
const company = await createCompany(companyInputData);
|
||||
return company;
|
||||
const companyId = await createCompany(companyInputData);
|
||||
|
||||
if (isDefined(companyId)) {
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/company/${companyId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return companyId;
|
||||
};
|
||||
|
||||
export const insertButtonForCompany = async () => {
|
||||
const companyButtonDiv = createDefaultButton(
|
||||
'twenty-company-btn',
|
||||
async () => {
|
||||
if (isDefined(companyButtonDiv)) {
|
||||
const companyBtnSpan = companyButtonDiv.getElementsByTagName('span')[0];
|
||||
companyBtnSpan.textContent = 'Saving...';
|
||||
const company = await addCompany();
|
||||
if (isDefined(company)) {
|
||||
companyBtnSpan.textContent = 'Saved';
|
||||
Object.assign(companyButtonDiv.style, { pointerEvents: 'none' });
|
||||
} else {
|
||||
companyBtnSpan.textContent = 'Try again';
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
const companyButtonDiv = createDefaultButton('twenty-company-btn');
|
||||
|
||||
const parentDiv: HTMLDivElement | null = document.querySelector(
|
||||
'.org-top-card-primary-actions__inner',
|
||||
@ -105,13 +98,35 @@ export const insertButtonForCompany = async () => {
|
||||
parentDiv.prepend(companyButtonDiv);
|
||||
}
|
||||
|
||||
const companyBtnSpan = companyButtonDiv.getElementsByTagName('span')[0];
|
||||
const companyButtonSpan = companyButtonDiv.getElementsByTagName('span')[0];
|
||||
const company = await checkIfCompanyExists();
|
||||
|
||||
const openCompanyOnSidePanel = (companyId: string) => {
|
||||
companyButtonSpan.textContent = 'View in Twenty';
|
||||
companyButtonDiv.onClickHandler(async () => {
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/company/${companyId}`,
|
||||
);
|
||||
chrome.runtime.sendMessage({ action: 'openSidepanel' });
|
||||
});
|
||||
};
|
||||
|
||||
if (isDefined(company)) {
|
||||
companyBtnSpan.textContent = 'Saved';
|
||||
Object.assign(companyButtonDiv.style, { pointerEvents: 'none' });
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/company/${company.id}`,
|
||||
);
|
||||
if (isDefined(company.id)) openCompanyOnSidePanel(company.id);
|
||||
} else {
|
||||
companyBtnSpan.textContent = 'Add to Twenty';
|
||||
companyButtonSpan.textContent = 'Add to Twenty';
|
||||
|
||||
companyButtonDiv.onClickHandler(async () => {
|
||||
companyButtonSpan.textContent = 'Saving...';
|
||||
const companyId = await addCompany();
|
||||
if (isDefined(companyId)) {
|
||||
openCompanyOnSidePanel(companyId);
|
||||
} else {
|
||||
companyButtonSpan.textContent = 'Try again';
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createDefaultButton } from '~/contentScript/createButton';
|
||||
import changeSidePanelUrl from '~/contentScript/utils/changeSidepanelUrl';
|
||||
import extractFirstAndLastName from '~/contentScript/utils/extractFirstAndLastName';
|
||||
import { createPerson, fetchPerson } from '~/db/person.db';
|
||||
import { PersonInput } from '~/db/types/person.types';
|
||||
@ -82,44 +83,58 @@ export const addPerson = async () => {
|
||||
}
|
||||
|
||||
personData.linkedinLink = { url: activeTabUrl, label: activeTabUrl };
|
||||
const person = await createPerson(personData);
|
||||
return person;
|
||||
const personId = await createPerson(personData);
|
||||
|
||||
if (isDefined(personId)) {
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/person/${personId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return personId;
|
||||
};
|
||||
|
||||
export const insertButtonForPerson = async () => {
|
||||
const personButtonDiv = createDefaultButton('twenty-person-btn', async () => {
|
||||
if (isDefined(personButtonDiv)) {
|
||||
const personBtnSpan = personButtonDiv.getElementsByTagName('span')[0];
|
||||
personBtnSpan.textContent = 'Saving...';
|
||||
const person = await addPerson();
|
||||
if (isDefined(person)) {
|
||||
personBtnSpan.textContent = 'Saved';
|
||||
Object.assign(personButtonDiv.style, { pointerEvents: 'none' });
|
||||
} else {
|
||||
personBtnSpan.textContent = 'Try again';
|
||||
}
|
||||
}
|
||||
});
|
||||
const personButtonDiv = createDefaultButton('twenty-person-btn');
|
||||
|
||||
if (isDefined(personButtonDiv)) {
|
||||
const parentDiv: HTMLDivElement | null = document.querySelector(
|
||||
'.pv-top-card-v2-ctas',
|
||||
const addedProfileDiv: HTMLDivElement | null = document.querySelector(
|
||||
'.pv-top-card-v2-ctas__custom',
|
||||
);
|
||||
|
||||
if (isDefined(parentDiv)) {
|
||||
if (isDefined(addedProfileDiv)) {
|
||||
Object.assign(personButtonDiv.style, {
|
||||
marginRight: '.8rem',
|
||||
});
|
||||
parentDiv.prepend(personButtonDiv);
|
||||
addedProfileDiv.prepend(personButtonDiv);
|
||||
}
|
||||
|
||||
const personBtnSpan = personButtonDiv.getElementsByTagName('span')[0];
|
||||
const personButtonSpan = personButtonDiv.getElementsByTagName('span')[0];
|
||||
const person = await checkIfPersonExists();
|
||||
|
||||
const openPersonOnSidePanel = (personId: string) => {
|
||||
personButtonSpan.textContent = 'View in Twenty';
|
||||
personButtonDiv.onClickHandler(async () => {
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/person/${personId}`,
|
||||
);
|
||||
chrome.runtime.sendMessage({ action: 'openSidepanel' });
|
||||
});
|
||||
};
|
||||
|
||||
if (isDefined(person)) {
|
||||
personBtnSpan.textContent = 'Saved';
|
||||
Object.assign(personButtonDiv.style, { pointerEvents: 'none' });
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/person/${person.id}`,
|
||||
);
|
||||
if (isDefined(person.id)) openPersonOnSidePanel(person.id);
|
||||
} else {
|
||||
personBtnSpan.textContent = 'Add to Twenty';
|
||||
personButtonSpan.textContent = 'Add to Twenty';
|
||||
personButtonDiv.onClickHandler(async () => {
|
||||
personButtonSpan.textContent = 'Saving...';
|
||||
const personId = await addPerson();
|
||||
if (isDefined(personId)) openPersonOnSidePanel(personId);
|
||||
else personButtonSpan.textContent = 'Try again';
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { insertButtonForCompany } from '~/contentScript/extractCompanyProfile';
|
||||
import { insertButtonForPerson } from '~/contentScript/extractPersonProfile';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
// Inject buttons into the DOM when SPA is reloaded on the resource url.
|
||||
// e.g. reload the page when on https://www.linkedin.com/in/mabdullahabaid/
|
||||
@ -20,85 +19,5 @@ chrome.runtime.onMessage.addListener(async (message, _, sendResponse) => {
|
||||
await insertButtonForPerson();
|
||||
}
|
||||
|
||||
if (message.action === 'TOGGLE') {
|
||||
await toggle();
|
||||
}
|
||||
|
||||
if (message.action === 'AUTHENTICATED') {
|
||||
await authenticated();
|
||||
}
|
||||
|
||||
sendResponse('Executing!');
|
||||
});
|
||||
|
||||
const IFRAME_WIDTH = '400px';
|
||||
|
||||
const createIframe = () => {
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.background = 'lightgrey';
|
||||
iframe.style.height = '100vh';
|
||||
iframe.style.width = IFRAME_WIDTH;
|
||||
iframe.style.position = 'fixed';
|
||||
iframe.style.top = '0px';
|
||||
iframe.style.right = `-${IFRAME_WIDTH}`;
|
||||
iframe.style.zIndex = '9000000000000000000';
|
||||
iframe.style.transition = 'ease-in-out 0.3s';
|
||||
return iframe;
|
||||
};
|
||||
|
||||
const handleContentIframeLoadComplete = () => {
|
||||
//If the pop-out window is already open then we replace loading iframe with our content iframe
|
||||
if (optionsIframe.style.right === '0px') contentIframe.style.right = '0px';
|
||||
optionsIframe.style.display = 'none';
|
||||
contentIframe.style.display = 'block';
|
||||
};
|
||||
|
||||
//Creating one iframe where we are loading our front end in the background
|
||||
const contentIframe = createIframe();
|
||||
contentIframe.style.display = 'none';
|
||||
|
||||
chrome.storage.local.get().then((store) => {
|
||||
if (isDefined(store.loginToken)) {
|
||||
contentIframe.src = `${import.meta.env.VITE_FRONT_BASE_URL}`;
|
||||
contentIframe.onload = handleContentIframeLoadComplete;
|
||||
}
|
||||
});
|
||||
|
||||
const optionsIframe = createIframe();
|
||||
optionsIframe.src = chrome.runtime.getURL('options.html');
|
||||
|
||||
document.body.appendChild(contentIframe);
|
||||
document.body.appendChild(optionsIframe);
|
||||
|
||||
const toggleIframe = (iframe: HTMLIFrameElement) => {
|
||||
if (
|
||||
iframe.style.right === `-${IFRAME_WIDTH}` &&
|
||||
iframe.style.display !== 'none'
|
||||
) {
|
||||
iframe.style.right = '0px';
|
||||
} else if (iframe.style.right === '0px' && iframe.style.display !== 'none') {
|
||||
iframe.style.right = `-${IFRAME_WIDTH}`;
|
||||
}
|
||||
};
|
||||
|
||||
const toggle = async () => {
|
||||
const store = await chrome.storage.local.get();
|
||||
if (isDefined(store.accessToken)) {
|
||||
toggleIframe(contentIframe);
|
||||
} else {
|
||||
toggleIframe(optionsIframe);
|
||||
}
|
||||
};
|
||||
|
||||
const authenticated = async () => {
|
||||
const store = await chrome.storage.local.get();
|
||||
if (isDefined(store.loginToken)) {
|
||||
contentIframe.src = `${
|
||||
import.meta.env.VITE_FRONT_BASE_URL
|
||||
}/verify?loginToken=${store.loginToken.token}`;
|
||||
contentIframe.onload = handleContentIframeLoadComplete;
|
||||
toggleIframe(contentIframe);
|
||||
} else {
|
||||
toggleIframe(optionsIframe);
|
||||
}
|
||||
};
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const changeSidePanelUrl = async (url: string) => {
|
||||
const { tab: activeTab } = await chrome.runtime.sendMessage({
|
||||
action: 'getActiveTab',
|
||||
});
|
||||
if (isDefined(activeTab) && isDefined(url)) {
|
||||
chrome.storage.local.set({ [`sidepanelUrl_${activeTab.id}`]: url });
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'changeSidepanelUrl',
|
||||
message: { url },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default changeSidePanelUrl;
|
||||
Reference in New Issue
Block a user