fix(posts): preserve gallery media metadata and polish LinkedIn PDF settings
Selecting media in the gallery dropped original_filename, size, and meta when building the picked item, so they never reached the post. Since the gallery is the only way to add media, this left PDF documents without a filename (empty title field, 'PDF' thumbnail label, 'Document' fallback on publish) and skipped size/duration validation for all gallery media. Carry those fields through (guarded for unsplash/giphy items that lack them). LinkedIn settings module: render only when a PDF is attached (its only relevant config now that format is inferred from media), pre-fill the document title from the PDF filename (editable), drop the redundant media warning (already surfaced on the channel) and the unused contentType prop/binding. LinkedInSelect: add Person/Organization tag icons. i18n: clearer document-not-alone message in all locales.
This commit is contained in:
parent
6184807aba
commit
428b8a5b27
7 changed files with 32 additions and 29 deletions
|
|
@ -187,7 +187,7 @@
|
|||
'no_image_allowed' => 'This post type accepts only videos.',
|
||||
'no_document_allowed' => 'This post type does not accept PDF documents.',
|
||||
'no_mixed_media' => "Images and a video can't be combined in the same post.",
|
||||
'document_not_alone' => 'A PDF must be the only attachment.',
|
||||
'document_not_alone' => 'A PDF must be posted on its own, without other images or videos.',
|
||||
'gif_not_allowed' => 'This platform does not accept GIF. Remove the GIF or choose a different network.',
|
||||
'image_too_large' => 'Image exceeds the :max limit for this post type (yours is :current).',
|
||||
'video_too_large' => 'Video exceeds the :max limit for this post type (yours is :current).',
|
||||
|
|
@ -346,7 +346,7 @@
|
|||
'no_mixed_media' => "Images and videos can't be combined in the same post.",
|
||||
'no_gifs' => 'GIFs are not supported here.',
|
||||
'no_documents' => 'PDF documents are not supported by this format.',
|
||||
'document_not_alone' => 'A PDF must be the only attachment.',
|
||||
'document_not_alone' => 'A PDF must be posted on its own, without other images or videos.',
|
||||
'video_too_large' => 'Video exceeds the size limit for this platform.',
|
||||
'video_too_long' => 'Video must be under :seconds seconds for this format.',
|
||||
'image_too_large' => 'Image exceeds the size limit for this platform.',
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@
|
|||
'no_image_allowed' => 'Este tipo acepta solo videos.',
|
||||
'no_document_allowed' => 'Este tipo no acepta documentos PDF.',
|
||||
'no_mixed_media' => 'No se pueden combinar imágenes y un video en la misma publicación.',
|
||||
'document_not_alone' => 'Un PDF debe ser el único archivo adjunto.',
|
||||
'document_not_alone' => 'Un PDF debe publicarse solo, sin otras imágenes ni videos.',
|
||||
'gif_not_allowed' => 'Esta red no acepta GIF. Elimínalo o selecciona otra red.',
|
||||
'image_too_large' => 'La imagen supera el límite de :max (la tuya es :current).',
|
||||
'video_too_large' => 'El video supera el límite de :max (el tuyo es :current).',
|
||||
|
|
@ -346,7 +346,7 @@
|
|||
'no_mixed_media' => 'No se pueden combinar imágenes y videos en la misma publicación.',
|
||||
'no_gifs' => 'Los GIFs no son compatibles aquí.',
|
||||
'no_documents' => 'Los documentos PDF no son compatibles con este formato.',
|
||||
'document_not_alone' => 'Un PDF debe ser el único archivo adjunto.',
|
||||
'document_not_alone' => 'Un PDF debe publicarse solo, sin otras imágenes ni videos.',
|
||||
'video_too_large' => 'El video supera el límite de tamaño de esta plataforma.',
|
||||
'video_too_long' => 'El video debe durar menos de :seconds segundos en este formato.',
|
||||
'image_too_large' => 'La imagen supera el límite de tamaño de esta plataforma.',
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@
|
|||
'no_image_allowed' => 'Este tipo aceita apenas vídeos.',
|
||||
'no_document_allowed' => 'Este tipo não aceita documentos PDF.',
|
||||
'no_mixed_media' => 'Não dá pra combinar imagens e um vídeo no mesmo post.',
|
||||
'document_not_alone' => 'Um PDF deve ser o único anexo.',
|
||||
'document_not_alone' => 'Um PDF precisa ser publicado sozinho, sem outras imagens ou vídeos.',
|
||||
'gif_not_allowed' => 'Esta rede não aceita GIF. Remova o GIF ou escolha outra rede.',
|
||||
'image_too_large' => 'A imagem passa do limite de :max (a sua tem :current).',
|
||||
'video_too_large' => 'O vídeo passa do limite de :max (o seu tem :current).',
|
||||
|
|
@ -346,7 +346,7 @@
|
|||
'no_mixed_media' => 'Não é possível combinar imagem e vídeo na mesma publicação.',
|
||||
'no_gifs' => 'GIFs não são suportados aqui.',
|
||||
'no_documents' => 'Documentos PDF não são suportados por este formato.',
|
||||
'document_not_alone' => 'Um PDF deve ser o único anexo.',
|
||||
'document_not_alone' => 'Um PDF precisa ser publicado sozinho, sem outras imagens ou vídeos.',
|
||||
'video_too_large' => 'Vídeo excede o limite de tamanho desta plataforma.',
|
||||
'video_too_long' => 'Vídeo deve ter menos de :seconds segundos neste formato.',
|
||||
'image_too_large' => 'Imagem excede o limite de tamanho desta plataforma.',
|
||||
|
|
|
|||
|
|
@ -163,7 +163,6 @@ const selectedChannels = computed(() => props.channels.filter((channel) => isSel
|
|||
v-else-if="channel.platform === Platform.LinkedIn || channel.platform === Platform.LinkedInPage"
|
||||
:social-account="channel.socialAccount"
|
||||
:platform="channel.platform"
|
||||
:content-type="channel.contentType"
|
||||
:media="media"
|
||||
:meta="channel.meta"
|
||||
:disabled="disabled"
|
||||
|
|
|
|||
|
|
@ -140,6 +140,9 @@ const toggleSelect = (asset: AssetMedia | SavedMedia, extra?: Partial<PickedMedi
|
|||
url: asset.url,
|
||||
type: asset.type,
|
||||
mime_type: asset.mime_type,
|
||||
original_filename: 'original_filename' in asset ? asset.original_filename : undefined,
|
||||
size: 'size' in asset ? asset.size : undefined,
|
||||
meta: 'meta' in asset ? (asset.meta ?? undefined) : undefined,
|
||||
...extra,
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { IconAlertTriangle, IconChevronDown, IconChevronUp } from '@tabler/icons-vue';
|
||||
import { IconChevronDown, IconChevronUp } from '@tabler/icons-vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { getMediaValidationWarning, isDocumentMedia } from '@/composables/useMedia';
|
||||
import { isDocumentMedia } from '@/composables/useMedia';
|
||||
import { getPlatformLogo } from '@/composables/usePlatformLogo';
|
||||
import type { MediaItem } from '@/types/media';
|
||||
import { Platform } from '@/types/platform';
|
||||
|
|
@ -20,7 +20,6 @@ interface SocialAccount {
|
|||
interface Props {
|
||||
socialAccount: SocialAccount | null;
|
||||
platform: string;
|
||||
contentType: string;
|
||||
media: MediaItem[];
|
||||
meta?: Record<string, any>;
|
||||
disabled?: boolean;
|
||||
|
|
@ -41,20 +40,23 @@ const open = ref(false);
|
|||
|
||||
const isPage = computed(() => props.platform === Platform.LinkedInPage);
|
||||
|
||||
// The publish format follows the media; the only thing to configure is the
|
||||
// title shown on a document (PDF) post, so we only expose it when a PDF is set.
|
||||
const hasPdf = computed(() => props.media.some((item) => isDocumentMedia(item)));
|
||||
const pdfDocument = computed(
|
||||
() => props.media.find((item) => isDocumentMedia(item)) ?? null,
|
||||
);
|
||||
|
||||
const hasPdf = computed(() => pdfDocument.value !== null);
|
||||
|
||||
const documentTitle = computed({
|
||||
get: () => (props.meta?.document_title as string | undefined) ?? '',
|
||||
get: () =>
|
||||
(props.meta?.document_title as string | undefined) ||
|
||||
pdfDocument.value?.original_filename ||
|
||||
'',
|
||||
set: (value: string) => emit('update:meta', { ...props.meta, document_title: value || null }),
|
||||
});
|
||||
|
||||
const warning = computed(() => getMediaValidationWarning(props.contentType, props.media));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-xl border-2 border-foreground bg-card shadow-2xs">
|
||||
<div v-if="hasPdf" class="rounded-xl border-2 border-foreground bg-card shadow-2xs">
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full cursor-pointer items-center justify-between gap-3 p-4 text-sm"
|
||||
|
|
@ -87,7 +89,7 @@ const warning = computed(() => getMediaValidationWarning(props.contentType, prop
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasPdf" class="space-y-2">
|
||||
<div class="space-y-2">
|
||||
<p class="text-[11px] font-black uppercase tracking-widest text-foreground/60">{{ $t('posts.form.linkedin.document_title') }}</p>
|
||||
<Input
|
||||
v-model="documentTitle"
|
||||
|
|
@ -96,14 +98,6 @@ const warning = computed(() => getMediaValidationWarning(props.contentType, prop
|
|||
:disabled="disabled || previewOnly"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="warning && !previewOnly"
|
||||
class="flex items-start gap-2 rounded-lg border-2 border-foreground bg-rose-50 p-2 text-xs font-semibold text-rose-700"
|
||||
>
|
||||
<IconAlertTriangle class="mt-0.5 size-3.5 shrink-0" />
|
||||
{{ $t(`posts.form.warnings.${warning.key}`, warning.params) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { IconBuilding, IconExternalLink, IconUser } from '@tabler/icons-vue';
|
||||
import {
|
||||
IconBuilding,
|
||||
IconExternalLink,
|
||||
IconUser,
|
||||
IconUsers,
|
||||
} from '@tabler/icons-vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
|
|
@ -115,8 +120,9 @@ const organizationUrl = (vanity: string | null): string | null =>
|
|||
<div class="flex items-center gap-2">
|
||||
<h3 class="truncate font-semibold">{{ person.name }}</h3>
|
||||
<span
|
||||
class="shrink-0 rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground"
|
||||
class="inline-flex shrink-0 items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground"
|
||||
>
|
||||
<IconUser class="h-3 w-3" />
|
||||
{{ $t('accounts.linkedin.person_tag') }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -156,8 +162,9 @@ const organizationUrl = (vanity: string | null): string | null =>
|
|||
<div class="flex items-center gap-2">
|
||||
<h3 class="truncate font-semibold">{{ org.name }}</h3>
|
||||
<span
|
||||
class="shrink-0 rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground"
|
||||
class="inline-flex shrink-0 items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground"
|
||||
>
|
||||
<IconUsers class="h-3 w-3" />
|
||||
{{ $t('accounts.linkedin.organization_tag') }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue