jam411 3 лет назад
Родитель
Сommit
668fe63362

+ 69 - 1
packages/app/src/components/Page.tsx

@@ -10,6 +10,7 @@ import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import { HtmlElementNode } from 'rehype-toc';
 
+import MarkdownTable from '~/client/models/MarkdownTable';
 import { useSaveOrUpdate } from '~/client/services/page-operation';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { OptionsToSave } from '~/interfaces/page-operation';
@@ -17,7 +18,7 @@ import {
   useIsGuestUser, useShareLinkId,
 } from '~/stores/context';
 import { useEditingMarkdown } from '~/stores/editor';
-import { useDrawioModal } from '~/stores/modal';
+import { useDrawioModal, useHandsontableModal } from '~/stores/modal';
 import { useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
 import { useViewOptions } from '~/stores/renderer';
 import {
@@ -28,6 +29,7 @@ import loggerFactory from '~/utils/logger';
 
 import RevisionRenderer from './Page/RevisionRenderer';
 import mdu from './PageEditor/MarkdownDrawioUtil';
+import mtu from './PageEditor/MarkdownTableUtil';
 
 
 declare global {
@@ -62,6 +64,7 @@ export const Page = (props) => {
   const { data: rendererOptions } = useViewOptions(storeTocNodeHandler);
   const { mutate: mutateCurrentPageTocNode } = useCurrentPageTocNode();
   const { open: openDrawioModal } = useDrawioModal();
+  const { open: openHandsontableModal } = useHandsontableModal();
 
   const saveOrUpdate = useSaveOrUpdate();
 
@@ -121,6 +124,71 @@ export const Page = (props) => {
     };
   }, [openDrawioModal, saveByDrawioModal]);
 
+  const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
+    if (currentPage == null || tagsInfo == null) {
+      return;
+    }
+
+    const currentMarkdown = currentPage.revision.body;
+    const optionsToSave: OptionsToSave = {
+      isSlackEnabled: false,
+      slackChannels: '',
+      grant: currentPage.grant,
+      grantUserGroupId: currentPage.grantedGroup?._id,
+      grantUserGroupName: currentPage.grantedGroup?.name,
+      pageTags: tagsInfo.tags,
+    };
+
+    const newMarkdown = mtu.replaceMarkdownTableInMarkdown(table, currentMarkdown, bol, eol);
+
+    try {
+      const currentRevisionId = currentPage.revision._id;
+      await saveOrUpdate(
+        newMarkdown,
+        { pageId: currentPage._id, path: currentPage.path, revisionId: currentRevisionId },
+        optionsToSave,
+      );
+
+      toastSuccess(t('toaster.save_succeeded'));
+
+      // rerender
+      mutateCurrentPage();
+      mutateEditingMarkdown(newMarkdown);
+    }
+    catch (error) {
+      logger.error('failed to save', error);
+      toastError(error);
+    }
+  }, [currentPage, mutateCurrentPage, mutateEditingMarkdown, saveOrUpdate, t, tagsInfo]);
+
+
+  const tableByHandsontableModal = useCallback((beginLineNumber, endLineNumber) => {
+    if (currentPage == null) {
+      return;
+    }
+
+    const markdown = currentPage.revision.body;
+    const tableLines = markdown.split(/\r\n|\r|\n/).slice(beginLineNumber - 1, endLineNumber).join('\n');
+    const table = MarkdownTable.fromMarkdownString(tableLines);
+    return table;
+  }, [currentPage]);
+
+  // set handler to open HandsonTableModal
+  useEffect(() => {
+    const handler = (bol, eol) => {
+      const table = tableByHandsontableModal(bol, eol);
+      if (table == null) {
+        return;
+      }
+      openHandsontableModal(table, undefined, false, table => saveByHandsontableModal(table, bol, eol));
+    };
+    globalEmitter.on('launchHandsonTableModal', handler);
+
+    return function cleanup() {
+      globalEmitter.removeListener('launchHandsonTableModal', handler);
+    };
+  }, [openHandsontableModal, saveByHandsontableModal, tableByHandsontableModal]);
+
   if (currentPage == null || isGuestUser == null || rendererOptions == null) {
     const entries = Object.entries({
       currentPage, isGuestUser, rendererOptions,

+ 8 - 2
packages/app/src/components/PageEditor/HandsontableModal.tsx

@@ -36,6 +36,7 @@ export const HandsontableModal = (): JSX.Element => {
   const table = handsontableModalData?.table;
   const autoFormatMarkdownTable = handsontableModalData?.autoFormatMarkdownTable ?? false;
   const editor = handsontableModalData?.editor;
+  const onSave = handsontableModalData?.onSave;
 
   const defaultMarkdownTable = () => {
     return new MarkdownTable(
@@ -121,7 +122,7 @@ export const HandsontableModal = (): JSX.Element => {
   };
 
   const save = () => {
-    if (hotTable == null || editor == null) {
+    if (hotTable == null) {
       return;
     }
 
@@ -130,8 +131,13 @@ export const HandsontableModal = (): JSX.Element => {
       markdownTableOption.latest,
     ).normalizeCells();
 
-    mtu.replaceFocusedMarkdownTableWithEditor(editor, newMarkdownTable);
+    if (onSave != null) {
+      onSave(newMarkdownTable);
+      cancel();
+      return;
+    }
 
+    mtu.replaceFocusedMarkdownTableWithEditor(editor, newMarkdownTable);
     cancel();
   };
 

+ 30 - 0
packages/app/src/components/ReactMarkdownComponents/TableWithEditButton.module.scss

@@ -0,0 +1,30 @@
+/**
+ * for table with handsontable modal button
+ */
+.test :global {
+  .editable-with-handsontable {
+    position: relative;
+
+    .handsontable-modal-trigger {
+      position: absolute;
+      top: 11px;
+      right: 10px;
+      padding: 0;
+      font-size: 16px;
+      line-height: 1;
+      vertical-align: bottom;
+      background-color: transparent;
+      border: none;
+      opacity: 0;
+    }
+
+    .page-mobile & .handsontable-modal-trigger {
+      opacity: 0.3;
+    }
+
+    &:hover .handsontable-modal-trigger {
+      opacity: 1;
+    }
+  }
+}
+

+ 52 - 0
packages/app/src/components/ReactMarkdownComponents/TableWithEditButton.tsx

@@ -0,0 +1,52 @@
+import React, { useCallback } from 'react';
+
+import EventEmitter from 'events';
+
+import { Element } from 'react-markdown/lib/rehype-filter';
+
+import { useIsGuestUser, useIsSharedUser } from '~/stores/context';
+
+import styles from './TableWithEditButton.module.scss';
+
+declare global {
+  // eslint-disable-next-line vars-on-top, no-var
+  var globalEmitter: EventEmitter;
+}
+
+type TableWithEditButtonProps = {
+  children: React.ReactNode,
+  node: Element,
+}
+
+
+export const TableWithEditButton = React.memo((props: TableWithEditButtonProps): JSX.Element => {
+  const { node, children } = props;
+
+  const { data: isGuestUser } = useIsGuestUser();
+  const { data: isSharedUser } = useIsSharedUser();
+
+  const bol = node.position?.start.line;
+  const eol = node.position?.end.line;
+
+  const editButtonClickHandler = useCallback(() => {
+    globalEmitter.emit('launchHandsonTableModal', bol, eol);
+  }, [bol, eol]);
+
+  const showEditButton = !isGuestUser && !isSharedUser;
+
+  return (
+    <div className={`${styles.test}`}>
+      <div className={`editable-with-handsontable ${styles['editable-with-handsontable']}`}>
+        { showEditButton && (
+          <button className="handsontable-modal-trigger" onClick={editButtonClickHandler}>
+            <i className="icon-note"></i>
+          </button>
+        )}
+        <table className="table table-bordered">
+          {children}
+        </table>
+      </div>
+    </div>
+  );
+});
+TableWithEditButton.displayName = 'TableWithEditButton';

+ 2 - 0
packages/app/src/services/renderer/renderer.tsx

@@ -26,6 +26,7 @@ import { CodeBlock } from '~/components/ReactMarkdownComponents/CodeBlock';
 import { DrawioViewerWithEditButton } from '~/components/ReactMarkdownComponents/DrawioViewerWithEditButton';
 import { Header } from '~/components/ReactMarkdownComponents/Header';
 import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
+import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import loggerFactory from '~/utils/logger';
 
@@ -353,6 +354,7 @@ export const generateViewOptions = (
     components.h3 = Header;
     components.lsx = props => <Lsx {...props} forceToFetchData />;
     components.drawio = DrawioViewerWithEditButton;
+    components.table = TableWithEditButton;
   }
 
   // // Add configurers for viewer

+ 26 - 11
packages/app/src/stores/modal.tsx

@@ -492,27 +492,42 @@ export const useDrawioModal = (status?: DrawioModalStatus): SWRResponse<DrawioMo
 type HandsontableModalStatus = {
   isOpened: boolean,
   table?: MarkdownTable,
-  editor: any,
-  autoFormatMarkdownTable: boolean,
+  editor?: any,
+  autoFormatMarkdownTable?: boolean,
+  onSave?: (table) => any,
 }
 
 type HandsontableModalStatusUtils = {
-  open(table: MarkdownTable, editor: any, autoFormatMarkdownTable: boolean): Promise<HandsontableModalStatus | undefined>
-  close(): Promise<HandsontableModalStatus | undefined>
+  open(
+    table?: MarkdownTable,
+    editor?: any,
+    autoFormatMarkdownTable?: boolean,
+    onSave?: (table) => any
+  ): void // Promise<HandsontableModalStatus | undefined>
+  close(): void // Promise<HandsontableModalStatus | undefined>
 }
 
 export const useHandsontableModal = (status?: HandsontableModalStatus): SWRResponse<HandsontableModalStatus, Error> & HandsontableModalStatusUtils => {
   const initialData: HandsontableModalStatus = {
-    isOpened: false, table: undefined, editor: undefined, autoFormatMarkdownTable: false,
+    isOpened: false,
+    table: undefined,
+    editor: undefined,
+    autoFormatMarkdownTable: false,
   };
   const swrResponse = useStaticSWR<HandsontableModalStatus, Error>('handsontableModalStatus', status, { fallbackData: initialData });
 
-  const open = (table: MarkdownTable, editor: any, autoFormatMarkdownTable: boolean) => swrResponse.mutate({
-    isOpened: true, table, editor, autoFormatMarkdownTable,
-  });
-  const close = () => swrResponse.mutate({
-    isOpened: false, table: undefined, editor: undefined, autoFormatMarkdownTable: false,
-  });
+  const { mutate } = swrResponse;
+
+  const open = useCallback((table: MarkdownTable, editor?: any, autoFormatMarkdownTable?: boolean, onSave?: (table) => any): void => {
+    mutate({
+      isOpened: true, table, editor, autoFormatMarkdownTable, onSave,
+    });
+  }, [mutate]);
+  const close = useCallback((): void => {
+    mutate({
+      isOpened: false, table: undefined, editor: undefined, autoFormatMarkdownTable: false, onSave: undefined,
+    });
+  }, [mutate]);
 
   return {
     ...swrResponse,

+ 0 - 28
packages/app/src/styles/_page.scss

@@ -1,34 +1,6 @@
 // // import diff2html styles
 // @import '~/diff2html/bundles/css/diff2html.min.css';
 
-/**
- * for table with handsontable modal button
- */
-.editable-with-handsontable {
-  position: relative;
-
-  .handsontable-modal-trigger {
-    position: absolute;
-    top: 11px;
-    right: 10px;
-    padding: 0;
-    font-size: 16px;
-    line-height: 1;
-    vertical-align: bottom;
-    background-color: transparent;
-    border: none;
-    opacity: 0;
-  }
-
-  .page-mobile & .handsontable-modal-trigger {
-    opacity: 0.3;
-  }
-
-  &:hover .handsontable-modal-trigger {
-    opacity: 1;
-  }
-}
-
 .card.grw-page-status-alert {
   $margin-bottom: $grw-navbar-bottom-height + 10px;