fix: resolve video playback issue in publications using view endpoint and legacy URL normalization
This commit is contained in:
parent
5c15a34a19
commit
3dff30ab73
@ -10,12 +10,16 @@ import { Metadata, ResolvingMetadata } from 'next';
|
||||
|
||||
function formatTextToHtml(text: string) {
|
||||
if (!text) return '';
|
||||
// Normalize any video preview URLs to view URLs for legacy content
|
||||
const normalizedText = text
|
||||
.replace(/(<video[^>]*src=["'])([^"']*)\/preview\?([^"']*["'])/gi, '$1$2/view?$3')
|
||||
.replace(/(<source[^>]*src=["'])([^"']*)\/preview\?([^"']*["'])/gi, '$1$2/view?$3');
|
||||
// Si ya contiene etiquetas estructurales de HTML (párrafos, saltos, divs, listas), lo devolvemos tal cual
|
||||
if (/<\/?(?:p|br|div|h[1-6]|ul|ol|li|table)[^>]*>/i.test(text)) {
|
||||
return text;
|
||||
if (/<\/?(?:p|br|div|h[1-6]|ul|ol|li|table)[^>]*>/i.test(normalizedText)) {
|
||||
return normalizedText;
|
||||
}
|
||||
// Convertimos texto plano con saltos de línea a párrafos y <br/>, permitiendo que etiquetas inline como <strong> o <iframe> se mantengan.
|
||||
return text.split(/\n\s*\n/)
|
||||
return normalizedText.split(/\n\s*\n/)
|
||||
.filter(p => p.trim() !== '')
|
||||
.map(p => `<p>${p.replace(/\n/g, '<br />')}</p>`)
|
||||
.join('');
|
||||
|
||||
@ -8,12 +8,16 @@ import { z } from 'zod';
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
|
||||
// Re-usable rich text validator
|
||||
const safeHtmlSchema = z.string().transform(html =>
|
||||
DOMPurify.sanitize(html, {
|
||||
ADD_TAGS: ['iframe'],
|
||||
ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling', 'style', 'class']
|
||||
})
|
||||
);
|
||||
const safeHtmlSchema = z.string().transform(html => {
|
||||
const normalizedHtml = html
|
||||
.replace(/(<video[^>]*src=["'])([^"']*)\/preview\?([^"']*["'])/gi, '$1$2/view?$3')
|
||||
.replace(/(<source[^>]*src=["'])([^"']*)\/preview\?([^"']*["'])/gi, '$1$2/view?$3');
|
||||
return DOMPurify.sanitize(normalizedHtml, {
|
||||
ADD_TAGS: ['iframe', 'video', 'source'],
|
||||
ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling', 'style', 'class', 'controls', 'src', 'type', 'muted', 'loop', 'autoplay', 'preload']
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Extrae y limpia los datos del FormData usando el SCHEMA_MAP como whitelist.
|
||||
|
||||
@ -11,7 +11,7 @@ export function getAppwriteImageUrl(fileId: string, bucketId?: string, width = 4
|
||||
if (!fileId) return '';
|
||||
const finalBucketId = bucketId || APPWRITE_ALMEIRA_CONFIG.mediaBucketId;
|
||||
if (isVideo) {
|
||||
return `${endpoint}/storage/buckets/${finalBucketId}/files/${fileId}/preview?project=${projectId}`;
|
||||
return `${endpoint}/storage/buckets/${finalBucketId}/files/${fileId}/view?project=${projectId}`;
|
||||
}
|
||||
return `${endpoint}/storage/buckets/${finalBucketId}/files/${fileId}/preview?project=${projectId}&width=${width}&height=${height}&quality=80`;
|
||||
}
|
||||
|
||||
@ -46,6 +46,9 @@ const VideoNode = Node.create({
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
if (HTMLAttributes.src && typeof HTMLAttributes.src === 'string') {
|
||||
HTMLAttributes.src = HTMLAttributes.src.replace('/preview?', '/view?');
|
||||
}
|
||||
return ['video', mergeAttributes(HTMLAttributes, { class: 'w-full aspect-video rounded-xl shadow-lg my-6', controls: 'true', preload: 'metadata' })]
|
||||
},
|
||||
});
|
||||
@ -76,7 +79,8 @@ const MenuBar = ({ editor }: { editor: any }) => {
|
||||
const handleMediaInsert = (fileId: string, url?: string, tipo?: string) => {
|
||||
if (url) {
|
||||
if (tipo === 'video') {
|
||||
editor.chain().focus().insertContent(`<video src="${url}" controls="true" preload="metadata" class="w-full aspect-video rounded-xl shadow-lg my-6"></video>`).run();
|
||||
const videoUrl = url.replace('/preview?', '/view?');
|
||||
editor.chain().focus().insertContent(`<video src="${videoUrl}" controls="true" preload="metadata" class="w-full aspect-video rounded-xl shadow-lg my-6"></video>`).run();
|
||||
} else {
|
||||
editor.chain().focus().setImage({ src: url }).run();
|
||||
}
|
||||
@ -300,8 +304,11 @@ export const RichTextEditor = ({ value, onChange, label }: RichTextEditorProps)
|
||||
|
||||
// Keep content synced if value changes externally
|
||||
useEffect(() => {
|
||||
if (editor && value !== editor.getHTML()) {
|
||||
editor.commands.setContent(value);
|
||||
if (editor && value && value !== editor.getHTML()) {
|
||||
const fixedValue = value
|
||||
.replace(/(<video[^>]*src=["'])([^"']*)\/preview\?([^"']*["'])/gi, '$1$2/view?$3')
|
||||
.replace(/(<source[^>]*src=["'])([^"']*)\/preview\?([^"']*["'])/gi, '$1$2/view?$3');
|
||||
editor.commands.setContent(fixedValue);
|
||||
}
|
||||
}, [value, editor]);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user