Wire Telegram into the post composer (preview, content type, logo)

This commit is contained in:
Paulo Castellano 2026-06-13 22:10:12 -03:00
parent b504243212
commit f378469842
9 changed files with 154 additions and 0 deletions

View file

@ -484,6 +484,10 @@
'label' => 'Post',
'description' => 'Text post with optional media',
],
'telegram_post' => [
'label' => 'Post',
'description' => 'Text post with optional media',
],
],
'platforms' => [
@ -586,6 +590,7 @@
'bluesky_post' => 'Bluesky Post',
'threads_post' => 'Threads Post',
'mastodon_post' => 'Mastodon Post',
'telegram_post' => 'Telegram Post',
'facebook_post' => 'Facebook Post',
'pinterest_pin' => 'Pinterest Pin',
'instagram_story' => 'Instagram Story',

View file

@ -484,6 +484,10 @@
'label' => 'Post',
'description' => 'Post de texto con multimedia opcional',
],
'telegram_post' => [
'label' => 'Post',
'description' => 'Post de texto con multimedia opcional',
],
],
'platforms' => [
@ -587,6 +591,7 @@
'bluesky_post' => 'Post en Bluesky',
'threads_post' => 'Post en Threads',
'mastodon_post' => 'Post en Mastodon',
'telegram_post' => 'Post en Telegram',
'facebook_post' => 'Post en Facebook',
'pinterest_pin' => 'Pin de Pinterest',
'instagram_story' => 'Story de Instagram',

View file

@ -484,6 +484,10 @@
'label' => 'Post',
'description' => 'Post de texto com mídia opcional',
],
'telegram_post' => [
'label' => 'Post',
'description' => 'Post de texto com mídia opcional',
],
],
'platforms' => [
@ -586,6 +590,7 @@
'bluesky_post' => 'Post no Bluesky',
'threads_post' => 'Post no Threads',
'mastodon_post' => 'Post no Mastodon',
'telegram_post' => 'Post no Telegram',
'facebook_post' => 'Post no Facebook',
'pinterest_pin' => 'Pin no Pinterest',
'instagram_story' => 'Story do Instagram',

View file

@ -121,6 +121,7 @@ const getProfileUrl = (platform: string, username: string | null, platformUserId
'threads': `https://threads.net/@${username}`,
'bluesky': `https://bsky.app/profile/${username}`,
'pinterest': `https://pinterest.com/${username}`,
'telegram': `https://t.me/${username}`,
};
return urls[platform] || null;
};

View file

