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

Merge pull request #7012 from weseek/feat/108177-direct-editing-from-page

feat: HandsonTableModal direct editing from Page
Ryoji Shimizu 3 лет назад
Родитель
Сommit
bbbc9e235d

+ 6 - 0
packages/app/src/components/Page.module.scss

@@ -0,0 +1,6 @@
+// mobile
+.page-mobile :global {
+  .editable-with-handsontable .handsontable-modal-trigger {
+    opacity: 0.3;
+  }
+}

+ 63 - 2
packages/app/src/components/Page.tsx

@@ -10,6 +10,7 @@ import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 import { HtmlElementNode } from 'rehype-toc';
 import { HtmlElementNode } from 'rehype-toc';
 
 
+import MarkdownTable from '~/client/models/MarkdownTable';
 import { useSaveOrUpdate } from '~/client/services/page-operation';
 import { useSaveOrUpdate } from '~/client/services/page-operation';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { OptionsToSave } from '~/interfaces/page-operation';
 import { OptionsToSave } from '~/interfaces/page-operation';
@@ -17,7 +18,7 @@ import {
   useIsGuestUser, useShareLinkId,
   useIsGuestUser, useShareLinkId,
 } from '~/stores/context';
 } from '~/stores/context';
 import { useEditingMarkdown } from '~/stores/editor';
 import { useEditingMarkdown } from '~/stores/editor';
