Просмотр исходного кода

Merge pull request #8146 from weseek/imprv/131762-131770-limit-file-type

imprv: Limit the file types in editor.
Yuki Takei 2 лет назад
Родитель
Сommit
55ab711267

+ 13 - 3
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -7,7 +7,9 @@ import nodePath from 'path';
 
 import type { IPageHasId } from '@growi/core';
 import { pathUtils } from '@growi/core/dist/utils';
-import { CodeMirrorEditorMain, GlobalCodeMirrorEditorKey, useCodeMirrorEditorIsolated } from '@growi/editor';
+import {
+  CodeMirrorEditorMain, GlobalCodeMirrorEditorKey, useCodeMirrorEditorIsolated, AcceptedUploadFileType,
+} from '@growi/editor';
 import detectIndent from 'detect-indent';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
@@ -357,6 +359,15 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
 
   }, [codeMirrorEditor, currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateIsLatestRevision, pageId]);
 
+  const acceptedFileType = useMemo(() => {
+    if (!isUploadableFile) {
+      return AcceptedUploadFileType.NONE;
+    }
+    if (isUploadableImage) {
+      return AcceptedUploadFileType.IMAGE;
+    }
+    return AcceptedUploadFileType.ALL;
+  }, [isUploadableFile, isUploadableImage]);
 
   const scrollPreviewByEditorLine = useCallback((line: number) => {
     if (previewRef.current == null) {
@@ -554,8 +565,6 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     return <></>;
   }
 
-  const isUploadable = isUploadableImage || isUploadableFile;
-
   return (
     <div data-testid="page-editor" id="page-editor" className={`flex-expand-horiz ${props.visibility ? '' : 'd-none'}`}>
       <div className="page-editor-editor-container flex-expand-vert">
@@ -576,6 +585,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
           onSave={saveWithShortcut}
           onUpload={uploadHandler}
           indentSize={currentIndentSize ?? defaultIndentSize}
+          acceptedFileType={acceptedFileType}
         />
       </div>
       <div className="page-editor-preview-container flex-expand-vert d-none d-lg-flex">

+ 5 - 3
packages/editor/src/components/CodeMirrorEditor/CodeMirrorEditor.tsx

@@ -6,7 +6,7 @@ import { indentUnit } from '@codemirror/language';
 import { EditorView } from '@codemirror/view';
 import type { ReactCodeMirrorProps } from '@uiw/react-codemirror';
 
-import { GlobalCodeMirrorEditorKey } from '../../consts';
+import { GlobalCodeMirrorEditorKey, AcceptedUploadFileType } from '../../consts';
 import { useFileDropzone } from '../../services';
 import { useCodeMirrorEditorIsolated } from '../../stores';
 
@@ -22,6 +22,7 @@ const CodeMirrorEditorContainer = forwardRef<HTMLDivElement>((props, ref) => {
 
 type Props = {
   editorKey: string | GlobalCodeMirrorEditorKey,
+  acceptedFileType: AcceptedUploadFileType,
   onChange?: (value: string) => void,
   onUpload?: (files: File[]) => void,
   indentSize?: number,
@@ -30,6 +31,7 @@ type Props = {
 export const CodeMirrorEditor = (props: Props): JSX.Element => {
   const {
     editorKey,
+    acceptedFileType,
     onChange,
     onUpload,
     indentSize,
@@ -98,12 +100,12 @@ export const CodeMirrorEditor = (props: Props): JSX.Element => {
 
   }, [codeMirrorEditor]);
 
-  const { getRootProps, open } = useFileDropzone({ onUpload });
+  const { getRootProps, open } = useFileDropzone({ onUpload, acceptedFileType });
 
   return (
     <div {...getRootProps()} className="flex-expand-vert">
       <CodeMirrorEditorContainer ref={containerRef} />
-      <Toolbar onFileOpen={open} />
+      <Toolbar onFileOpen={open} acceptedFileType={acceptedFileType} />
     </div>
   );
 };

+ 38 - 0
packages/editor/src/components/CodeMirrorEditor/Toolbar/AttachmentsButton.tsx

@@ -0,0 +1,38 @@
+import {
+  DropdownItem,
+} from 'reactstrap';
+
+import { AcceptedUploadFileType } from '../../../consts/accepted-upload-file-type';
+
+type Props = {
+  onFileOpen: () => void,
+  acceptedFileType: AcceptedUploadFileType,
+}
+
+export const AttachmentsButton = (props: Props): JSX.Element => {
+
+  const { onFileOpen, acceptedFileType } = props;
+
+  if (acceptedFileType === AcceptedUploadFileType.ALL) {
+    return (
+      <>
+        <DropdownItem className="d-flex gap-1 align-items-center" onClick={onFileOpen}>
+          <span className="material-icons-outlined fs-5">attach_file</span>
+          Files
+        </DropdownItem>
+      </>
+    );
+  }
+  if (acceptedFileType === AcceptedUploadFileType.IMAGE) {
+    return (
+      <>
+        <DropdownItem className="d-flex gap-1 align-items-center" onClick={onFileOpen}>
+          <span className="material-icons-outlined fs-5">image</span>
+          Images
+        </DropdownItem>
+      </>
+    );
+  }
+
+  return <></>;
+};

+ 8 - 9
packages/editor/src/components/CodeMirrorEditor/Toolbar/AttachmentsDropup.tsx

@@ -5,13 +5,19 @@ import {
   DropdownItem,
 } from 'reactstrap';
 
+import { AcceptedUploadFileType } from '../../../consts/accepted-upload-file-type';
+
+import { AttachmentsButton } from './AttachmentsButton';
+
+
 type Props = {
   onFileOpen: () => void,
+  acceptedFileType: AcceptedUploadFileType,
 }
 
 export const AttachmentsDropup = (props: Props): JSX.Element => {
 
-  const { onFileOpen } = props;
+  const { onFileOpen, acceptedFileType } = props;
   return (
     <>
       <UncontrolledDropdown direction="up" className="lh-1">
@@ -24,14 +30,7 @@ export const AttachmentsDropup = (props: Props): JSX.Element => {
             Attachments
           </DropdownItem>
           <DropdownItem divider />
-          <DropdownItem className="d-flex gap-1 align-items-center" onClick={onFileOpen}>
-            <span className="material-icons-outlined fs-5">attach_file</span>
-            Files
-          </DropdownItem>
-          <DropdownItem className="d-flex gap-1 align-items-center" onClick={onFileOpen}>
-            <span className="material-icons-outlined fs-5">image</span>
-            Images
-          </DropdownItem>
+          <AttachmentsButton onFileOpen={onFileOpen} acceptedFileType={acceptedFileType} />
           <DropdownItem className="d-flex gap-1 align-items-center">
             <span className="material-icons-outlined fs-5">link</span>
             Link

+ 5 - 2
packages/editor/src/components/CodeMirrorEditor/Toolbar/Toolbar.tsx

@@ -7,18 +7,21 @@ import { TableButton } from './TableButton';
 import { TemplateButton } from './TemplateButton';
 import { TextFormatTools } from './TextFormatTools';
 
+import { AcceptedUploadFileType } from 'src/consts';
+
 import styles from './Toolbar.module.scss';
 
 type Props = {
   onFileOpen: () => void,
+  acceptedFileType: AcceptedUploadFileType
 }
 
 export const Toolbar = memo((props: Props): JSX.Element => {
 
-  const { onFileOpen } = props;
+  const { onFileOpen, acceptedFileType } = props;
   return (
     <div className={`d-flex gap-2 p-2 codemirror-editor-toolbar ${styles['codemirror-editor-toolbar']}`}>
-      <AttachmentsDropup onFileOpen={onFileOpen} />
+      <AttachmentsDropup onFileOpen={onFileOpen} acceptedFileType={acceptedFileType} />
       <TextFormatTools />
       <EmojiButton />
       <TableButton />

+ 5 - 2
packages/editor/src/components/CodeMirrorEditorComment.tsx

@@ -3,7 +3,7 @@ import { useEffect } from 'react';
 import type { Extension } from '@codemirror/state';
 import { keymap, scrollPastEnd } from '@codemirror/view';
 
-import { GlobalCodeMirrorEditorKey } from '../consts';
+import { GlobalCodeMirrorEditorKey, AcceptedUploadFileType } from '../consts';
 import { useCodeMirrorEditorIsolated } from '../stores';
 
 import { CodeMirrorEditor } from '.';
@@ -17,14 +17,16 @@ const additionalExtensions: Extension[] = [
 type Props = {
   onChange?: (value: string) => void,
   onComment?: () => void,
+  acceptedFileType?: AcceptedUploadFileType,
 }
 
 export const CodeMirrorEditorComment = (props: Props): JSX.Element => {
   const {
-    onComment, onChange,
+    onComment, onChange, acceptedFileType,
   } = props;
 
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.COMMENT);
+  const acceptedFileTypeNoOpt = acceptedFileType ?? AcceptedUploadFileType.NONE;
 
   // setup additional extensions
   useEffect(() => {
@@ -60,6 +62,7 @@ export const CodeMirrorEditorComment = (props: Props): JSX.Element => {
     <CodeMirrorEditor
       editorKey={GlobalCodeMirrorEditorKey.COMMENT}
       onChange={onChange}
+      acceptedFileType={acceptedFileTypeNoOpt}
     />
   );
 };

+ 5 - 2
packages/editor/src/components/CodeMirrorEditorMain.tsx

@@ -3,7 +3,7 @@ import { useEffect } from 'react';
 import type { Extension } from '@codemirror/state';
 import { keymap, scrollPastEnd } from '@codemirror/view';
 
-import { GlobalCodeMirrorEditorKey } from '../consts';
+import { GlobalCodeMirrorEditorKey, AcceptedUploadFileType } from '../consts';
 import { useCodeMirrorEditorIsolated } from '../stores';
 
 import { CodeMirrorEditor } from '.';
@@ -18,15 +18,17 @@ type Props = {
   onChange?: (value: string) => void,
   onSave?: () => void,
   onUpload?: (files: File[]) => void,
+  acceptedFileType?: AcceptedUploadFileType,
   indentSize?: number,
 }
 
 export const CodeMirrorEditorMain = (props: Props): JSX.Element => {
   const {
-    onSave, onChange, onUpload, indentSize,
+    onSave, onChange, onUpload, acceptedFileType, indentSize,
   } = props;
 
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
+  const acceptedFileTypeNoOpt = acceptedFileType ?? AcceptedUploadFileType.NONE;
 
   // setup additional extensions
   useEffect(() => {
@@ -63,6 +65,7 @@ export const CodeMirrorEditorMain = (props: Props): JSX.Element => {
       editorKey={GlobalCodeMirrorEditorKey.MAIN}
       onChange={onChange}
       onUpload={onUpload}
+      acceptedFileType={acceptedFileTypeNoOpt}
       indentSize={indentSize}
     />
   );

+ 2 - 2
packages/editor/src/components/playground/Playground.tsx

@@ -4,7 +4,7 @@ import {
 
 import { toast } from 'react-toastify';
 
-import { GlobalCodeMirrorEditorKey } from '../../consts';
+import { AcceptedUploadFileType, GlobalCodeMirrorEditorKey } from '../../consts';
 import { useCodeMirrorEditorIsolated } from '../../stores';
 import { CodeMirrorEditorMain } from '../CodeMirrorEditorMain';
 
@@ -48,7 +48,6 @@ export const Playground = (): JSX.Element => {
 
   }, [codeMirrorEditor]);
 
-
   return (
     <>
       <div className="flex-expand-vert justify-content-center align-items-center bg-dark" style={{ minHeight: '83px' }}>
@@ -61,6 +60,7 @@ export const Playground = (): JSX.Element => {
             onChange={setMarkdownToPreview}
             onUpload={uploadHandler}
             indentSize={4}
+            acceptedFileType={AcceptedUploadFileType.ALL}
           />
         </div>
         <div className="flex-expand-vert d-none d-lg-flex bg-light text-dark border-start border-dark-subtle p-3">

+ 6 - 0
packages/editor/src/consts/accepted-upload-file-type.ts

@@ -0,0 +1,6 @@
+export const AcceptedUploadFileType = {
+  ALL: '*',
+  IMAGE: 'image/*',
+  NONE: '',
+} as const;
+export type AcceptedUploadFileType = typeof AcceptedUploadFileType[keyof typeof AcceptedUploadFileType];

+ 1 - 0
packages/editor/src/consts/index.ts

@@ -1 +1,2 @@
 export * from './global-code-mirror-editor-key';
+export * from './accepted-upload-file-type';

+ 13 - 2
packages/editor/src/services/file-dropzone/use-file-dropzone.ts

@@ -1,15 +1,18 @@
 import { useCallback } from 'react';
 
-import { useDropzone } from 'react-dropzone';
+import { useDropzone, Accept } from 'react-dropzone';
 import type { DropzoneState } from 'react-dropzone';
 
+import { AcceptedUploadFileType } from '../../consts';
+
 type DropzoneEditor = {
   onUpload?: (files: File[]) => void,
+  acceptedFileType: AcceptedUploadFileType,
 }
 
 export const useFileDropzone = (props: DropzoneEditor): DropzoneState => {
 
-  const { onUpload } = props;
+  const { onUpload, acceptedFileType } = props;
 
   const dropHandler = useCallback((acceptedFiles: File[]) => {
     if (onUpload == null) {
@@ -18,10 +21,18 @@ export const useFileDropzone = (props: DropzoneEditor): DropzoneState => {
     onUpload(acceptedFiles);
   }, [onUpload]);
 
+  const accept: Accept = {
+    acceptedFileType: [],
+  };
+
+  const disabled = acceptedFileType === AcceptedUploadFileType.NONE;
+
   return useDropzone({
     noKeyboard: true,
     noClick: true,
+    disabled,
     onDrop: dropHandler,
+    accept,
   });
 
 };