mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13: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 TextAlignExtension from '@tiptap/extension-text-align';
|
||||||
import UnderlineExtension from '@tiptap/extension-underline';
|
import UnderlineExtension from '@tiptap/extension-underline';
|
||||||
import HighlightExtension from '@tiptap/extension-highlight';
|
import HighlightExtension from '@tiptap/extension-highlight';
|
||||||
|
import { VideoExtension } from '../lib/tiptap-video';
|
||||||
import { newsApi } from '../api/news';
|
import { newsApi } from '../api/news';
|
||||||
import { AdminBackButton } from '../components/admin';
|
import { AdminBackButton } from '../components/admin';
|
||||||
import { Toggle } from '../components/admin/Toggle';
|
import { Toggle } from '../components/admin/Toggle';
|
||||||
@@ -246,6 +247,7 @@ export default function AdminNewsCreate() {
|
|||||||
types: ['heading', 'paragraph'],
|
types: ['heading', 'paragraph'],
|
||||||
}),
|
}),
|
||||||
HighlightExtension,
|
HighlightExtension,
|
||||||
|
VideoExtension,
|
||||||
],
|
],
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[],
|
[],
|
||||||
@@ -319,13 +321,13 @@ export default function AdminNewsCreate() {
|
|||||||
if (result.media_type === 'image') {
|
if (result.media_type === 'image') {
|
||||||
editor.chain().focus().setImage({ src: result.url, alt: file.name }).run();
|
editor.chain().focus().setImage({ src: result.url, alt: file.name }).run();
|
||||||
} else {
|
} else {
|
||||||
const safeUrl = result.url.replace(/"/g, '"');
|
|
||||||
editor
|
editor
|
||||||
.chain()
|
.chain()
|
||||||
.focus()
|
.focus()
|
||||||
.insertContent(
|
.insertContent({
|
||||||
`<video src="${safeUrl}" controls class="w-full rounded-xl max-h-96"></video>`,
|
type: 'video',
|
||||||
)
|
attrs: { src: result.url, class: 'w-full rounded-xl max-h-96' },
|
||||||
|
})
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
haptic.success();
|
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) => {
|
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
|
||||||
if (node.tagName === 'VIDEO') {
|
if (node.tagName === 'VIDEO') {
|
||||||
const src = node.getAttribute('src') ?? '';
|
const src = node.getAttribute('src') ?? '';
|
||||||
try {
|
try {
|
||||||
const url = new URL(src);
|
const url = new URL(src);
|
||||||
if (url.protocol !== 'https:') {
|
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
||||||
node.remove();
|
node.remove();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user