@ -9,6 +9,7 @@ import InstagramPreview from './InstagramPreview.vue';
import LinkedInPreview from './LinkedInPreview.vue';
import MastodonPreview from './MastodonPreview.vue';
import PinterestPreview from './PinterestPreview.vue';
import TelegramPreview from './TelegramPreview.vue';
import ThreadsPreview from './ThreadsPreview.vue';
import TikTokPreview from './TikTokPreview.vue';
import XPreview from './XPreview.vue';
@ -65,6 +66,8 @@ const previewComponent = computed(() => {
return BlueskyPreview;
case 'mastodon':
return MastodonPreview;
case 'telegram':
return TelegramPreview;
default:
return LinkedInPreview;
}

View file

@ -0,0 +1,130 @@
<script setup lang="ts">
import VideoPreview from '@/components/posts/previews/VideoPreview.vue';
import { isVideoMedia } from '@/composables/useMedia';
import type { MediaItem } from '@/types/media';
interface SocialAccount {
id: string;
platform: string;
display_name: string;
username: string;
avatar_url: string | null;
}
interface Props {
socialAccount: SocialAccount;
content: string;
media: MediaItem[];
}
defineProps<Props>();
</script>
<template>
<div
class="flex h-full w-full flex-col overflow-hidden bg-[#a4bce0] dark:bg-[#0e1621]"
>
<!-- Header -->
<div
class="flex items-center gap-3 bg-white px-4 py-2.5 dark:bg-[#17212b]"
>
<img
v-if="socialAccount.avatar_url"
:src="socialAccount.avatar_url"
:alt="socialAccount.display_name"
class="h-9 w-9 rounded-full object-cover"
/>
<div
v-else
class="flex h-9 w-9 items-center justify-center rounded-full bg-gradient-to-br from-[#2aabee] to-[#229ed9] font-semibold text-white"
>
{{ socialAccount.display_name?.charAt(0) }}
</div>
<div class="min-w-0 flex-1">
<div
class="truncate text-[15px] font-semibold text-[#1f232b] dark:text-white"
>
{{ socialAccount.display_name || 'Channel' }}
</div>
<div class="text-[13px] text-[#707991] dark:text-[#708499]">
channel
</div>
</div>
</div>
<!-- Chat area -->
<div class="flex-1 overflow-y-auto px-3 py-4">
<div
class="max-w-[85%] overflow-hidden rounded-2xl rounded-bl-md bg-white shadow-sm dark:bg-[#182533]"
>
<!-- Media -->
<div v-if="media.length > 0">
<div
class="overflow-hidden"
:class="{
'grid grid-cols-2 gap-0.5': media.length >= 2,
}"
>
<div
v-for="(item, index) in media.slice(0, 4)"
:key="item.id"
class="relative overflow-hidden"
:class="{
'aspect-[4/3]': media.length === 1,
'aspect-square': media.length > 1,
}"
>
<img
v-if="!isVideoMedia(item)"
:src="item.url"
:alt="item.original_filename"
class="h-full w-full object-cover"
/>
<VideoPreview
v-else
:src="item.url"
video-class="w-full h-full object-cover bg-black"
/>
<div
v-if="media.length > 4 && index === 3"
class="absolute inset-0 flex items-center justify-center bg-black/60"
>
<span class="text-xl font-semibold text-white"
>+{{ media.length - 4 }}</span
>
</div>
</div>
</div>
</div>
<!-- Text + meta -->
<div class="px-3 py-2">
<div
v-if="content"
class="text-[15px] leading-[20px] whitespace-pre-wrap text-[#1f232b] dark:text-white"
>
{{ content }}
</div>
<div
class="mt-1 flex items-center justify-end gap-1 text-[12px] text-[#707991] dark:text-[#708499]"
>
<svg
class="h-3.5 w-3.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z"
/>
<circle cx="12" cy="12" r="3" />
</svg>
<span>0</span>
<span class="ml-1">4:30 PM</span>
</div>
</div>
</div>
</div>
</div>
</template>

View file

@ -11,6 +11,7 @@ const PLATFORM_LOGOS: Record<string, string> = {
bluesky: '/images/accounts/bluesky.png',
pinterest: '/images/accounts/pinterest.png',
mastodon: '/images/accounts/mastodon.png',
telegram: '/images/accounts/telegram.svg',
};
const PLATFORM_LABELS: Record<string, string> = {
@ -26,6 +27,7 @@ const PLATFORM_LABELS: Record<string, string> = {
bluesky: 'Bluesky',
pinterest: 'Pinterest',
mastodon: 'Mastodon',
telegram: 'Telegram',
};
const PLATFORM_CONTENT_TYPES: Record<string, string[]> = {
@ -41,6 +43,7 @@ const PLATFORM_CONTENT_TYPES: Record<string, string[]> = {
pinterest: ['pinterest_pin', 'pinterest_video_pin', 'pinterest_carousel'],
bluesky: ['bluesky_post'],
mastodon: ['mastodon_post'],
telegram: ['telegram_post'],
};
export interface ContentTypeOption {

View file

@ -19,6 +19,7 @@ export const ContentType = {
PinterestCarousel: 'pinterest_carousel',
BlueskyPost: 'bluesky_post',
MastodonPost: 'mastodon_post',
TelegramPost: 'telegram_post',
} as const;
export type ContentTypeValue = (typeof ContentType)[keyof typeof ContentType];

View file

@ -11,6 +11,7 @@ export const Platform = {
Pinterest: 'pinterest',
Bluesky: 'bluesky',
Mastodon: 'mastodon',
Telegram: 'telegram',
} as const;
export type PlatformValue = (typeof Platform)[keyof typeof Platform];