Skip to content

Commit 77c79f9

Browse files
authored
Merge pull request #478 from gitroomhq/feat/linkedin
Tag companies on LinkedIn
2 parents a9630b6 + be75123 commit 77c79f9

File tree

6 files changed

+98
-36
lines changed

6 files changed

+98
-36
lines changed

apps/frontend/src/components/launches/calendar.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { groupBy, sortBy } from 'lodash';
2727
import Image from 'next/image';
2828
import { extend } from 'dayjs';
2929
import { isUSCitizen } from './helpers/isuscitizen.utils';
30+
import removeMd from 'remove-markdown';
3031
extend(isSameOrAfter);
3132
extend(isSameOrBefore);
3233

@@ -604,7 +605,7 @@ const CalendarItem: FC<{
604605
</div>
605606
<div className="whitespace-pre-wrap line-clamp-3">
606607
{state === 'DRAFT' ? 'Draft: ' : ''}
607-
{post.content}
608+
{removeMd(post.content).replace(/\n/g, ' ')}
608609
</div>
609610
</div>
610611
);

apps/frontend/src/components/launches/general.preview.component.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ import { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';
33
import { useFormatting } from '@gitroom/frontend/components/launches/helpers/use.formatting';
44
import clsx from 'clsx';
55
import { VideoOrImage } from '@gitroom/react/helpers/video.or.image';
6-
import { Chakra_Petch } from 'next/font/google';
76
import { FC } from 'react';
87
import { textSlicer } from '@gitroom/helpers/utils/count.length';
9-
const chakra = Chakra_Petch({ weight: '400', subsets: ['latin'] });
8+
import interClass from '@gitroom/react/helpers/inter.font';
109

1110
export const GeneralPreviewComponent: FC<{maximumCharacters?: number}> = (props) => {
1211
const { value: topValue, integration } = useIntegration();
@@ -64,7 +63,7 @@ export const GeneralPreviewComponent: FC<{maximumCharacters?: number}> = (props)
6463
{integration?.display || '@username'}
6564
</div>
6665
</div>
67-
<pre className={clsx('text-wrap', chakra.className)} dangerouslySetInnerHTML={{__html: value.text}} />
66+
<pre className={clsx('text-wrap', interClass)} dangerouslySetInnerHTML={{__html: value.text}} />
6867
{!!value?.images?.length && (
6968
<div className={clsx("w-full rounded-[16px] overflow-hidden mt-[12px]", value?.images?.length > 3 ? 'grid grid-cols-2 gap-[4px]' : 'flex gap-[4px]')}>
7069
{value.images.map((image, index) => (

apps/frontend/src/components/launches/helpers/linkedin.component.tsx

+23-16
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
1414
import { Input } from '@gitroom/react/form/input';
1515
import { Button } from '@gitroom/react/form/button';
16+
import { useToaster } from '@gitroom/react/toaster/toaster';
1617

1718
const postUrlEmitter = new EventEmitter();
1819

@@ -76,26 +77,32 @@ export const LinkedinCompany: FC<{
7677
const { onClose, onSelect, id } = props;
7778
const fetch = useFetch();
7879
const [company, setCompany] = useState<any>(null);
80+
const toast = useToaster();
7981

8082
const getCompany = async () => {
8183
if (!company) {
82-
return ;
84+
return;
85+
}
86+
87+
try {
88+
const { options } = await (
89+
await fetch('/integrations/function', {
90+
method: 'POST',
91+
body: JSON.stringify({
92+
id,
93+
name: 'company',
94+
data: {
95+
url: company,
96+
},
97+
}),
98+
})
99+
).json();
100+
101+
onSelect(options.value);
102+
onClose();
103+
} catch (e) {
104+
toast.show('Failed to load profile', 'warning');
83105
}
84-
const {options} = await (
85-
await fetch('/integrations/function', {
86-
method: 'POST',
87-
body: JSON.stringify({
88-
id,
89-
name: 'company',
90-
data: {
91-
url: company,
92-
},
93-
}),
94-
})
95-
).json();
96-
97-
onSelect(options.value);
98-
onClose();
99106
};
100107

101108
return (

apps/frontend/src/components/launches/helpers/use.formatting.ts

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export const useFormatting = (
2626
if (params.removeMarkdown) {
2727
newText = removeMd(newText);
2828
}
29+
newText = newText.replace(/@\w{1,15}/g, function(match) {
30+
return `<strong>${match}</strong>`;
31+
});
2932
if (params.saveBreaklines) {
3033
newText = newText.replace('𝔫𝔢𝔴𝔩𝔦𝔫𝔢', '\n');
3134
}

apps/frontend/src/components/launches/providers/high.order.provider.tsx

+39-3
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ import { newImage } from '@gitroom/frontend/components/launches/helpers/new.imag
2828
import { postSelector } from '@gitroom/frontend/components/post-url-selector/post.url.selector';
2929
import { UpDownArrow } from '@gitroom/frontend/components/launches/up.down.arrow';
3030
import { arrayMoveImmutable } from 'array-move';
31-
import { linkedinCompany } from '@gitroom/frontend/components/launches/helpers/linkedin.component';
31+
import {
32+
LinkedinCompany,
33+
linkedinCompany,
34+
} from '@gitroom/frontend/components/launches/helpers/linkedin.component';
3235
import { Editor } from '@gitroom/frontend/components/launches/editor';
3336
import { useCopilotAction, useCopilotReadable } from '@copilotkit/react-core';
3437
import { AddPostButton } from '@gitroom/frontend/components/launches/add.post.button';
3538
import { GeneralPreviewComponent } from '@gitroom/frontend/components/launches/general.preview.component';
3639
import { capitalize } from 'lodash';
40+
import { useModals } from '@mantine/modals';
3741

3842
// Simple component to change back to settings on after changing tab
3943
export const SetTab: FC<{ changeTab: () => void }> = (props) => {
@@ -69,8 +73,8 @@ export const EditorWrapper: FC<{ children: ReactNode }> = ({ children }) => {
6973
};
7074

7175
export const withProvider = function <T extends object>(
72-
SettingsComponent: FC<{values?: any}> | null,
73-
CustomPreviewComponent?: FC<{maximumCharacters?: number}>,
76+
SettingsComponent: FC<{ values?: any }> | null,
77+
CustomPreviewComponent?: FC<{ maximumCharacters?: number }>,
7478
dto?: any,
7579
checkValidity?: (
7680
value: Array<Array<{ path: string }>>,
@@ -91,6 +95,8 @@ export const withProvider = function <T extends object>(
9195
}) => {
9296
const existingData = useExistingData();
9397
const { integration, date } = useIntegration();
98+
const [showLinkedinPopUp, setShowLinkedinPopUp] = useState<any>(false);
99+
94100
useCopilotReadable({
95101
description:
96102
integration?.type === 'social'
@@ -255,6 +261,21 @@ export const withProvider = function <T extends object>(
255261
},
256262
});
257263

264+
const tagPersonOrCompany = useCallback(
265+
(integration: string, editor: (value: string) => void) => () => {
266+
setShowLinkedinPopUp(
267+
<LinkedinCompany
268+
onSelect={(tag) => {
269+
editor(tag);
270+
}}
271+
id={integration}
272+
onClose={() => setShowLinkedinPopUp(false)}
273+
/>
274+
);
275+
},
276+
[]
277+
);
278+
258279
// this is a trick to prevent the data from being deleted, yet we don't render the elements
259280
if (!props.show) {
260281
return null;
@@ -263,6 +284,7 @@ export const withProvider = function <T extends object>(
263284
return (
264285
<FormProvider {...form}>
265286
<SetTab changeTab={() => setShowTab(0)} />
287+
{showLinkedinPopUp ? showLinkedinPopUp : null}
266288
<div className="mt-[15px] w-full flex flex-col flex-1">
267289
{!props.hideMenu && (
268290
<div className="flex gap-[4px]">
@@ -319,6 +341,20 @@ export const withProvider = function <T extends object>(
319341
<div>
320342
<div className="flex gap-[4px]">
321343
<div className="flex-1 text-textColor editor">
344+
{integration?.identifier === 'linkedin' && (
345+
<Button
346+
className="mb-[5px]"
347+
onClick={tagPersonOrCompany(
348+
integration.id,
349+
(newValue: string) =>
350+
changeValue(index)(
351+
val.content + newValue
352+
)
353+
)}
354+
>
355+
Tag a company
356+
</Button>
357+
)}
322358
<Editor
323359
order={index}
324360
height={InPlaceValue.length > 1 ? 200 : 250}

libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts

+29-13
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
156156
async company(token: string, data: { url: string }) {
157157
const { url } = data;
158158
const getCompanyVanity = url.match(
159-
/^https?:\/\/?www\.?linkedin\.com\/company\/([^/]+)\/$/
159+
/^https?:\/\/(?:www\.)?linkedin\.com\/company\/([^/]+)\/?$/
160160
);
161161
if (!getCompanyVanity || !getCompanyVanity?.length) {
162162
throw new Error('Invalid LinkedIn company URL');
@@ -282,6 +282,32 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
282282
}
283283
}
284284

285+
private fixText(text: string) {
286+
const pattern = /@\[.+?]\(urn:li:organization.+?\)/g;
287+
const matches = text.match(pattern) || [];
288+
const splitAll = text.split(pattern);
289+
const splitTextReformat = splitAll.map((p) => {
290+
return p
291+
.replace(/\*/g, '\\*')
292+
.replace(/\(/g, '\\(')
293+
.replace(/\)/g, '\\)')
294+
.replace(/\{/g, '\\{')
295+
.replace(/}/g, '\\}')
296+
.replace(/@/g, '\\@');
297+
});
298+
299+
const connectAll = splitTextReformat.reduce((all, current) => {
300+
const match = matches.shift();
301+
all.push(current);
302+
if (match) {
303+
all.push(match);
304+
}
305+
return all;
306+
}, [] as string[]);
307+
308+
return connectAll.join('');
309+
}
310+
285311
async post(
286312
id: string,
287313
accessToken: string,
@@ -340,12 +366,7 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
340366
type === 'personal'
341367
? `urn:li:person:${id}`
342368
: `urn:li:organization:${id}`,
343-
commentary: firstPost.message
344-
.replace(/\*/g, '\\*')
345-
.replace(/\(/g, '\\(')
346-
.replace(/\)/g, '\\)')
347-
.replace(/\{/g, '\\{')
348-
.replace(/}/g, '\\}'),
369+
commentary: this.fixText(firstPost.message),
349370
visibility: 'PUBLIC',
350371
distribution: {
351372
feedDistribution: 'MAIN_FEED',
@@ -410,12 +431,7 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
410431
? `urn:li:person:${id}`
411432
: `urn:li:organization:${id}`,
412433
object: topPostId,
413-
message: post.message
414-
.replace(/\*/g, '\\*')
415-
.replace(/\(/g, '\\(')
416-
.replace(/\)/g, '\\)')
417-
.replace(/\{/g, '\\{')
418-
.replace(/}/g, '\\}'),
434+
message: this.fixText(post.message),
419435
}),
420436
}
421437
)

0 commit comments

Comments
 (0)