From eca9f00cd815d7d0d69826bd340064816cc3255c Mon Sep 17 00:00:00 2001
From: Guodong Su <stephen.su66@gmail.com>
Date: Mon, 20 Jan 2025 13:35:39 +0800
Subject: [PATCH] feat(video):add video file

---
 .../dialog/add-video-link-dialog.js           | 95 +++++++++++++++++++
 .../sdoc/sdoc-editor/external-operations.js   | 29 +++++-
 2 files changed, 123 insertions(+), 1 deletion(-)
 create mode 100644 frontend/src/components/dialog/add-video-link-dialog.js

diff --git a/frontend/src/components/dialog/add-video-link-dialog.js b/frontend/src/components/dialog/add-video-link-dialog.js
new file mode 100644
index 00000000000..1838be21a3f
--- /dev/null
+++ b/frontend/src/components/dialog/add-video-link-dialog.js
@@ -0,0 +1,95 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Button, Modal, Input, ModalBody, ModalFooter, Form, FormGroup, Label, Alert } from 'reactstrap';
+import { gettext } from '../../utils/constants';
+import SeahubModalHeader from '@/components/common/seahub-modal-header';
+
+const propTypes = {
+  onAddLink: PropTypes.func.isRequired,
+  toggleDialog: PropTypes.func.isRequired,
+};
+
+class AddVideoLink extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      videoLink: '',
+      errMessage: '',
+      isSubmitBtnActive: false,
+    };
+    this.newInput = React.createRef();
+  }
+
+  isValidVideoLink = (link) => {
+    // const videoRegex = /\.(mp4|mov|avi|mkv|webm|ogv)$/i;
+    // return videoRegex.test(link);
+    return link;
+  };
+
+  handleChange = (e) => {
+    const videoLink = e.target.value.trim();
+    const isValid = this.isValidVideoLink(videoLink);
+
+    if (!e.target.value.trim()) {
+      this.setState({ isSubmitBtnActive: false });
+    }
+
+    this.setState({
+      videoLink: videoLink,
+      isSubmitBtnActive: isValid,
+      errMessage: isValid ? '' : gettext('Please enter a valid video URL ending in .mp4, .mov, etc.'),
+    });
+
+  };
+
+  handleSubmit = () => {
+    if (!this.state.isSubmitBtnActive) return;
+    this.props.onAddLink(this.state.videoLink);
+    this.props.toggleDialog();
+  };
+
+  handleKeyDown = (e) => {
+    if (e.key === 'Enter') {
+      this.handleSubmit();
+      e.preventDefault();
+    }
+  };
+
+  onAfterModelOpened = () => {
+    if (!this.newInput.current) return;
+    this.newInput.current.focus();
+    this.newInput.current.setSelectionRange(0, 0);
+  };
+
+  render() {
+    const { toggleDialog } = this.props;
+    return (
+      <Modal isOpen={true} toggle={toggleDialog} onOpened={this.onAfterModelOpened}>
+        <SeahubModalHeader toggle={toggleDialog}>{gettext('Insert_link')}</SeahubModalHeader>
+        <ModalBody>
+          <Form>
+            <FormGroup>
+              <Label for='videoLink'>{gettext('Link_address')}</Label>
+              <Input
+                id='videoLink'
+                onKeyDown={this.handleKeyDown}
+                innerRef={this.newInput}
+                value={this.state.videoLink}
+                onChange={this.handleChange}
+              />
+            </FormGroup>
+          </Form>
+          {this.state.errMessage && <Alert color='danger' className='mt-2'>{this.state.errMessage}</Alert>}
+        </ModalBody>
+        <ModalFooter>
+          <Button color='secondary' onClick={toggleDialog}>{gettext('Cancel')}</Button>
+          <Button color='primary' onClick={this.handleSubmit} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
+        </ModalFooter>
+      </Modal>
+    );
+  }
+}
+
+AddVideoLink.propTypes = propTypes;
+
+export default AddVideoLink;
diff --git a/frontend/src/pages/sdoc/sdoc-editor/external-operations.js b/frontend/src/pages/sdoc/sdoc-editor/external-operations.js
index e1918e2fc07..67f7c994b44 100644
--- a/frontend/src/pages/sdoc/sdoc-editor/external-operations.js
+++ b/frontend/src/pages/sdoc/sdoc-editor/external-operations.js
@@ -7,6 +7,7 @@ import toaster from '../../../components/toast';
 import InternalLinkDialog from '../../../components/dialog/internal-link-dialog';
 import ShareDialog from '../../../components/dialog/share-dialog';
 import CreateFile from '../../../components/dialog/create-file-dialog';
+import AddVideoLink from '../../../components/dialog/add-video-link-dialog';
 
 const propTypes = {
   repoID: PropTypes.string.isRequired,
@@ -33,6 +34,8 @@ class ExternalOperations extends React.Component {
       fileType: '.sdoc',
       editor: null,
       insertSdocFileLink: null,
+      isShowAddVideoLinkDialog: false,
+      insertVideo: null
     };
   }
 
@@ -46,6 +49,7 @@ class ExternalOperations extends React.Component {
     this.unsubscribeNewNotification = eventBus.subscribe(EXTERNAL_EVENT.NEW_NOTIFICATION, this.onNewNotification);
     this.unsubscribeClearNotification = eventBus.subscribe(EXTERNAL_EVENT.CLEAR_NOTIFICATION, this.onClearNotification);
     this.unsubscribeCreateSdocFile = eventBus.subscribe(EXTERNAL_EVENT.CREATE_SDOC_FILE, this.onCreateSdocFile);
+    this.unsubscribeAddVideoLink = eventBus.subscribe(EXTERNAL_EVENT.ADD_VIDEO_LINK, this.onAddVideoLink);
   }
 
   componentWillUnmount() {
@@ -58,8 +62,18 @@ class ExternalOperations extends React.Component {
     this.unsubscribeNewNotification();
     this.unsubscribeCreateSdocFile();
     this.unsubscribeClearNotification();
+    this.unsubscribeAddVideoLink();
   }
 
+  onAddVideoLink = (params) => {
+    if (params?.editor && params?.insertVideo) {
+      this.setState({ editor: params.editor, insertVideo: params.insertVideo });
+    }
+    this.setState({
+      isShowAddVideoLinkDialog: !this.state.isShowAddVideoLinkDialog
+    });
+  };
+
   onInternalLinkToggle = (options) => {
     if (options && options.internalLink) {
       this.setState({ internalLink: options.internalLink });
@@ -164,9 +178,16 @@ class ExternalOperations extends React.Component {
     });
   };
 
+  onAddLink = (videoLink) => {
+    const { insertVideo, editor } = this.state;
+    if (insertVideo && editor) {
+      insertVideo(editor, [{ isEmbeddableLink: true }], [videoLink]);
+    }
+  };
+
   render() {
     const { repoID, docPath, docName, docPerm, dirPath } = this.props;
-    const { isShowInternalLinkDialog, isShowShareDialog, internalLink, isShowCreateFileDialog, fileType } = this.state;
+    const { isShowInternalLinkDialog, isShowShareDialog, internalLink, isShowCreateFileDialog, fileType, isShowAddVideoLinkDialog } = this.state;
     return (
       <>
         {isShowInternalLinkDialog && (
@@ -196,6 +217,12 @@ class ExternalOperations extends React.Component {
             toggleDialog={this.onCreateSdocFile}
           />
         )}
+        {isShowAddVideoLinkDialog && (
+          <AddVideoLink
+            onAddLink={this.onAddLink}
+            toggleDialog={this.onAddVideoLink}
+          />
+        )}
       </>
     );
   }