diff --git a/apps/backend/.swcrc b/apps/backend/.swcrc
deleted file mode 100644
index 7d41ef14..00000000
--- a/apps/backend/.swcrc
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- "jsc": {
- "parser": {
- "syntax": "typescript",
- "tsx": false,
- "decorators": true,
- "dynamicImport": true
- },
- "target": "es2020",
- "baseUrl": "/Users/nevodavid/Projects/gitroom",
- "paths": {
- "@gitroom/backend/*": ["apps/backend/src/*"],
- "@gitroom/cron/*": ["apps/cron/src/*"],
- "@gitroom/frontend/*": ["apps/frontend/src/*"],
- "@gitroom/helpers/*": ["libraries/helpers/src/*"],
- "@gitroom/nestjs-libraries/*": ["libraries/nestjs-libraries/src/*"],
- "@gitroom/react/*": ["libraries/react-shared-libraries/src/*"],
- "@gitroom/plugins/*": ["libraries/plugins/src/*"],
- "@gitroom/workers/*": ["apps/workers/src/*"],
- "@gitroom/extension/*": ["apps/extension/src/*"]
- },
- "keepClassNames": true,
- "transform": {
- "legacyDecorator": true,
- "decoratorMetadata": true
- },
- "loose": true
- },
- "module": {
- "type": "commonjs",
- "strict": false,
- "strictMode": true,
- "lazy": false,
- "noInterop": false
- },
- "sourceMaps": true,
- "minify": false
-}
\ No newline at end of file
diff --git a/apps/cron/.swcrc b/apps/cron/.swcrc
deleted file mode 100644
index 7d41ef14..00000000
--- a/apps/cron/.swcrc
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- "jsc": {
- "parser": {
- "syntax": "typescript",
- "tsx": false,
- "decorators": true,
- "dynamicImport": true
- },
- "target": "es2020",
- "baseUrl": "/Users/nevodavid/Projects/gitroom",
- "paths": {
- "@gitroom/backend/*": ["apps/backend/src/*"],
- "@gitroom/cron/*": ["apps/cron/src/*"],
- "@gitroom/frontend/*": ["apps/frontend/src/*"],
- "@gitroom/helpers/*": ["libraries/helpers/src/*"],
- "@gitroom/nestjs-libraries/*": ["libraries/nestjs-libraries/src/*"],
- "@gitroom/react/*": ["libraries/react-shared-libraries/src/*"],
- "@gitroom/plugins/*": ["libraries/plugins/src/*"],
- "@gitroom/workers/*": ["apps/workers/src/*"],
- "@gitroom/extension/*": ["apps/extension/src/*"]
- },
- "keepClassNames": true,
- "transform": {
- "legacyDecorator": true,
- "decoratorMetadata": true
- },
- "loose": true
- },
- "module": {
- "type": "commonjs",
- "strict": false,
- "strictMode": true,
- "lazy": false,
- "noInterop": false
- },
- "sourceMaps": true,
- "minify": false
-}
\ No newline at end of file
diff --git a/apps/frontend/src/components/launches/providers/linkedin/linkedin.provider.tsx b/apps/frontend/src/components/launches/providers/linkedin/linkedin.provider.tsx
index 785a0d14..39a42e59 100644
--- a/apps/frontend/src/components/launches/providers/linkedin/linkedin.provider.tsx
+++ b/apps/frontend/src/components/launches/providers/linkedin/linkedin.provider.tsx
@@ -1,10 +1,40 @@
import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider';
-export default withProvider(
- null,
+import { Checkbox } from '@gitroom/react/form/checkbox';
+import { useT } from '@gitroom/react/translation/get.transation.service.client';
+import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
+import { LinkedinDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/linkedin.dto';
+
+const LinkedInSettings = () => {
+ const t = useT();
+ const { watch, register, formState, control } = useSettings();
+
+ return (
+
+
+
+ );
+};
+export default withProvider(
+ LinkedInSettings,
undefined,
- undefined,
- async (posts) => {
+ LinkedinDto,
+ async (posts, vals) => {
const [firstPost, ...restPosts] = posts;
+
+ if (
+ vals.post_as_images_carousel &&
+ (firstPost.length < 2 ||
+ firstPost.some((p) => p.path.indexOf('mp4') > -1))
+ ) {
+ return 'LinkedIn carousel can only be created with 2 or more images and no videos.';
+ }
+
if (
firstPost.length > 1 &&
firstPost.some((p) => p.path.indexOf('mp4') > -1)
diff --git a/i18n.lock b/i18n.lock
index 770ba02d..7d13037a 100644
--- a/i18n.lock
+++ b/i18n.lock
@@ -487,3 +487,4 @@ checksums:
start_7_days_free_trial: e9c42510c2cc750fabe704ebc0a9e768
change_language: c798f65b78e23b2cf8fc29a1a24a182f
that_a_wrap: 0ecf5b5a1fbac9c2653f2642baf5d4a5
+ post_as_images_carousel: 2f82f0f6adbf03abfeec3389800d7232
diff --git a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/linkedin.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/linkedin.dto.ts
new file mode 100644
index 00000000..b3840d14
--- /dev/null
+++ b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/linkedin.dto.ts
@@ -0,0 +1,7 @@
+import { IsBoolean, IsOptional } from 'class-validator';
+
+export class LinkedinDto {
+ @IsBoolean()
+ @IsOptional()
+ post_as_images_carousel: boolean;
+}
\ No newline at end of file
diff --git a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts
index 13c6b065..b49cc81c 100644
--- a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts
+++ b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts
@@ -11,6 +11,9 @@ import { readOrFetch } from '@gitroom/helpers/utils/read.or.fetch';
import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';
import { Integration } from '@prisma/client';
import { PostPlug } from '@gitroom/helpers/decorators/post.plug';
+import { LinkedinDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/linkedin.dto';
+import imageToPDF from 'image-to-pdf';
+import { Readable } from 'stream';
export class LinkedinProvider extends SocialAbstract implements SocialProvider {
identifier = 'linkedin';
@@ -199,13 +202,24 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
picture: any,
type = 'personal' as 'company' | 'personal'
) {
+ // Determine the appropriate endpoint based on file type
+ const isVideo = fileName.indexOf('mp4') > -1;
+ const isPdf = fileName.toLowerCase().indexOf('pdf') > -1;
+
+ let endpoint: string;
+ if (isVideo) {
+ endpoint = 'videos';
+ } else if (isPdf) {
+ endpoint = 'documents';
+ } else {
+ endpoint = 'images';
+ }
+
const {
- value: { uploadUrl, image, video, uploadInstructions, ...all },
+ value: { uploadUrl, image, video, document, uploadInstructions, ...all },
} = await (
await this.fetch(
- `https://api.linkedin.com/v2/${
- fileName.indexOf('mp4') > -1 ? 'videos' : 'images'
- }?action=initializeUpload`,
+ `https://api.linkedin.com/rest/${endpoint}?action=initializeUpload`,
{
method: 'POST',
headers: {
@@ -220,7 +234,7 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
type === 'personal'
? `urn:li:person:${personId}`
: `urn:li:organization:${personId}`,
- ...(fileName.indexOf('mp4') > -1
+ ...(isVideo
? {
fileSizeBytes: picture.length,
uploadCaptions: false,
@@ -234,7 +248,7 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
).json();
const sendUrlRequest = uploadInstructions?.[0]?.uploadUrl || uploadUrl;
- const finalOutput = video || image;
+ const finalOutput = video || image || document;
const etags = [];
for (let i = 0; i < picture.length; i += 1024 * 1024 * 2) {
@@ -244,8 +258,10 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
'X-Restli-Protocol-Version': '2.0.0',
'LinkedIn-Version': '202501',
Authorization: `Bearer ${accessToken}`,
- ...(fileName.indexOf('mp4') > -1
+ ...(isVideo
? { 'Content-Type': 'application/octet-stream' }
+ : isPdf
+ ? { 'Content-Type': 'application/pdf' }
: {}),
},
body: picture.slice(i, i + 1024 * 1024 * 2),
@@ -254,9 +270,9 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
etags.push(upload.headers.get('etag'));
}
- if (fileName.indexOf('mp4') > -1) {
+ if (isVideo) {
const a = await this.fetch(
- 'https://api.linkedin.com/v2/videos?action=finalizeUpload',
+ 'https://api.linkedin.com/rest/videos?action=finalizeUpload',
{
method: 'POST',
body: JSON.stringify({
@@ -314,147 +330,347 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider {
return connectAll.join('');
}
- async post(
- id: string,
+ private async convertImagesToPdfCarousel(
+ postDetails: PostDetails[],
+ firstPost: PostDetails
+ ): Promise[]> {
+ // Collect all images from all posts
+ const allImages = postDetails.flatMap(
+ (post) =>
+ post.media?.filter(
+ (media) =>
+ media.url.toLowerCase().includes('.jpg') ||
+ media.url.toLowerCase().includes('.jpeg') ||
+ media.url.toLowerCase().includes('.png')
+ ) || []
+ );
+
+ if (allImages.length === 0) {
+ return postDetails;
+ }
+
+ // Convert images to buffers and get dimensions
+ const imageData = await Promise.all(
+ allImages.map(async (media) => {
+ const buffer = await readOrFetch(media.url);
+ const image = sharp(buffer, {
+ animated: lookup(media.url) === 'image/gif',
+ });
+ const metadata = await image.metadata();
+
+ return {
+ buffer,
+ width: metadata.width || 0,
+ height: metadata.height || 0,
+ };
+ })
+ );
+
+ // Use the dimensions of the first image for the PDF page size
+ // You could also use the largest dimensions if you prefer
+ const firstImageDimensions = imageData[0];
+ const pageSize = [firstImageDimensions.width, firstImageDimensions.height];
+
+ // Convert images to PDF with exact image dimensions
+ const pdfStream = imageToPDF(
+ imageData.map((data) => data.buffer),
+ pageSize
+ );
+
+ // Convert stream to buffer
+ const pdfBuffer = await this.streamToBuffer(pdfStream);
+
+ // Create a temporary file-like object for the PDF
+ const pdfMedia = {
+ url: 'carousel.pdf',
+ buffer: pdfBuffer,
+ };
+
+ // Return modified post details with PDF instead of images
+ const modifiedFirstPost = {
+ ...firstPost,
+ media: [pdfMedia] as any[],
+ };
+
+ // Remove media from other posts since we're combining everything into one PDF
+ const modifiedRestPosts = postDetails.slice(1).map((post) => ({
+ ...post,
+ media: [] as any[],
+ }));
+
+ return [modifiedFirstPost, ...modifiedRestPosts];
+ }
+
+ private async streamToBuffer(stream: Readable): Promise {
+ return new Promise((resolve, reject) => {
+ const chunks: Buffer[] = [];
+ stream.on('data', (chunk) => chunks.push(chunk));
+ stream.on('end', () => resolve(Buffer.concat(chunks)));
+ stream.on('error', reject);
+ });
+ }
+
+ private async processMediaForPosts(
+ postDetails: PostDetails[],
accessToken: string,
- postDetails: PostDetails[],
- integration: Integration,
- type = 'personal' as 'company' | 'personal'
- ): Promise {
- const [firstPost, ...restPosts] = postDetails;
+ personId: string,
+ type: 'company' | 'personal'
+ ): Promise> {
+ const mediaUploads = await Promise.all(
+ postDetails.flatMap(
+ (post) =>
+ post.media?.map(async (media) => {
+ let mediaBuffer: Buffer;
+
+ // Check if media has a buffer (from PDF conversion)
+ if (
+ media &&
+ typeof media === 'object' &&
+ 'buffer' in media &&
+ Buffer.isBuffer(media.buffer)
+ ) {
+ mediaBuffer = (media as any).buffer;
+ } else {
+ mediaBuffer = await this.prepareMediaBuffer(media.url);
+ }
+
+ const uploadedMediaId = await this.uploadPicture(
+ media.url,
+ accessToken,
+ personId,
+ mediaBuffer,
+ type
+ );
- const uploadAll = (
- await Promise.all(
- postDetails.flatMap((p) =>
- p?.media?.flatMap(async (m) => {
return {
- id: await this.uploadPicture(
- m.url,
- accessToken,
- id,
- m.url.indexOf('mp4') > -1
- ? Buffer.from(await readOrFetch(m.url))
- : await sharp(await readOrFetch(m.url), {
- animated: lookup(m.url) === 'image/gif',
- })
- .toFormat('jpeg')
- .resize({
- width: 1000,
- })
- .toBuffer(),
- type
- ),
- postId: p.id,
+ id: uploadedMediaId,
+ postId: post.id,
};
- })
- )
+ }) || []
)
- ).reduce((acc, val) => {
- if (!val?.id) {
- return acc;
- }
- acc[val.postId] = acc[val.postId] || [];
- acc[val.postId].push(val.id);
+ );
+ return mediaUploads.reduce((acc, upload) => {
+ if (!upload?.id) return acc;
+
+ acc[upload.postId] = acc[upload.postId] || [];
+ acc[upload.postId].push(upload.id);
return acc;
}, {} as Record);
+ }
- const media_ids = (uploadAll[firstPost.id] || []).filter((f) => f);
+ private async prepareMediaBuffer(mediaUrl: string): Promise {
+ const isVideo = mediaUrl.indexOf('mp4') > -1;
- const data = await this.fetch('https://api.linkedin.com/v2/posts', {
+ if (isVideo) {
+ return Buffer.from(await readOrFetch(mediaUrl));
+ }
+
+ return await sharp(await readOrFetch(mediaUrl), {
+ animated: lookup(mediaUrl) === 'image/gif',
+ })
+ .toFormat('jpeg')
+ .resize({ width: 1000 })
+ .toBuffer();
+ }
+
+ private buildPostContent(isPdf: boolean, mediaIds: string[]) {
+ if (mediaIds.length === 0) {
+ return {};
+ }
+
+ if (mediaIds.length === 1) {
+ return {
+ content: {
+ media: {
+ ...(isPdf ? { title: 'slides.pdf' } : {}),
+ id: mediaIds[0],
+ },
+ },
+ };
+ }
+
+ return {
+ content: {
+ multiImage: {
+ images: mediaIds.map((id) => ({ id })),
+ },
+ },
+ };
+ }
+
+ private createLinkedInPostPayload(
+ id: string,
+ type: 'company' | 'personal',
+ message: string,
+ mediaIds: string[],
+ isPdf: boolean
+ ) {
+ const author =
+ type === 'personal' ? `urn:li:person:${id}` : `urn:li:organization:${id}`;
+
+ return {
+ author,
+ commentary: this.fixText(message),
+ visibility: 'PUBLIC',
+ distribution: {
+ feedDistribution: 'MAIN_FEED',
+ targetEntities: [] as string[],
+ thirdPartyDistributionChannels: [] as string[],
+ },
+ ...this.buildPostContent(isPdf, mediaIds),
+ lifecycleState: 'PUBLISHED',
+ isReshareDisabledByAuthor: false,
+ };
+ }
+
+ private async createMainPost(
+ id: string,
+ accessToken: string,
+ firstPost: PostDetails,
+ mediaIds: string[],
+ type: 'company' | 'personal',
+ isPdf: boolean
+ ): Promise {
+ const postPayload = this.createLinkedInPostPayload(
+ id,
+ type,
+ firstPost.message,
+ mediaIds,
+ isPdf
+ );
+
+ const response = await this.fetch('https://api.linkedin.com/rest/posts', {
method: 'POST',
headers: {
+ 'LinkedIn-Version': '202501',
'X-Restli-Protocol-Version': '2.0.0',
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
- body: JSON.stringify({
- author:
- type === 'personal'
- ? `urn:li:person:${id}`
- : `urn:li:organization:${id}`,
- commentary: this.fixText(firstPost.message),
- visibility: 'PUBLIC',
- distribution: {
- feedDistribution: 'MAIN_FEED',
- targetEntities: [],
- thirdPartyDistributionChannels: [],
- },
- ...(media_ids.length > 0
- ? {
- content: {
- ...(media_ids.length === 0
- ? {}
- : media_ids.length === 1
- ? {
- media: {
- id: media_ids[0],
- },
- }
- : {
- multiImage: {
- images: media_ids.map((id) => ({
- id,
- })),
- },
- }),
- },
- }
- : {}),
- lifecycleState: 'PUBLISHED',
- isReshareDisabledByAuthor: false,
- }),
+ body: JSON.stringify(postPayload),
});
- if (data.status !== 201 && data.status !== 200) {
+ if (response.status !== 201 && response.status !== 200) {
throw new Error('Error posting to LinkedIn');
}
- const topPostId = data.headers.get('x-restli-id')!;
+ return response.headers.get('x-restli-id')!;
+ }
- const ids = [
+ private async createCommentPost(
+ id: string,
+ accessToken: string,
+ post: PostDetails,
+ parentPostId: string,
+ type: 'company' | 'personal'
+ ): Promise {
+ const actor =
+ type === 'personal' ? `urn:li:person:${id}` : `urn:li:organization:${id}`;
+
+ const response = await this.fetch(
+ `https://api.linkedin.com/v2/socialActions/${decodeURIComponent(
+ parentPostId
+ )}/comments`,
{
- status: 'posted',
- postId: topPostId,
- id: firstPost.id,
- releaseURL: `https://www.linkedin.com/feed/update/${topPostId}`,
- },
- ];
- for (const post of restPosts) {
- const { object } = await (
- await this.fetch(
- `https://api.linkedin.com/v2/socialActions/${decodeURIComponent(
- topPostId
- )}/comments`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${accessToken}`,
- },
- body: JSON.stringify({
- actor:
- type === 'personal'
- ? `urn:li:person:${id}`
- : `urn:li:organization:${id}`,
- object: topPostId,
- message: {
- text: this.fixText(post.message),
- },
- }),
- }
- )
- ).json();
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${accessToken}`,
+ },
+ body: JSON.stringify({
+ actor,
+ object: parentPostId,
+ message: {
+ text: this.fixText(post.message),
+ },
+ }),
+ }
+ );
- ids.push({
- status: 'posted',
- postId: object,
- id: post.id,
- releaseURL: `https://www.linkedin.com/embed/feed/update/${object}`,
- });
+ const { object } = await response.json();
+ return object;
+ }
+
+ private createPostResponse(
+ postId: string,
+ originalPostId: string,
+ isMainPost: boolean = false
+ ): PostResponse {
+ const baseUrl = isMainPost
+ ? 'https://www.linkedin.com/feed/update/'
+ : 'https://www.linkedin.com/embed/feed/update/';
+
+ return {
+ status: 'posted',
+ postId,
+ id: originalPostId,
+ releaseURL: `${baseUrl}${postId}`,
+ };
+ }
+
+ async post(
+ id: string,
+ accessToken: string,
+ postDetails: PostDetails[],
+ integration: Integration,
+ type = 'personal' as 'company' | 'personal'
+ ): Promise {
+ let processedPostDetails = postDetails;
+ const [firstPost] = postDetails;
+
+ // Check if we should convert images to PDF carousel
+ if (firstPost.settings?.post_as_images_carousel) {
+ processedPostDetails = await this.convertImagesToPdfCarousel(
+ postDetails,
+ firstPost
+ );
}
- return ids;
+ const [processedFirstPost, ...restPosts] = processedPostDetails;
+
+ // Process and upload media for all posts
+ const uploadedMedia = await this.processMediaForPosts(
+ processedPostDetails,
+ accessToken,
+ id,
+ type
+ );
+
+ // Get media IDs for the main post
+ const mainPostMediaIds = (
+ uploadedMedia[processedFirstPost.id] || []
+ ).filter(Boolean);
+
+ // Create the main LinkedIn post
+ const mainPostId = await this.createMainPost(
+ id,
+ accessToken,
+ processedFirstPost,
+ mainPostMediaIds,
+ type,
+ !!firstPost.settings?.post_as_images_carousel
+ );
+
+ // Build response array starting with main post
+ const responses: PostResponse[] = [
+ this.createPostResponse(mainPostId, processedFirstPost.id, true),
+ ];
+
+ // Create comment posts for remaining posts
+ for (const post of restPosts) {
+ const commentPostId = await this.createCommentPost(
+ id,
+ accessToken,
+ post,
+ mainPostId,
+ type
+ );
+
+ responses.push(this.createPostResponse(commentPostId, post.id, false));
+ }
+
+ return responses;
}
@PostPlug({
diff --git a/libraries/react-shared-libraries/src/translation/locales/ar/translation.json b/libraries/react-shared-libraries/src/translation/locales/ar/translation.json
index 943bb9b3..b80ad6d3 100644
--- a/libraries/react-shared-libraries/src/translation/locales/ar/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/ar/translation.json
@@ -482,5 +482,6 @@
"90_days": "90 يومًا",
"start_7_days_free_trial": "ابدأ تجربة مجانية لمدة 7 أيام",
"change_language": "تغيير اللغة",
- "that_a_wrap": "انتهينا!\n\nإذا أعجبك هذا التسلسل:\n\n1. تابعني على @{{username}} للمزيد من هذه المواضيع\n2. أعد تغريد التغريدة أدناه لمشاركة هذا التسلسل مع جمهورك\n"
+ "that_a_wrap": "انتهينا!\n\nإذا أعجبك هذا التسلسل:\n\n1. تابعني على @{{username}} للمزيد من هذه المواضيع\n2. أعد تغريد التغريدة أدناه لمشاركة هذا التسلسل مع جمهورك\n",
+ "post_as_images_carousel": "انشر كعرض شرائح للصور"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/bn/translation.json b/libraries/react-shared-libraries/src/translation/locales/bn/translation.json
index 9d5c38f2..423c74db 100644
--- a/libraries/react-shared-libraries/src/translation/locales/bn/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/bn/translation.json
@@ -482,5 +482,6 @@
"90_days": "৯০ দিন",
"start_7_days_free_trial": "৭ দিনের বিনামূল্যে ট্রায়াল শুরু করুন",
"change_language": "ভাষা পরিবর্তন করুন",
- "that_a_wrap": "এটাই শেষ!\n\nযদি আপনি এই থ্রেডটি উপভোগ করে থাকেন:\n\n১. আরও এমন পোস্টের জন্য আমাকে @{{username}} ফলো করুন\n২. আপনার অডিয়েন্সের সাথে এই থ্রেডটি শেয়ার করতে নিচের টুইটটি রিটুইট করুন\n"
+ "that_a_wrap": "এটাই শেষ!\n\nযদি আপনি এই থ্রেডটি উপভোগ করে থাকেন:\n\n১. আরও এমন পোস্টের জন্য আমাকে @{{username}} ফলো করুন\n২. আপনার অডিয়েন্সের সাথে এই থ্রেডটি শেয়ার করতে নিচের টুইটটি রিটুইট করুন\n",
+ "post_as_images_carousel": "ছবির ক্যারোসেল হিসেবে পোস্ট করুন"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/de/translation.json b/libraries/react-shared-libraries/src/translation/locales/de/translation.json
index 7a07013d..63f68358 100644
--- a/libraries/react-shared-libraries/src/translation/locales/de/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/de/translation.json
@@ -482,5 +482,6 @@
"90_days": "90 Tage",
"start_7_days_free_trial": "7-tägige kostenlose Testversion starten",
"change_language": "Sprache ändern",
- "that_a_wrap": "Das war's!\n\nWenn dir dieser Thread gefallen hat:\n\n1. Folge mir @{{username}} für mehr davon\n2. Retweete den untenstehenden Tweet, um diesen Thread mit deinem Publikum zu teilen\n"
+ "that_a_wrap": "Das war's!\n\nWenn dir dieser Thread gefallen hat:\n\n1. Folge mir @{{username}} für mehr davon\n2. Retweete den untenstehenden Tweet, um diesen Thread mit deinem Publikum zu teilen\n",
+ "post_as_images_carousel": "Als Bilderkarussell posten"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/en/translation.json b/libraries/react-shared-libraries/src/translation/locales/en/translation.json
index e6616838..9b8adfd7 100644
--- a/libraries/react-shared-libraries/src/translation/locales/en/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/en/translation.json
@@ -482,5 +482,6 @@
"90_days": "90 Days",
"start_7_days_free_trial": "Start 7 days free trial",
"change_language": "Change Language",
- "that_a_wrap": "That's a wrap!\n\nIf you enjoyed this thread:\n\n1. Follow me @{{username}} for more of these\n2. RT the tweet below to share this thread with your audience\n"
+ "that_a_wrap": "That's a wrap!\n\nIf you enjoyed this thread:\n\n1. Follow me @{{username}} for more of these\n2. RT the tweet below to share this thread with your audience\n",
+ "post_as_images_carousel": "Post as images carousel"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/es/translation.json b/libraries/react-shared-libraries/src/translation/locales/es/translation.json
index a181c037..c293f1f5 100644
--- a/libraries/react-shared-libraries/src/translation/locales/es/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/es/translation.json
@@ -482,5 +482,6 @@
"90_days": "90 días",
"start_7_days_free_trial": "Comienza la prueba gratuita de 7 días",
"change_language": "Cambiar idioma",
- "that_a_wrap": "¡Eso es todo!\n\nSi te gustó este hilo:\n\n1. Sígueme en @{{username}} para más contenido como este\n2. Haz RT al tuit de abajo para compartir este hilo con tu audiencia\n"
+ "that_a_wrap": "¡Eso es todo!\n\nSi te gustó este hilo:\n\n1. Sígueme en @{{username}} para más contenido como este\n2. Haz RT al tuit de abajo para compartir este hilo con tu audiencia\n",
+ "post_as_images_carousel": "Publicar como carrusel de imágenes"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/fr/translation.json b/libraries/react-shared-libraries/src/translation/locales/fr/translation.json
index 8f70790c..54b1d1c4 100644
--- a/libraries/react-shared-libraries/src/translation/locales/fr/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/fr/translation.json
@@ -482,5 +482,6 @@
"90_days": "90 jours",
"start_7_days_free_trial": "Commencez l’essai gratuit de 7 jours",
"change_language": "Changer de langue",
- "that_a_wrap": "C'est terminé !\n\nSi vous avez aimé ce fil :\n\n1. Suivez-moi @{{username}} pour en voir d'autres\n2. Retweetez le tweet ci-dessous pour partager ce fil avec votre audience\n"
+ "that_a_wrap": "C'est terminé !\n\nSi vous avez aimé ce fil :\n\n1. Suivez-moi @{{username}} pour en voir d'autres\n2. Retweetez le tweet ci-dessous pour partager ce fil avec votre audience\n",
+ "post_as_images_carousel": "Publier en carrousel d’images"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/he/translation.json b/libraries/react-shared-libraries/src/translation/locales/he/translation.json
index 50e3ab2c..a4b41ccd 100644
--- a/libraries/react-shared-libraries/src/translation/locales/he/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/he/translation.json
@@ -482,5 +482,6 @@
"90_days": "90 ימים",
"start_7_days_free_trial": "התחל תקופת ניסיון חינם ל-7 ימים",
"change_language": "שנה שפה",
- "that_a_wrap": "זה הסוף!\n\nאם נהנית מהשרשור הזה:\n\n1. עקוב אחרי @{{username}} לעוד תכנים כאלה\n2. רטווט את הציוץ למטה כדי לשתף את השרשור עם הקהל שלך\n"
+ "that_a_wrap": "זה הסוף!\n\nאם נהנית מהשרשור הזה:\n\n1. עקוב אחרי @{{username}} לעוד תכנים כאלה\n2. רטווט את הציוץ למטה כדי לשתף את השרשור עם הקהל שלך\n",
+ "post_as_images_carousel": "פרסם כתמונות בגלריה"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/it/translation.json b/libraries/react-shared-libraries/src/translation/locales/it/translation.json
index 946a1463..65e18d2e 100644
--- a/libraries/react-shared-libraries/src/translation/locales/it/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/it/translation.json
@@ -482,5 +482,6 @@
"90_days": "90 giorni",
"start_7_days_free_trial": "Inizia la prova gratuita di 7 giorni",
"change_language": "Cambia lingua",
- "that_a_wrap": "È tutto!\n\nSe ti è piaciuto questo thread:\n\n1. Seguimi su @{{username}} per altri contenuti come questo\n2. Ritwitta il tweet qui sotto per condividere questo thread con il tuo pubblico\n"
+ "that_a_wrap": "È tutto!\n\nSe ti è piaciuto questo thread:\n\n1. Seguimi su @{{username}} per altri contenuti come questo\n2. Ritwitta il tweet qui sotto per condividere questo thread con il tuo pubblico\n",
+ "post_as_images_carousel": "Pubblica come carosello di immagini"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/ja/translation.json b/libraries/react-shared-libraries/src/translation/locales/ja/translation.json
index 65931c65..93ea8f94 100644
--- a/libraries/react-shared-libraries/src/translation/locales/ja/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/ja/translation.json
@@ -482,5 +482,6 @@
"90_days": "90日間",
"start_7_days_free_trial": "7日間の無料トライアルを開始",
"change_language": "言語を変更",
- "that_a_wrap": "以上で終了です!\n\nこのスレッドを楽しんでいただけたなら:\n\n1. @{{username}} をフォローして、さらに多くの投稿をご覧ください\n2. 下のツイートをリツイートして、このスレッドをあなたのフォロワーと共有してください\n"
+ "that_a_wrap": "以上で終了です!\n\nこのスレッドを楽しんでいただけたなら:\n\n1. @{{username}} をフォローして、さらに多くの投稿をご覧ください\n2. 下のツイートをリツイートして、このスレッドをあなたのフォロワーと共有してください\n",
+ "post_as_images_carousel": "画像カルーセルとして投稿"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/ko/translation.json b/libraries/react-shared-libraries/src/translation/locales/ko/translation.json
index 0dcd2698..99fb60b7 100644
--- a/libraries/react-shared-libraries/src/translation/locales/ko/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/ko/translation.json
@@ -482,5 +482,6 @@
"90_days": "90일",
"start_7_days_free_trial": "7일 무료 체험 시작하기",
"change_language": "언어 변경",
- "that_a_wrap": "여기까지입니다!\n\n이 스레드가 유익하셨다면:\n\n1. 더 많은 정보를 원하시면 @{{username}}를 팔로우하세요\n2. 아래 트윗을 리트윗해서 이 스레드를 여러분의 팔로워들과 공유하세요\n"
+ "that_a_wrap": "여기까지입니다!\n\n이 스레드가 유익하셨다면:\n\n1. 더 많은 정보를 원하시면 @{{username}}를 팔로우하세요\n2. 아래 트윗을 리트윗해서 이 스레드를 여러분의 팔로워들과 공유하세요\n",
+ "post_as_images_carousel": "이미지 캐러셀로 게시"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/pt/translation.json b/libraries/react-shared-libraries/src/translation/locales/pt/translation.json
index 2706a561..add488d8 100644
--- a/libraries/react-shared-libraries/src/translation/locales/pt/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/pt/translation.json
@@ -482,5 +482,6 @@
"90_days": "90 Dias",
"start_7_days_free_trial": "Comece o teste gratuito de 7 dias",
"change_language": "Mudar idioma",
- "that_a_wrap": "É isso aí!\n\nSe você gostou deste fio:\n\n1. Siga-me @{{username}} para ver mais conteúdos como este\n2. Dê RT no tweet abaixo para compartilhar este fio com seu público\n"
+ "that_a_wrap": "É isso aí!\n\nSe você gostou deste fio:\n\n1. Siga-me @{{username}} para ver mais conteúdos como este\n2. Dê RT no tweet abaixo para compartilhar este fio com seu público\n",
+ "post_as_images_carousel": "Publicar como carrossel de imagens"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/ru/translation.json b/libraries/react-shared-libraries/src/translation/locales/ru/translation.json
index 3c1ceebb..1547ddd7 100644
--- a/libraries/react-shared-libraries/src/translation/locales/ru/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/ru/translation.json
@@ -482,5 +482,6 @@
"90_days": "90 дней",
"start_7_days_free_trial": "Начать 7-дневную бесплатную пробную версию",
"change_language": "Сменить язык",
- "that_a_wrap": "На этом всё!\n\nЕсли вам понравилась эта серия:\n\n1. Подпишитесь на меня @{{username}}, чтобы не пропустить новые посты\n2. Ретвитните твит ниже, чтобы поделиться этой серией со своей аудиторией\n"
+ "that_a_wrap": "На этом всё!\n\nЕсли вам понравилась эта серия:\n\n1. Подпишитесь на меня @{{username}}, чтобы не пропустить новые посты\n2. Ретвитните твит ниже, чтобы поделиться этой серией со своей аудиторией\n",
+ "post_as_images_carousel": "Опубликовать как карусель изображений"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/tr/translation.json b/libraries/react-shared-libraries/src/translation/locales/tr/translation.json
index 681a913b..054f97e5 100644
--- a/libraries/react-shared-libraries/src/translation/locales/tr/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/tr/translation.json
@@ -482,5 +482,6 @@
"90_days": "90 Gün",
"start_7_days_free_trial": "7 gün ücretsiz denemeyi başlat",
"change_language": "Dili Değiştir",
- "that_a_wrap": "Bu iş burada bitti!\n\nEğer bu diziyi beğendiyseniz:\n\n1. Daha fazlası için beni @{{username}} hesabından takip edin\n2. Aşağıdaki tweet'i RT'leyerek bu diziyi kendi kitlenizle paylaşın\n"
+ "that_a_wrap": "Bu iş burada bitti!\n\nEğer bu diziyi beğendiyseniz:\n\n1. Daha fazlası için beni @{{username}} hesabından takip edin\n2. Aşağıdaki tweet'i RT'leyerek bu diziyi kendi kitlenizle paylaşın\n",
+ "post_as_images_carousel": "Görselleri kaydırmalı gönder olarak paylaş"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/vi/translation.json b/libraries/react-shared-libraries/src/translation/locales/vi/translation.json
index 3339a76e..8e601f7f 100644
--- a/libraries/react-shared-libraries/src/translation/locales/vi/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/vi/translation.json
@@ -482,5 +482,6 @@
"90_days": "90 ngày",
"start_7_days_free_trial": "Bắt đầu dùng thử miễn phí 7 ngày",
"change_language": "Thay đổi ngôn ngữ",
- "that_a_wrap": "Kết thúc rồi!\n\nNếu bạn thích chuỗi bài này:\n\n1. Hãy theo dõi tôi @{{username}} để xem thêm nhiều nội dung như vậy\n2. Retweet bài bên dưới để chia sẻ chuỗi này với mọi người\n"
+ "that_a_wrap": "Kết thúc rồi!\n\nNếu bạn thích chuỗi bài này:\n\n1. Hãy theo dõi tôi @{{username}} để xem thêm nhiều nội dung như vậy\n2. Retweet bài bên dưới để chia sẻ chuỗi này với mọi người\n",
+ "post_as_images_carousel": "Đăng dưới dạng băng chuyền hình ảnh"
}
diff --git a/libraries/react-shared-libraries/src/translation/locales/zh/translation.json b/libraries/react-shared-libraries/src/translation/locales/zh/translation.json
index b4fa9ae7..852765ab 100644
--- a/libraries/react-shared-libraries/src/translation/locales/zh/translation.json
+++ b/libraries/react-shared-libraries/src/translation/locales/zh/translation.json
@@ -482,5 +482,6 @@
"90_days": "90天",
"start_7_days_free_trial": "开始7天免费试用",
"change_language": "切换语言",
- "that_a_wrap": "本帖到此结束!\n\n如果你喜欢这个话题:\n\n1. 关注我 @{{username}},获取更多类似内容\n2. 转发下方推文,与更多人分享本帖\n"
+ "that_a_wrap": "本帖到此结束!\n\n如果你喜欢这个话题:\n\n1. 关注我 @{{username}},获取更多类似内容\n2. 转发下方推文,与更多人分享本帖\n",
+ "post_as_images_carousel": "以图片轮播的形式发布"
}
diff --git a/package.json b/package.json
index 49e517bf..cbd0e479 100644
--- a/package.json
+++ b/package.json
@@ -149,6 +149,7 @@
"i18next": "^25.2.1",
"i18next-browser-languagedetector": "^8.1.0",
"i18next-resources-to-backend": "^1.2.1",
+ "image-to-pdf": "^3.0.2",
"ioredis": "^5.3.2",
"json-to-graphql-query": "^2.2.5",
"jsonwebtoken": "^9.0.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0fdd83a3..28f9e426 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -324,6 +324,9 @@ importers:
i18next-resources-to-backend:
specifier: ^1.2.1
version: 1.2.1
+ image-to-pdf:
+ specifier: ^3.0.2
+ version: 3.0.2
ioredis:
specifier: ^5.3.2
version: 5.6.1
@@ -5552,6 +5555,9 @@ packages:
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+ '@swc/helpers@0.3.17':
+ resolution: {integrity: sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==}
+
'@swc/helpers@0.5.13':
resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==}
@@ -7182,6 +7188,10 @@ packages:
base-x@5.0.1:
resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==}
+ base64-js@0.0.8:
+ resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==}
+ engines: {node: '>= 0.4'}
+
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@@ -7290,6 +7300,9 @@ packages:
brorand@1.1.0:
resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==}
+ brotli@1.3.3:
+ resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==}
+
browserify-aes@1.2.0:
resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==}
@@ -8202,6 +8215,10 @@ packages:
resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==}
engines: {node: '>= 0.4'}
+ deep-equal@2.2.3:
+ resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==}
+ engines: {node: '>= 0.4'}
+
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -8318,6 +8335,9 @@ packages:
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+ dfa@1.2.0:
+ resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==}
+
diacritics@1.3.0:
resolution: {integrity: sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==}
@@ -8555,6 +8575,9 @@ packages:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
+ es-get-iterator@1.1.3:
+ resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==}
+
es-iterator-helpers@1.2.1:
resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
engines: {node: '>= 0.4'}
@@ -9137,6 +9160,9 @@ packages:
debug:
optional: true
+ fontkit@1.9.0:
+ resolution: {integrity: sha512-HkW/8Lrk8jl18kzQHvAw9aTHe1cqsyx5sDnxncx652+CIfhawokEPkeM3BoIC+z/Xv7a0yMr0f3pRRwhGH455g==}
+
for-each@0.3.5:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
@@ -9845,6 +9871,9 @@ packages:
engines: {node: '>=16.x'}
hasBin: true
+ image-to-pdf@3.0.2:
+ resolution: {integrity: sha512-6/IQCt4f384zjQ1w8P7FHIN/tF0mau8RbAIydT/+wyfZ1RAb8E2fiKe9t/k0V880h0d3zRpw9Q1bM5AIgVL/4g==}
+
immer@9.0.21:
resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==}
@@ -10453,6 +10482,9 @@ packages:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
+ jpeg-exif@1.1.4:
+ resolution: {integrity: sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==}
+
js-base64@3.7.7:
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
@@ -10932,6 +10964,9 @@ packages:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
+ linebreak@1.1.0:
+ resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==}
+
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@@ -11719,10 +11754,12 @@ packages:
multer@1.4.4-lts.1:
resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==}
engines: {node: '>= 6.0.0'}
+ deprecated: Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.
multer@1.4.5-lts.2:
resolution: {integrity: sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==}
engines: {node: '>= 6.0.0'}
+ deprecated: Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.
multicast-dns@7.2.5:
resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==}
@@ -12248,6 +12285,9 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+ pako@0.2.9:
+ resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
+
param-case@3.0.4:
resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
@@ -12370,6 +12410,9 @@ packages:
resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==}
engines: {node: '>=0.12'}
+ pdfkit@0.15.2:
+ resolution: {integrity: sha512-s3GjpdBFSCaeDSX/v73MI5UsPqH1kjKut2AXCgxQ5OH10lPVOu5q5vLAG0OCpz/EYqKsTSw1WHpENqMvp43RKg==}
+
peberminta@0.9.0:
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
@@ -12468,6 +12511,9 @@ packages:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
+ png-js@1.0.0:
+ resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==}
+
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
@@ -13508,6 +13554,9 @@ packages:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
+ restructure@2.0.1:
+ resolution: {integrity: sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==}
+
retry-axios@2.6.0:
resolution: {integrity: sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==}
engines: {node: '>=10.7.0'}
@@ -14038,6 +14087,10 @@ packages:
resolution: {integrity: sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==}
engines: {node: '>=0.10.0'}
+ stop-iteration-iterator@1.1.0:
+ resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
+ engines: {node: '>= 0.4'}
+
store2@2.14.4:
resolution: {integrity: sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==}
@@ -14407,6 +14460,9 @@ packages:
tiny-case@1.0.3:
resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==}
+ tiny-inflate@1.0.3:
+ resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
+
tiny-invariant@1.0.6:
resolution: {integrity: sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==}
@@ -14813,10 +14869,16 @@ packages:
resolution: {integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==}
engines: {node: '>=4'}
+ unicode-properties@1.4.1:
+ resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==}
+
unicode-property-aliases-ecmascript@2.1.0:
resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==}
engines: {node: '>=4'}
+ unicode-trie@2.0.0:
+ resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
+
unidragger@3.0.1:
resolution: {integrity: sha512-RngbGSwBFmqGBWjkaH+yB677uzR95blSQyxq6hYbrQCejH3Mx1nm8DVOuh3M9k2fQyTstWUG5qlgCnNqV/9jVw==}
@@ -19540,6 +19602,21 @@ snapshots:
- typescript
- verdaccio
+ '@nrwl/js@19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(typescript@5.5.4)':
+ dependencies:
+ '@nx/js': 19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(typescript@5.5.4)
+ transitivePeerDependencies:
+ - '@babel/traverse'
+ - '@swc-node/register'
+ - '@swc/core'
+ - '@swc/wasm'
+ - '@types/node'
+ - debug
+ - nx
+ - supports-color
+ - typescript
+ - verdaccio
+
'@nrwl/nest@19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(chokidar@3.5.3)(eslint@8.57.0)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(ts-node@10.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(typescript@5.5.4))(typescript@5.5.4)':
dependencies:
'@nx/nest': 19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(chokidar@3.5.3)(eslint@8.57.0)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(ts-node@10.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(typescript@5.5.4))(typescript@5.5.4)
@@ -19886,7 +19963,7 @@ snapshots:
'@babel/preset-env': 7.27.1(@babel/core@7.27.1)
'@babel/preset-typescript': 7.27.1(@babel/core@7.27.1)
'@babel/runtime': 7.27.1
- '@nrwl/js': 19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(typescript@5.4.5)
+ '@nrwl/js': 19.7.2(@babel/traverse@7.27.1)(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))(@types/node@18.16.9)(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))(typescript@5.5.4)
'@nx/devkit': 19.7.2(nx@19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13)))
'@nx/workspace': 19.7.2(@swc-node/register@1.9.2(@swc/core@1.5.7(@swc/helpers@0.5.13))(@swc/types@0.1.7)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.13))
babel-plugin-const-enum: 1.2.0(@babel/core@7.27.1)
@@ -22453,6 +22530,10 @@ snapshots:
'@swc/counter@0.1.3': {}
+ '@swc/helpers@0.3.17':
+ dependencies:
+ tslib: 2.8.1
+
'@swc/helpers@0.5.13':
dependencies:
tslib: 2.8.1
@@ -24777,6 +24858,8 @@ snapshots:
base-x@5.0.1: {}
+ base64-js@0.0.8: {}
+
base64-js@1.5.1: {}
base64url@3.0.1: {}
@@ -24909,6 +24992,10 @@ snapshots:
brorand@1.1.0: {}
+ brotli@1.3.3:
+ dependencies:
+ base64-js: 1.5.1
+
browserify-aes@1.2.0:
dependencies:
buffer-xor: 1.0.3
@@ -25940,6 +26027,27 @@ snapshots:
object-keys: 1.1.1
regexp.prototype.flags: 1.5.4
+ deep-equal@2.2.3:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ call-bind: 1.0.8
+ es-get-iterator: 1.1.3
+ get-intrinsic: 1.3.0
+ is-arguments: 1.2.0
+ is-array-buffer: 3.0.5
+ is-date-object: 1.1.0
+ is-regex: 1.2.1
+ is-shared-array-buffer: 1.0.4
+ isarray: 2.0.5
+ object-is: 1.1.6
+ object-keys: 1.1.1
+ object.assign: 4.1.7
+ regexp.prototype.flags: 1.5.4
+ side-channel: 1.1.0
+ which-boxed-primitive: 1.1.1
+ which-collection: 1.0.2
+ which-typed-array: 1.1.19
+
deep-is@0.1.4: {}
deepmerge@2.2.1: {}
@@ -26031,6 +26139,8 @@ snapshots:
dependencies:
dequal: 2.0.3
+ dfa@1.2.0: {}
+
diacritics@1.3.0: {}
didyoumean@1.2.2: {}
@@ -26320,6 +26430,18 @@ snapshots:
es-errors@1.3.0: {}
+ es-get-iterator@1.1.3:
+ dependencies:
+ call-bind: 1.0.8
+ get-intrinsic: 1.3.0
+ has-symbols: 1.1.0
+ is-arguments: 1.2.0
+ is-map: 2.0.3
+ is-set: 2.0.3
+ is-string: 1.1.1
+ isarray: 2.0.5
+ stop-iteration-iterator: 1.1.0
+
es-iterator-helpers@1.2.1:
dependencies:
call-bind: 1.0.8
@@ -27174,6 +27296,18 @@ snapshots:
optionalDependencies:
debug: 4.4.0(supports-color@5.5.0)
+ fontkit@1.9.0:
+ dependencies:
+ '@swc/helpers': 0.3.17
+ brotli: 1.3.3
+ clone: 2.1.2
+ deep-equal: 2.2.3
+ dfa: 1.2.0
+ restructure: 2.0.1
+ tiny-inflate: 1.0.3
+ unicode-properties: 1.4.1
+ unicode-trie: 2.0.0
+
for-each@0.3.5:
dependencies:
is-callable: 1.2.7
@@ -28190,6 +28324,10 @@ snapshots:
dependencies:
queue: 6.0.2
+ image-to-pdf@3.0.2:
+ dependencies:
+ pdfkit: 0.15.2
+
immer@9.0.21: {}
immutable@4.3.7: {}
@@ -29026,6 +29164,8 @@ snapshots:
joycon@3.1.1: {}
+ jpeg-exif@1.1.4: {}
+
js-base64@3.7.7: {}
js-beautify@1.15.4:
@@ -29516,6 +29656,11 @@ snapshots:
lilconfig@3.1.3: {}
+ linebreak@1.1.0:
+ dependencies:
+ base64-js: 0.0.8
+ unicode-trie: 2.0.0
+
lines-and-columns@1.2.4: {}
lines-and-columns@2.0.3: {}
@@ -31399,6 +31544,8 @@ snapshots:
package-json-from-dist@1.0.1: {}
+ pako@0.2.9: {}
+
param-case@3.0.4:
dependencies:
dot-case: 3.0.4
@@ -31529,6 +31676,14 @@ snapshots:
safe-buffer: 5.2.1
sha.js: 2.4.11
+ pdfkit@0.15.2:
+ dependencies:
+ crypto-js: 4.2.0
+ fontkit: 1.9.0
+ jpeg-exif: 1.1.4
+ linebreak: 1.1.0
+ png-js: 1.0.0
+
peberminta@0.9.0: {}
peek-readable@4.1.0: {}
@@ -31636,6 +31791,8 @@ snapshots:
pluralize@8.0.0: {}
+ png-js@1.0.0: {}
+
pngjs@5.0.0: {}
polotno@2.22.2(@types/react@18.3.1)(@types/sortablejs@1.15.8)(react-dom@18.3.1(react@18.3.1))(react-native@0.79.2(@babel/core@7.27.1)(@types/react@18.3.1)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1):
@@ -32890,6 +33047,8 @@ snapshots:
onetime: 5.1.2
signal-exit: 3.0.7
+ restructure@2.0.1: {}
+
retry-axios@2.6.0(axios@1.9.0):
dependencies:
axios: 1.9.0(debug@4.4.0)
@@ -33539,6 +33698,11 @@ snapshots:
stealthy-require@1.1.1: {}
+ stop-iteration-iterator@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ internal-slot: 1.1.0
+
store2@2.14.4: {}
storybook-source-link@4.0.1(@storybook/addons@7.6.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
@@ -33966,6 +34130,8 @@ snapshots:
tiny-case@1.0.3: {}
+ tiny-inflate@1.0.3: {}
+
tiny-invariant@1.0.6: {}
tiny-invariant@1.2.0: {}
@@ -34362,8 +34528,18 @@ snapshots:
unicode-match-property-value-ecmascript@2.2.0: {}
+ unicode-properties@1.4.1:
+ dependencies:
+ base64-js: 1.5.1
+ unicode-trie: 2.0.0
+
unicode-property-aliases-ecmascript@2.1.0: {}
+ unicode-trie@2.0.0:
+ dependencies:
+ pako: 0.2.9
+ tiny-inflate: 1.0.3
+
unidragger@3.0.1:
dependencies:
ev-emitter: 2.1.2