# Introduction In this PR we've migrated `twenty-shared` from a `vite` app [libary-mode](https://vite.dev/guide/build#library-mode) to a [preconstruct](https://preconstruct.tools/) "atomic" application ( in the future would like to introduce preconstruct to handle of all our atomic dependencies such as `twenty-emails` `twenty-ui` etc it will be integrated at the monorepo's root directly, would be to invasive in the first, starting incremental via `twenty-shared`) For more information regarding the motivations please refer to nor: - https://github.com/twentyhq/core-team-issues/issues/587 - https://github.com/twentyhq/core-team-issues/issues/281#issuecomment-2630949682 close https://github.com/twentyhq/core-team-issues/issues/589 close https://github.com/twentyhq/core-team-issues/issues/590 ## How to test In order to ease the review this PR will ship all the codegen at the very end, the actual meaning full diff is `+2,411 −114` In order to migrate existing dependent packages to `twenty-shared` multi barrel new arch you need to run in local: ```sh yarn tsx packages/twenty-shared/scripts/migrateFromSingleToMultiBarrelImport.ts && \ npx nx run-many -t lint --fix -p twenty-front twenty-ui twenty-server twenty-emails twenty-shared twenty-zapier ``` Note that `migrateFromSingleToMultiBarrelImport` is idempotent, it's atm included in the PR but should not be merged. ( such as codegen will be added before merging this script will be removed ) ## Misc - related opened issue preconstruct https://github.com/preconstruct/preconstruct/issues/617 ## Closed related PR - https://github.com/twentyhq/twenty/pull/11028 - https://github.com/twentyhq/twenty/pull/10993 - https://github.com/twentyhq/twenty/pull/10960 ## Upcoming enhancement: ( in others dedicated PRs ) - 1/ refactor generate barrel to export atomic module instead of `*` - 2/ generate barrel own package with several files and tests - 3/ Migration twenty-ui the same way - 4/ Use `preconstruct` at monorepo global level ## Conclusion As always any suggestions are welcomed !
134 lines
4.6 KiB
TypeScript
134 lines
4.6 KiB
TypeScript
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';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
|
|
export const checkIfPersonExists = async () => {
|
|
const { tab: activeTab } = await chrome.runtime.sendMessage({
|
|
action: 'getActiveTab',
|
|
});
|
|
|
|
let activeTabUrl = '';
|
|
if (isDefined(activeTab.url.endsWith('/'))) {
|
|
activeTabUrl = activeTab.url.slice(0, -1);
|
|
}
|
|
|
|
const personNameElement = document.querySelector('.text-heading-xlarge');
|
|
const personName = personNameElement ? personNameElement.textContent : '';
|
|
|
|
const { firstName, lastName } = extractFirstAndLastName(String(personName));
|
|
const person = await fetchPerson({
|
|
name: {
|
|
firstName: { eq: firstName },
|
|
lastName: { eq: lastName },
|
|
},
|
|
linkedinLink: { url: { eq: activeTabUrl }, label: { eq: activeTabUrl } },
|
|
});
|
|
return person;
|
|
};
|
|
|
|
export const addPerson = async () => {
|
|
const personNameElement = document.querySelector('.text-heading-xlarge');
|
|
|
|
const separatorElement = document.querySelector(
|
|
'.pv-text-details__separator',
|
|
);
|
|
const personCityElement = separatorElement?.previousElementSibling;
|
|
|
|
const profilePictureElement = document.querySelector(
|
|
'.pv-top-card-profile-picture__image',
|
|
);
|
|
|
|
const firstListItem = document.querySelector(
|
|
'div[data-view-name="profile-component-entity"]',
|
|
);
|
|
const secondDivElement = firstListItem?.querySelector('div:nth-child(2)');
|
|
const ariaHiddenSpan = secondDivElement?.querySelector(
|
|
'span[aria-hidden="true"]',
|
|
);
|
|
|
|
// Get the text content or other necessary data from the DOM elements.
|
|
const personName = personNameElement ? personNameElement.textContent : '';
|
|
const personCity = personCityElement
|
|
? personCityElement.textContent?.trim().replace(/\s+/g, ' ').split(',')[0]
|
|
: '';
|
|
const profilePicture = profilePictureElement
|
|
? profilePictureElement?.getAttribute('src')
|
|
: '';
|
|
const jobTitle = ariaHiddenSpan ? ariaHiddenSpan.textContent?.trim() : '';
|
|
|
|
const { firstName, lastName } = extractFirstAndLastName(String(personName));
|
|
|
|
// Prepare person data to send to the backend.
|
|
const personData: PersonInput = {
|
|
name: { firstName, lastName },
|
|
city: personCity ?? '',
|
|
avatarUrl: profilePicture ?? '',
|
|
jobTitle: jobTitle ?? '',
|
|
linkedinLink: { url: '', label: '' },
|
|
};
|
|
|
|
// Extract active tab url using chrome API - an event is triggered here and is caught by background script.
|
|
const { tab: activeTab } = await chrome.runtime.sendMessage({
|
|
action: 'getActiveTab',
|
|
});
|
|
|
|
let activeTabUrl = '';
|
|
|
|
// Remove last slash from the URL for consistency when saving usernames.
|
|
if (isDefined(activeTab.url.endsWith('/'))) {
|
|
activeTabUrl = activeTab.url.slice(0, -1);
|
|
}
|
|
|
|
personData.linkedinLink = { url: activeTabUrl, label: activeTabUrl };
|
|
const personId = await createPerson(personData);
|
|
|
|
if (isDefined(personId)) {
|
|
await changeSidePanelUrl(`/object/person/${personId}`);
|
|
}
|
|
|
|
return personId;
|
|
};
|
|
|
|
export const insertButtonForPerson = async () => {
|
|
const personButtonDiv = createDefaultButton('twenty-person-btn');
|
|
|
|
if (isDefined(personButtonDiv)) {
|
|
const addedProfileDiv = document.querySelector('.artdeco-card > .ph5');
|
|
|
|
if (isDefined(addedProfileDiv)) {
|
|
Object.assign(personButtonDiv.style, {
|
|
marginTop: '.8rem',
|
|
});
|
|
addedProfileDiv.append(personButtonDiv);
|
|
}
|
|
|
|
const personButtonSpan = personButtonDiv.getElementsByTagName('span')[0];
|
|
const person = await checkIfPersonExists();
|
|
|
|
const openPersonOnSidePanel = (personId: string) => {
|
|
personButtonSpan.textContent = 'View in Twenty';
|
|
personButtonDiv.onClickHandler(async () => {
|
|
await changeSidePanelUrl(`/object/person/${personId}`);
|
|
chrome.runtime.sendMessage({ action: 'openSidepanel' });
|
|
});
|
|
};
|
|
|
|
if (isDefined(person)) {
|
|
await changeSidePanelUrl(`/object/person/${person.id}`);
|
|
if (isDefined(person.id)) openPersonOnSidePanel(person.id);
|
|
} else {
|
|
await changeSidePanelUrl(`/objects/people`);
|
|
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';
|
|
});
|
|
}
|
|
}
|
|
};
|