-import { useDrawioModal } from '~/stores/modal';
+import { useDrawioModal, useHandsontableModal } from '~/stores/modal';
 import { useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
 import { useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
 import { useViewOptions } from '~/stores/renderer';
 import { useViewOptions } from '~/stores/renderer';
 import {
 import {
@@ -28,6 +29,9 @@ import loggerFactory from '~/utils/logger';
 
 
 import RevisionRenderer from './Page/RevisionRenderer';
 import RevisionRenderer from './Page/RevisionRenderer';
 import mdu from './PageEditor/MarkdownDrawioUtil';
 import mdu from './PageEditor/MarkdownDrawioUtil';
+import mtu from './PageEditor/MarkdownTableUtil';
+
+import styles from './Page.module.scss';
 
 
 
 
 declare global {
 declare global {
@@ -62,6 +66,7 @@ export const Page = (props) => {
   const { data: rendererOptions } = useViewOptions(storeTocNodeHandler);
   const { data: rendererOptions } = useViewOptions(storeTocNodeHandler);
   const { mutate: mutateCurrentPageTocNode } = useCurrentPageTocNode();
   const { mutate: mutateCurrentPageTocNode } = useCurrentPageTocNode();
   const { open: openDrawioModal } = useDrawioModal();
   const { open: openDrawioModal } = useDrawioModal();
+  const { open: openHandsontableModal } = useHandsontableModal();
 
 
   const saveOrUpdate = useSaveOrUpdate();
   const saveOrUpdate = useSaveOrUpdate();
 
 
@@ -72,6 +77,7 @@ export const Page = (props) => {
   }, [mutateCurrentPageTocNode, tocRef.current]); // include tocRef.current to call mutateCurrentPageTocNode when tocRef.current changes
   }, [mutateCurrentPageTocNode, tocRef.current]); // include tocRef.current to call mutateCurrentPageTocNode when tocRef.current changes
 
 
 
 
+  // TODO: refactor commonize saveByDrawioModal and saveByHandsontableModal
   const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
   const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
     if (currentPage == null || tagsInfo == null) {
     if (currentPage == null || tagsInfo == null) {
       return;
       return;
@@ -131,6 +137,61 @@ export const Page = (props) => {
     };
     };
   }, [openDrawioModal, saveByDrawioModal, shareLinkId]);
   }, [openDrawioModal, saveByDrawioModal, shareLinkId]);
 
 
+  const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
+    if (currentPage == null || tagsInfo == null || shareLinkId != 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, shareLinkId, t, tagsInfo]);
+
+  // set handler to open HandsonTableModal
+  useEffect(() => {
+    if (currentPage == null || shareLinkId != null) {
+      return;
+    }
+
+    const handler = (bol: number, eol: number) => {
+      const markdown = currentPage.revision.body;
+      const currentMarkdownTable = mtu.getMarkdownTableFromLine(markdown, bol, eol);
+      openHandsontableModal(currentMarkdownTable, undefined, false, table => saveByHandsontableModal(table, bol, eol));
+    };
+    globalEmitter.on('launchHandsonTableModal', handler);
+
+    return function cleanup() {
+      globalEmitter.removeListener('launchHandsonTableModal', handler);
+    };
+  }, [currentPage, openHandsontableModal, saveByHandsontableModal, shareLinkId]);
+
   if (currentPage == null || isGuestUser == null || rendererOptions == null) {
   if (currentPage == null || isGuestUser == null || rendererOptions == null) {
     const entries = Object.entries({
     const entries = Object.entries({
       currentPage, isGuestUser, rendererOptions,
       currentPage, isGuestUser, rendererOptions,
@@ -145,7 +206,7 @@ export const Page = (props) => {
   const { _id: revisionId, body: markdown } = currentPage.revision;
   const { _id: revisionId, body: markdown } = currentPage.revision;
 
 
   return (
   return (
-    <div className={`mb-5 ${isMobile ? 'page-mobile' : ''}`}>
+    <div className={`mb-5 ${isMobile ? `page-mobile ${styles['page-mobile']}` : ''}`}>
 
 
       { revisionId != null && (
       { revisionId != null && (
         <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />
         <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown} />

+ 14 - 9
packages/app/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -159,6 +159,7 @@ class CodeMirrorEditor extends AbstractEditor {
 
 
     this.foldDrawioSection = this.foldDrawioSection.bind(this);
     this.foldDrawioSection = this.foldDrawioSection.bind(this);
     this.clickDrawioIconHandler = this.clickDrawioIconHandler.bind(this);
     this.clickDrawioIconHandler = this.clickDrawioIconHandler.bind(this);
+    this.clickTableIconHandler = this.clickTableIconHandler.bind(this);
 
 
   }
   }
 
 
@@ -891,6 +892,16 @@ class CodeMirrorEditor extends AbstractEditor {
     );
     );
   }
   }
 
 
+  clickTableIconHandler() {
+    const markdownTable = mtu.getMarkdownTable(this.getCodeMirror());
+
+    this.props.onClickTableBtn(
+      markdownTable,
+      this.getCodeMirror(),
+      this.props.editorSettings.autoFormatMarkdownTable,
+    );
+  }
+
   getNavbarItems() {
   getNavbarItems() {
     return [
     return [
       <Button
       <Button
@@ -1016,13 +1027,7 @@ class CodeMirrorEditor extends AbstractEditor {
         color={null}
         color={null}
         size="sm"
         size="sm"
         title="Table"
         title="Table"
-        onClick={() => {
-          this.props.onClickTableBtn(
-            mtu.getMarkdownTable(this.getCodeMirror()),
-            this.getCodeMirror(),
-            this.props.editorSettings.autoFormatMarkdownTable,
-          );
-        }}
+        onClick={this.clickTableIconHandler}
       >
       >
         <EditorIcon icon="Table" />
         <EditorIcon icon="Table" />
       </Button>,
       </Button>,
@@ -1164,8 +1169,8 @@ const CodeMirrorEditorFc = React.forwardRef((props, ref) => {
     openDrawioModal(drawioMxFile, onSave);
     openDrawioModal(drawioMxFile, onSave);
   }, [openDrawioModal]);
   }, [openDrawioModal]);
 
 
-  const openTableModalHandler = useCallback((table, editor, autoFormatMarkdownTable) => {
-    openHandsontableModal(table, editor, autoFormatMarkdownTable);
+  const openTableModalHandler = useCallback((markdownTable, editor, autoFormatMarkdownTable) => {
+    openHandsontableModal(markdownTable, editor, autoFormatMarkdownTable);
   }, [openHandsontableModal]);
   }, [openHandsontableModal]);
 
 
   return (
   return (

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

@@ -32,10 +32,12 @@ export const HandsontableModal = (): JSX.Element => {
 
 
   const { t } = useTranslation('commons');
   const { t } = useTranslation('commons');
   const { data: handsontableModalData, close: closeHandsontableModal } = useHandsontableModal();
   const { data: handsontableModalData, close: closeHandsontableModal } = useHandsontableModal();
+
   const isOpened = handsontableModalData?.isOpened ?? false;
   const isOpened = handsontableModalData?.isOpened ?? false;
   const table = handsontableModalData?.table;
   const table = handsontableModalData?.table;
   const autoFormatMarkdownTable = handsontableModalData?.autoFormatMarkdownTable ?? false;
   const autoFormatMarkdownTable = handsontableModalData?.autoFormatMarkdownTable ?? false;
   const editor = handsontableModalData?.editor;
   const editor = handsontableModalData?.editor;
+  const onSave = handsontableModalData?.onSave;
 
 
   const defaultMarkdownTable = () => {
   const defaultMarkdownTable = () => {
     return new MarkdownTable(
     return new MarkdownTable(
@@ -121,7 +123,7 @@ export const HandsontableModal = (): JSX.Element => {
   };
   };
 
 
   const save = () => {
   const save = () => {
-    if (hotTable == null || editor == null) {
+    if (hotTable == null) {
       return;
       return;
     }
     }
 
 
@@ -130,8 +132,14 @@ export const HandsontableModal = (): JSX.Element => {
       markdownTableOption.latest,
       markdownTableOption.latest,
     ).normalizeCells();
     ).normalizeCells();
 
 
-    mtu.replaceFocusedMarkdownTableWithEditor(editor, newMarkdownTable);
+    // onSave is passed only when editing table directly from the page.
+    if (onSave != null) {
+      onSave(newMarkdownTable);
+      cancel();
+      return;
+    }
 
 
+    mtu.replaceFocusedMarkdownTableWithEditor(editor, newMarkdownTable);
     cancel();
     cancel();
   };
   };
 
 

+ 5 - 0
packages/app/src/components/PageEditor/MarkdownTableUtil.js

@@ -96,6 +96,11 @@ class MarkdownTableUtil {
     return MarkdownTable.fromMarkdownString(strFromBotToEot);
     return MarkdownTable.fromMarkdownString(strFromBotToEot);
   }
   }
 
 
+  getMarkdownTableFromLine(markdown, bol, eol) {
+    const tableLines = markdown.split(/\r\n|\r|\n/).slice(bol - 1, eol).join('\n');
+    return MarkdownTable.fromMarkdownString(tableLines);
+  }
+
   /**
   /**
    * return boolean value whether the cursor position is end of line
    * return boolean value whether the cursor position is end of line
    */
    */

+ 3 - 2
packages/app/src/components/ReactMarkdownComponents/DrawioViewerWithEditButton.tsx

@@ -8,7 +8,7 @@ import {
 } from '@growi/remark-drawio-plugin';
 } from '@growi/remark-drawio-plugin';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
-import { useIsGuestUser, useIsSharedUser } from '~/stores/context';
+import { useIsGuestUser, useIsSharedUser, useShareLinkId } from '~/stores/context';
 
 
 import styles from './DrawioViewerWithEditButton.module.scss';
 import styles from './DrawioViewerWithEditButton.module.scss';
 
 
@@ -26,6 +26,7 @@ export const DrawioViewerWithEditButton = React.memo((props: DrawioViewerProps):
 
 
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isSharedUser } = useIsSharedUser();
   const { data: isSharedUser } = useIsSharedUser();
+  const { data: shareLinkId } = useShareLinkId();
 
 
   const [isRendered, setRendered] = useState(false);
   const [isRendered, setRendered] = useState(false);
   const [mxfile, setMxfile] = useState('');
   const [mxfile, setMxfile] = useState('');
@@ -49,7 +50,7 @@ export const DrawioViewerWithEditButton = React.memo((props: DrawioViewerProps):
     }
     }
   }, []);
   }, []);
 
 
-  const showEditButton = isRendered && !isGuestUser && !isSharedUser;
+  const showEditButton = isRendered && !isGuestUser && !isSharedUser && shareLinkId == null;
 
 
   return (
   return (
     <div className={`drawio-viewer-with-edit-button ${styles['drawio-viewer-with-edit-button']}`}>
     <div className={`drawio-viewer-with-edit-button ${styles['drawio-viewer-with-edit-button']}`}>

+ 11 - 2
packages/app/src/components/ReactMarkdownComponents/Header.tsx

@@ -5,8 +5,9 @@ import EventEmitter from 'events';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
 import { Element } from 'react-markdown/lib/rehype-filter';
 import { Element } from 'react-markdown/lib/rehype-filter';
 
 
-import { NextLink } from './NextLink';
+import { useIsGuestUser, useIsSharedUser, useShareLinkId } from '~/stores/context';
 
 
+import { NextLink } from './NextLink';
 
 
 import styles from './Header.module.scss';
 import styles from './Header.module.scss';
 
 
@@ -55,6 +56,10 @@ export const Header = (props: HeaderProps): JSX.Element => {
     node, id, children, level,
     node, id, children, level,
   } = props;
   } = props;
 
 
+  const { data: isGuestUser } = useIsGuestUser();
+  const { data: isSharedUser } = useIsSharedUser();
+  const { data: shareLinkId } = useShareLinkId();
+
   const router = useRouter();
   const router = useRouter();
 
 
   const [isActive, setActive] = useState(false);
   const [isActive, setActive] = useState(false);
@@ -80,13 +85,17 @@ export const Header = (props: HeaderProps): JSX.Element => {
     };
     };
   }, [activateByHash, router.events]);
   }, [activateByHash, router.events]);
 
 
+  const showEditButton = !isGuestUser && !isSharedUser && shareLinkId == null;
+
   return (
   return (
     <CustomTag id={id} className={`revision-head ${styles['revision-head']} ${isActive ? 'blink' : ''}`}>
     <CustomTag id={id} className={`revision-head ${styles['revision-head']} ${isActive ? 'blink' : ''}`}>
       {children}
       {children}
       <NextLink href={`#${id}`} className="revision-head-link">
       <NextLink href={`#${id}`} className="revision-head-link">
         <span className="icon-link"></span>
         <span className="icon-link"></span>
       </NextLink>
       </NextLink>
-      <EditLink line={node.position?.start.line} />
+      {showEditButton && (
+        <EditLink line={node.position?.start.line} />
+      )}
     </CustomTag>
     </CustomTag>
   );
   );
 };
 };

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

@@ -0,0 +1,25 @@
+/**
+ * for table with handsontable modal button
+ */
+.editable-with-handsontable :global {
+  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;
+  }
+}
+
+.editable-with-handsontable:hover :global {
+  .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, useShareLinkId } 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,
+  className?: string
+}
+
+export const TableWithEditButton = React.memo((props: TableWithEditButtonProps): JSX.Element => {
+
+  const { children, node, className } = props;
+
+  const { data: isGuestUser } = useIsGuestUser();
+  const { data: isSharedUser } = useIsSharedUser();
+  const { data: shareLinkId } = useShareLinkId();
+
+  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 && shareLinkId == null;
+
+  return (
+    <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={`${className}`}>
+        {children}
+      </table>
+    </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 { DrawioViewerWithEditButton } from '~/components/ReactMarkdownComponents/DrawioViewerWithEditButton';
 import { Header } from '~/components/ReactMarkdownComponents/Header';
 import { Header } from '~/components/ReactMarkdownComponents/Header';
 import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
 import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
+import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -353,6 +354,7 @@ export const generateViewOptions = (
     components.h3 = Header;
     components.h3 = Header;
     components.lsx = props => <Lsx {...props} forceToFetchData />;
     components.lsx = props => <Lsx {...props} forceToFetchData />;
     components.drawio = DrawioViewerWithEditButton;
     components.drawio = DrawioViewerWithEditButton;
+    components.table = TableWithEditButton;
   }
   }
 
 
   // // Add configurers for viewer
   // // Add configurers for viewer

+ 45 - 12
packages/app/src/stores/modal.tsx

@@ -489,30 +489,63 @@ export const useDrawioModal = (status?: DrawioModalStatus): SWRResponse<DrawioMo
 /*
 /*
 * HandsonTableModal
 * HandsonTableModal
 */
 */
+type HandsonTableModalSaveHandler = (table: MarkdownTable) => void;
+
 type HandsontableModalStatus = {
 type HandsontableModalStatus = {
   isOpened: boolean,
   isOpened: boolean,
-  table?: MarkdownTable,
-  editor: any,
-  autoFormatMarkdownTable: boolean,
+  table: MarkdownTable,
+  // TODO: Define editor type
+  editor?: any,
+  autoFormatMarkdownTable?: boolean,
+  // onSave is passed only when editing table directly from the page.
+  onSave?: HandsonTableModalSaveHandler
 }
 }
 
 
 type HandsontableModalStatusUtils = {
 type HandsontableModalStatusUtils = {
-  open(table: MarkdownTable, editor: any, autoFormatMarkdownTable: boolean): Promise<HandsontableModalStatus | undefined>
-  close(): Promise<HandsontableModalStatus | undefined>
+  open(
+    table: MarkdownTable,
+    editor?: any,
+    autoFormatMarkdownTable?: boolean,
+    onSave?: HandsonTableModalSaveHandler
+  ): void
+  close(): void
 }
 }
 
 
+const defaultMarkdownTable = () => {
+  return new MarkdownTable(
+    [
+      ['col1', 'col2', 'col3'],
+      ['', '', ''],
+      ['', '', ''],
+    ],
+    {
+      align: ['', '', ''],
+    },
+  );
+};
+
 export const useHandsontableModal = (status?: HandsontableModalStatus): SWRResponse<HandsontableModalStatus, Error> & HandsontableModalStatusUtils => {
 export const useHandsontableModal = (status?: HandsontableModalStatus): SWRResponse<HandsontableModalStatus, Error> & HandsontableModalStatusUtils => {
   const initialData: HandsontableModalStatus = {
   const initialData: HandsontableModalStatus = {
-    isOpened: false, table: undefined, editor: undefined, autoFormatMarkdownTable: false,
+    isOpened: false,
+    table: defaultMarkdownTable(),
+    editor: undefined,
+    autoFormatMarkdownTable: false,
   };
   };
+
   const swrResponse = useStaticSWR<HandsontableModalStatus, Error>('handsontableModalStatus', status, { fallbackData: initialData });
   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?: HandsonTableModalSaveHandler): void => {
+    mutate({
+      isOpened: true, table, editor, autoFormatMarkdownTable, onSave,
+    });
+  }, [mutate]);
+  const close = useCallback((): void => {
+    mutate({
+      isOpened: false, table: defaultMarkdownTable(), editor: undefined, autoFormatMarkdownTable: false, onSave: undefined,
+    });
+  }, [mutate]);
 
 
   return {
   return {
     ...swrResponse,
     ...swrResponse,

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

@@ -1,34 +1,6 @@
 // // import diff2html styles
 // // import diff2html styles
 // @import '~/diff2html/bundles/css/diff2html.min.css';
 // @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 {
 .card.grw-page-status-alert {
   $margin-bottom: $grw-navbar-bottom-height + 10px;
   $margin-bottom: $grw-navbar-bottom-height + 10px;