Skip to content

Commit

Permalink
feat: support embed video on document
Browse files Browse the repository at this point in the history
  • Loading branch information
qinluhe committed Feb 6, 2025
1 parent 34607bf commit b33ad13
Show file tree
Hide file tree
Showing 17 changed files with 537 additions and 78 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"react-i18next": "^14.1.0",
"react-katex": "^3.0.1",
"react-measure": "^2.5.2",
"react-player": "^2.16.0",
"react-redux": "^8.0.5",
"react-router-dom": "^6.22.3",
"react-swipeable-views": "^0.14.0",
Expand Down
28 changes: 24 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/@types/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3063,5 +3063,11 @@
"openInBrowser": "Open in browser",
"openInApp": "Open in app",
"comments": "Comments",
"duplicateAsTemplate": "Duplicate as template"
"duplicateAsTemplate": "Duplicate as template",
"embedVideo": "Embed video",
"embedAVideo": "Embed a video",
"embedLink": "Embed link",
"embedVideoLinkPlaceholder": "Paste a video link here.",
"videoSupported": "Supported services: YouTube, Vimeo, and more.",
"copiedVideoLink": "Video original link copied to clipboard"
}
15 changes: 15 additions & 0 deletions src/application/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export enum BlockType {
CalloutBlock = 'callout',
DividerBlock = 'divider',
ImageBlock = 'image',
VideoBlock = 'video',
GridBlock = 'grid',
BoardBlock = 'board',
CalendarBlock = 'calendar',
Expand Down Expand Up @@ -124,6 +125,20 @@ export interface ImageBlockData extends BlockData {
retry_local_url?: string;
}

export enum VideoType {
Local = 0,
Internal = 1,
External = 2,
}

export interface VideoBlockData extends BlockData {
url?: string;
width?: number;
height?: number;
align?: AlignType;
video_type?: VideoType;
}

export enum GalleryLayout {
Carousel = 0,
Grid = 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { YjsEditor } from '@/application/slate-yjs';
import { CustomEditor } from '@/application/slate-yjs/command';
import { findSlateEntryByBlockId } from '@/application/slate-yjs/utils/editor';
import { VideoBlockData, VideoType } from '@/application/types';
import { TabPanel, ViewTab, ViewTabs } from '@/components/_shared/tabs/ViewTabs';
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useSlateStatic } from 'slate-react';
import EmbedLink from 'src/components/_shared/image-upload/EmbedLink';

function VideoBlockPopoverContent({
blockId,
onClose,
}: {
blockId: string;
onClose: () => void;
}) {
const editor = useSlateStatic() as YjsEditor;
const { t } = useTranslation();

const [tabValue, setTabValue] = React.useState('embed');
const entry = useMemo(() => {
try {
return findSlateEntryByBlockId(editor, blockId);
} catch(e) {
return null;
}
}, [blockId, editor]);
const handleTabChange = useCallback((_event: React.SyntheticEvent, newValue: string) => {
setTabValue(newValue);
}, []);

const handleInsertEmbedLink = useCallback((url: string) => {
CustomEditor.setBlockData(editor, blockId, {
url,
video_type: VideoType.External,
} as VideoBlockData);
onClose();
}, [blockId, editor, onClose]);
const tabOptions = useMemo(() => {
return [
{
key: 'embed',
label: t('embedLink'),
panel: <div className={'flex flex-col my-2'}><EmbedLink
onDone={handleInsertEmbedLink}
defaultLink={(entry?.[0].data as VideoBlockData).url}
placeholder={t('embedVideoLinkPlaceholder')}
/>
<div className={'text-text-caption text-sm w-full text-center'}>{t('videoSupported')}</div>
</div>,
},
];
}, [entry, handleInsertEmbedLink, t]);
const selectedIndex = tabOptions.findIndex((tab) => tab.key === tabValue);

return (
<div className={'flex flex-col p-2 gap-2'}>
<ViewTabs
value={tabValue}
onChange={handleTabChange}
className={'min-h-[38px] px-2 border-b border-line-divider w-[560px] max-w-[964px]'}
>
{tabOptions.map((tab) => {
const { key, label } = tab;

return <ViewTab
key={key}
iconPosition="start"
color="inherit"
label={label}
value={key}
/>;
})}
</ViewTabs>
{tabOptions.map((tab, index) => {
const { key, panel } = tab;

return (
<TabPanel
className={'flex h-full w-full flex-col'}
key={key}
index={index}
value={selectedIndex}
>
{panel}
</TabPanel>
);
})}
</div>
);
}

export default VideoBlockPopoverContent;
22 changes: 14 additions & 8 deletions src/components/editor/components/block-popover/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useEditorContext } from '@/components/editor/EditorContext';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { ReactEditor, useSlateStatic } from 'slate-react';
import MathEquationPopoverContent from './MathEquationPopoverContent';
import VideoBlockPopoverContent from './VideoBlockPopoverContent';

