Merge pull request #786 from gitroomhq/feat/carusel

LinkedIn post as carousel
This commit is contained in:
Nevo David 2025-06-08 16:05:57 +07:00 committed by GitHub
commit 9192cc4c85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 590 additions and 220 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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 (
<div className="mb-[20px]">
<Checkbox
variant="hollow"
label={t('post_as_images_carousel', 'Post as images carousel')}
{...register('post_as_images_carousel', {
value: false,
})}
/>
</div>
);
};
export default withProvider<LinkedinDto>(
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)

View File

@ -487,3 +487,4 @@ checksums:
start_7_days_free_trial: e9c42510c2cc750fabe704ebc0a9e768
change_language: c798f65b78e23b2cf8fc29a1a24a182f
that_a_wrap: 0ecf5b5a1fbac9c2653f2642baf5d4a5
post_as_images_carousel: 2f82f0f6adbf03abfeec3389800d7232

View File

@ -0,0 +1,7 @@
import { IsBoolean, IsOptional } from 'class-validator';
export class LinkedinDto {
@IsBoolean()
@IsOptional()
post_as_images_carousel: boolean;
}

View File

@ -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<LinkedinDto>[],
firstPost: PostDetails<LinkedinDto>
): Promise<PostDetails<LinkedinDto>[]> {
// 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<Buffer> {
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<LinkedinDto>[],
accessToken: string,
postDetails: PostDetails[],
integration: Integration,
type = 'personal' as 'company' | 'personal'
): Promise<PostResponse[]> {
const [firstPost, ...restPosts] = postDetails;
personId: string,
type: 'company' | 'personal'
): Promise<Record<string, string[]>> {
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<string, string[]>);
}
const media_ids = (uploadAll[firstPost.id] || []).filter((f) => f);
private async prepareMediaBuffer(mediaUrl: string): Promise<Buffer> {
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<string> {
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<string> {
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<LinkedinDto>[],
integration: Integration,
type = 'personal' as 'company' | 'personal'
): Promise<PostResponse[]> {
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({

View File

@ -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": "انشر كعرض شرائح للصور"
}

View File

@ -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": "ছবির ক্যারোসেল হিসেবে পোস্ট করুন"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -482,5 +482,6 @@
"90_days": "90 jours",
"start_7_days_free_trial": "Commencez lessai 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 dimages"
}

View File

@ -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": "פרסם כתמונות בגלריה"
}

View File

@ -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"
}

View File

@ -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": "画像カルーセルとして投稿"
}

View File

@ -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": "이미지 캐러셀로 게시"
}

View File

@ -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"
}

View File

@ -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": "Опубликовать как карусель изображений"
}

View File

@ -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ş"
}

View File

@ -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"
}

View File

@ -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": "以图片轮播的形式发布"
}

View File

@ -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",

View File

@ -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