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

Merge pull request #7460 from weseek/feat/rich-attachment-filename

feat: Rich attachment shown items
Ryoji Shimizu 3 лет назад
Родитель
Сommit
66b853d37e

+ 73 - 14
packages/app/src/components/ReactMarkdownComponents/Attachment.tsx

@@ -1,24 +1,83 @@
-import React from 'react';
+import React, { useMemo, useCallback } from 'react';
 
-type AttachmentProps = {
-  className?: string,
-  url: string,
-  attachmentName: string,
+import { UserPicture } from '@growi/ui';
+import prettyBytes from 'pretty-bytes';
+
+import { useSWRxAttachments } from '~/stores/attachment';
+import { useCurrentPageId } from '~/stores/page';
+
+export const Attachment: React.FC<{
   attachmentId: string,
-}
+  url: string,
+  attachmentName: string
+}> = React.memo(({ attachmentId, url, attachmentName }) => {
+  const { data: pageId } = useCurrentPageId();
+  // TODO: We need to be able to get it from all pages if there are a lot of attachments.
+  const { data: dataAttachments, remove } = useSWRxAttachments(pageId, 1);
+
+  const attachment = useMemo(() => {
+    if (dataAttachments == null) {
+      return;
+    }
 
-export const Attachment = React.memo((props: AttachmentProps): JSX.Element => {
-  const { className, url, attachmentName } = props;
+    return dataAttachments.attachments.find(item => item._id === attachmentId);
+  }, [attachmentId, dataAttachments]);
+
+  // TODO: Call delete attachment modal.
+  const handleAttachmentDelete = useCallback(async() => {
+    if (attachment == null) {
+      return;
+    }
+
+    try {
+      await remove({ attachment_id: attachment._id });
+      // TODO: success notification
+    }
+    catch (err) {
+      // TODO: error handling
+    }
+  }, [attachment, remove]);
+
+  // TODO: update fo attachment is not found
+  // TODO: fix hydration failed error
+  if (attachment == null) {
+    return (
+      <div className='text-muted'>This attachment not found.</div>
+    );
+  }
 
   return (
-    <div className="card">
-      <h3 className="card-title m-0">Remark Attachment Component</h3>
-      <div className="card-body">
-        <a className={className} href={url}>
-          {attachmentName}
-        </a>
+    <div className="card my-3" style={{ width: 'fit-content' }}>
+      <div className="card-body pr-0">
+        <div className='row'>
+          <div className='col-2'>
+            <div className='icon-doc' style={{ fontSize: '2.7rem', opacity: '0.5' }}/>
+          </div>
+          <div className='col-10'>
+            <div>
+              <a className='' href={attachment.downloadPathProxied}>{attachment.originalName}</a>
+              <span className='ml-2'>
+                <a className="attachment-download" href={attachment.downloadPathProxied}>
+                  <i className="icon-cloud-download" />
+                </a>
+              </span>
+              <span className='ml-2'>
+                <a className="text-danger attachment-delete" onClick={handleAttachmentDelete}>
+                  <i className="icon-trash" />
+                </a>
+              </span>
+            </div>
+            <div>
+              <UserPicture user={attachment.creator} size="sm"></UserPicture>
+              {/* TODO: check locale */}
+              <span className='ml-2 text-muted'>{new Date(attachment.createdAt).toLocaleString()}</span>
+              <span className='border-left ml-2 pl-2 text-muted'>{prettyBytes(attachment.fileSize)}</span>
+            </div>
+          </div>
+        </div>
       </div>
     </div>
+
   );
 });
 Attachment.displayName = 'Attachment';

+ 2 - 2
packages/app/src/services/renderer/remark-plugins/attachment.ts

@@ -2,7 +2,7 @@ import { Schema as SanitizeOption } from 'hast-util-sanitize';
 import { Plugin } from 'unified';
 import { visit } from 'unist-util-visit';
 
-const SUPPORTED_ATTRIBUTES = ['url', 'attachmentName', 'attachmentId'];
+const SUPPORTED_ATTRIBUTES = ['attachmentId', 'url', 'attachmentName'];
 
 const isAttachmentLink = (url: string) => {
   // https://regex101.com/r/9qZhiK/1
@@ -20,9 +20,9 @@ export const remarkPlugin: Plugin = () => {
           const data = node.data ?? (node.data = {});
           data.hName = 'attachment';
           data.hProperties = {
+            attachmentId: pathName[2],
             url: node.url,
             attachmentName: node.children[0].value,
-            attachmentId: pathName[2],
           };
 
           // omit position to fix the key regardless of its position

+ 2 - 2
packages/core/src/interfaces/attachment.ts

@@ -5,10 +5,10 @@ import type { IUser } from './user';
 export type IAttachment = {
   page?: Ref<IPage>,
   creator?: Ref<IUser>,
-
+  createdAt: Date,
+  fileSize: number,
   // virtual property
   filePathProxied: string,
-
   fileFormat: string,
   downloadPathProxied: string,
   originalName: string,