const defaultOrigins: Origins = {
anchorOrigin: {
Expand All @@ -21,7 +22,7 @@ const defaultOrigins: Origins = {
},
};

function BlockPopover () {
function BlockPopover() {
const {
open,
anchorEl,
Expand All @@ -35,7 +36,7 @@ function BlockPopover () {

const handleClose = useCallback(() => {
window.getSelection()?.removeAllRanges();
if (!blockId) return;
if(!blockId) return;

const [, path] = findSlateEntryByBlockId(editor, blockId);

Expand All @@ -45,8 +46,8 @@ function BlockPopover () {
}, [blockId, close, editor]);

const content = useMemo(() => {
if (!blockId) return;
switch (type) {
if(!blockId) return;
switch(type) {
case BlockType.FileBlock:
return <FileBlockPopoverContent
blockId={blockId}
Expand All @@ -62,6 +63,11 @@ function BlockPopover () {
blockId={blockId}
onClose={handleClose}
/>;
case BlockType.VideoBlock:
return <VideoBlockPopoverContent
blockId={blockId}
onClose={handleClose}
/>;
default:
return null;
}
Expand All @@ -70,7 +76,7 @@ function BlockPopover () {
const paperRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (blockId) {
if(blockId) {

setSelectedBlockIds?.([blockId]);
} else {
Expand All @@ -79,18 +85,18 @@ function BlockPopover () {
}, [blockId, setSelectedBlockIds]);

useEffect(() => {
if (!open) return;
if(!open) return;
editor.deselect();
}, [open, editor]);

useEffect(() => {
const panelPosition = anchorEl?.getBoundingClientRect();

if (open && panelPosition) {
if(open && panelPosition) {
const origins = calculateOptimalOrigins({
top: panelPosition.bottom,
left: panelPosition.left,
}, 560, type === BlockType.ImageBlock ? 400 : 200, defaultOrigins, 16);
}, 560, (type === BlockType.ImageBlock || type === BlockType.VideoBlock) ? 400 : 200, defaultOrigins, 16);

setOrigins({
transformOrigin: {
Expand Down
14 changes: 10 additions & 4 deletions src/components/editor/components/blocks/image/ImageResizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ function ImageResizer({
width,
onWidthChange,
isLeft,
onDragStart,
onDragEnd,
}: {
isLeft?: boolean;
minWidth: number;
width: number;
onWidthChange: (newWidth: number) => void;
onDragStart?: () => void;
onDragEnd?: () => void;
}) {
const originalWidth = useRef(width);
const startX = useRef(0);
Expand All @@ -20,28 +24,30 @@ function ImageResizer({
const diff = isLeft ? startX.current - e.clientX : e.clientX - startX.current;
const newWidth = originalWidth.current + diff;

if (newWidth < minWidth) {
if(newWidth < minWidth) {
return;
}

onWidthChange(newWidth);
},
[isLeft, minWidth, onWidthChange]
[isLeft, minWidth, onWidthChange],
);

const onResizeEnd = useCallback(() => {
document.removeEventListener('mousemove', onResize);
document.removeEventListener('mouseup', onResizeEnd);
}, [onResize]);
onDragEnd?.();
}, [onResize, onDragEnd]);

const onResizeStart = useCallback(
(e: React.MouseEvent) => {
startX.current = e.clientX;
originalWidth.current = width;
document.addEventListener('mousemove', onResize);
document.addEventListener('mouseup', onResizeEnd);
onDragStart?.();
},
[onResize, onResizeEnd, width]
[onResize, onResizeEnd, width, onDragStart],
);

return (
Expand Down
Loading

0 comments on commit b33ad13

Please sign in to comment.