mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
fix: video not rendering — add TipTap Video extension, allow HTTP src
TipTap didn't recognize <video> tags without a custom node extension, serializing them as escaped text. Also revert video src to allow HTTP since request.base_url returns http:// behind reverse proxy.
This commit is contained in:
30
src/lib/tiptap-video.ts
Normal file
30
src/lib/tiptap-video.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Node, mergeAttributes } from '@tiptap/core';
|
||||
|
||||
/**
|
||||
* TipTap extension for inline <video> elements.
|
||||
*
|
||||
* Without this extension, TipTap does not recognize <video> tags and
|
||||
* serializes them as escaped text when calling editor.getHTML().
|
||||
*/
|
||||
export const VideoExtension = Node.create({
|
||||
name: 'video',
|
||||
group: 'block',
|
||||
atom: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
src: { default: null },
|
||||
controls: { default: true },
|
||||
class: { default: null },
|
||||
preload: { default: 'metadata' },
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{ tag: 'video' }];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['video', mergeAttributes(HTMLAttributes, { controls: '' })];
|
||||
},
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import PlaceholderExtension from '@tiptap/extension-placeholder';
|
||||
import TextAlignExtension from '@tiptap/extension-text-align';
|
||||
import UnderlineExtension from '@tiptap/extension-underline';
|
||||
import HighlightExtension from '@tiptap/extension-highlight';
|
||||
import { VideoExtension } from '../lib/tiptap-video';
|
||||
import { newsApi } from '../api/news';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { Toggle } from '../components/admin/Toggle';
|
||||
@@ -246,6 +247,7 @@ export default function AdminNewsCreate() {
|
||||
types: ['heading', 'paragraph'],
|
||||
}),
|
||||
HighlightExtension,
|
||||
VideoExtension,
|
||||
],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[],
|
||||
@@ -319,13 +321,13 @@ export default function AdminNewsCreate() {
|
||||
if (result.media_type === 'image') {
|
||||
editor.chain().focus().setImage({ src: result.url, alt: file.name }).run();
|
||||
} else {
|
||||
const safeUrl = result.url.replace(/"/g, '"');
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContent(
|
||||
`<video src="${safeUrl}" controls class="w-full rounded-xl max-h-96"></video>`,
|
||||
)
|
||||
.insertContent({
|
||||
type: 'video',
|
||||
attrs: { src: result.url, class: 'w-full rounded-xl max-h-96' },
|
||||
})
|
||||
.run();
|
||||
}
|
||||
haptic.success();
|
||||
|
||||
@@ -189,13 +189,14 @@ function sanitizeHtml(html: string): string {
|
||||
}
|
||||
});
|
||||
|
||||
// Hook: validate <video> src — HTTPS only
|
||||
// Hook: validate <video> src — only allow http/https (block javascript:, data:, etc.)
|
||||
// HTTP is permitted because request.base_url behind a reverse proxy returns http://
|
||||
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
|
||||
if (node.tagName === 'VIDEO') {
|
||||
const src = node.getAttribute('src') ?? '';
|
||||
try {
|
||||
const url = new URL(src);
|
||||
if (url.protocol !== 'https:') {
|
||||
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
||||
node.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user