soumaeda 2 лет назад
Родитель
Сommit
a5f875cffd

+ 8 - 3
apps/app/src/components/PageEditor/HandsontableModal.tsx

@@ -1,5 +1,8 @@
 import React, { useEffect, useState } from 'react';
 
+import {
+  GlobalCodeMirrorEditorKey, useCodeMirrorEditorIsolated,
+} from '@growi/editor';
 import { HotTable } from '@handsontable/react';
 import Handsontable from 'handsontable';
 import { useTranslation } from 'next-i18next';
@@ -11,6 +14,7 @@ import { debounce } from 'throttle-debounce';
 
 import MarkdownTable from '~/client/models/MarkdownTable';
 import mtu from '~/components/PageEditor/MarkdownTableUtil';
+import mtue from '~/components/PageEditor/MarkdownTableUtilForEditor';
 import { useHandsontableModal } from '~/stores/modal';
 
 import ExpandOrContractButton from '../ExpandOrContractButton';
@@ -32,11 +36,12 @@ export const HandsontableModal = (): JSX.Element => {
 
   const { t } = useTranslation('commons');
   const { data: handsontableModalData, close: closeHandsontableModal } = useHandsontableModal();
+  const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
 
   const isOpened = handsontableModalData?.isOpened ?? false;
   const autoFormatMarkdownTable = handsontableModalData?.autoFormatMarkdownTable ?? false;
-  const editor = handsontableModalData?.editor;
   const onSave = handsontableModalData?.onSave;
+  const editor = codeMirrorEditor?.view;
 
   const defaultMarkdownTable = () => {
     return new MarkdownTable(
@@ -101,7 +106,7 @@ export const HandsontableModal = (): JSX.Element => {
   const debouncedHandleWindowExpandedChange = debounce(100, handleWindowExpandedChange);
 
   const handleModalOpen = () => {
-    const editorMarkdownTable = mtu.getMarkdownTable(editor);
+    const editorMarkdownTable = mtue.getMarkdownTable(editor);
     const initTableInstance = editorMarkdownTable == null ? defaultMarkdownTable : editorMarkdownTable.clone();
     setMarkdownTable(editorMarkdownTable ?? defaultMarkdownTable);
     setMarkdownTableOnInit(initTableInstance);
@@ -163,7 +168,7 @@ export const HandsontableModal = (): JSX.Element => {
       return;
     }
 
-    mtu.replaceFocusedMarkdownTableWithEditor(editor, newMarkdownTable);
+    mtue.replaceFocusedMarkdownTableWithEditor(editor, newMarkdownTable);
     cancel();
   };
 

+ 161 - 0
apps/app/src/components/PageEditor/MarkdownTableUtilForEditor.js

@@ -0,0 +1,161 @@
+import MarkdownTable from '~/client/models/MarkdownTable';
+
+/**
+ * Utility for markdown table
+ */
+class MarkdownTableUtil {
+
+  constructor() {
+    // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83
+    this.tableAlignmentLineRE = /^[-:|][-:|\s]*$/;
+    this.tableAlignmentLineNegRE = /^[^-:]*$/; // it is need to check to ignore empty row which is matched above RE
+    // https://regex101.com/r/7BN2fR/10
+    this.linePartOfTableRE = /^([^\r\n|]*)\|(([^\r\n|]*\|)+)$/;
+    // https://regex101.com/r/1UuWBJ/3
+    this.emptyLineOfTableRE = /^([^\r\n|]*)\|((\s*\|)+)$/;
+
+    this.getEot = this.getEot.bind(this);
+    this.getStrFromBot = this.getStrFromBot.bind(this);
+    this.getStrToEot = this.getStrToEot.bind(this);
+    this.isInTable = this.isInTable.bind(this);
+    this.replaceFocusedMarkdownTableWithEditor = this.replaceFocusedMarkdownTableWithEditor.bind(this);
+    this.replaceMarkdownTableWithReformed = this.replaceFocusedMarkdownTableWithEditor; // alias
+  }
+
+  /**
+   * return the postion of the BOT(beginning of table)
+   * (If the cursor is not in a table, return its position)
+   */
+  getBot(editor) {
+    const curPos = editor.getCursor();
+    if (!this.isInTable(editor)) {
+      return { line: curPos.line, ch: curPos.ch };
+    }
+
+    const firstLine = editor.getDoc().firstLine();
+    let line = curPos.line - 1;
+    for (; line >= firstLine; line--) {
+      const strLine = editor.getDoc().getLine(line);
+      if (!this.linePartOfTableRE.test(strLine)) {
+        break;
+      }
+    }
+    const botLine = Math.max(firstLine, line + 1);
+    return { line: botLine, ch: 0 };
+  }
+
+  /**
+   * return the postion of the EOT(end of table)
+   * (If the cursor is not in a table, return its position)
+   */
+  getEot(editor) {
+    const curPos = editor.getCursor();
+    if (!this.isInTable(editor)) {
+      return { line: curPos.line, ch: curPos.ch };
+    }
+
+    const lastLine = editor.getDoc().lastLine();
+    let line = curPos.line + 1;
+    for (; line <= lastLine; line++) {
+      const strLine = editor.getDoc().getLine(line);
+      if (!this.linePartOfTableRE.test(strLine)) {
+        break;
+      }
+    }
+    const eotLine = Math.min(line - 1, lastLine);
+    const lineLength = editor.getDoc().getLine(eotLine).length;
+    return { line: eotLine, ch: lineLength };
+  }
+
+  /**
+   * return strings from BOT(beginning of table) to the cursor position
+   */
+  getStrFromBot(editor) {
+    const curPos = editor.getCursor();
+    return editor.getDoc().getRange(this.getBot(editor), curPos);
+  }
+
+  /**
+   * return strings from the cursor position to EOT(end of table)
+   */
+  getStrToEot(editor) {
+    const curPos = editor.getCursor();
+    return editor.getDoc().getRange(curPos, this.getEot(editor));
+  }
+
+  /**
+   * return MarkdownTable instance of the table where the cursor is
+   * (If the cursor is not in a table, return null)
+   */
+  getMarkdownTable(editor) {
+    if (!this.isInTable(editor)) {
+      return null;
+    }
+
+    const strFromBotToEot = editor.getDoc().getRange(this.getBot(editor), this.getEot(editor));
+    return MarkdownTable.fromMarkdownString(strFromBotToEot);
+  }
+
+  /**
+   * return boolean value whether the cursor position is end of line
+   */
+  isEndOfLine(editor) {
+    const curPos = editor.getCursor();
+    return (curPos.ch === editor.getDoc().getLine(curPos.line).length);
+  }
+
+  /**
+   * return boolean value whether the cursor position is in a table
+   */
+  isInTable(editor) {
+    const curPos = editor.getCursor();
+    return this.linePartOfTableRE.test(editor.getDoc().getLine(curPos.line));
+  }
+
+  /**
+   * add a row at the end
+   * (This function overwrite directory markdown table specified as argument.)
+   * @param {MarkdownTable} markdown table
+   */
+  addRowToMarkdownTable(mdtable) {
+    const numCol = mdtable.table.length > 0 ? mdtable.table[0].length : 1;
+    const newRow = [];
+    (new Array(numCol)).forEach(() => { return newRow.push('') }); // create cols
+    mdtable.table.push(newRow);
+  }
+
+  /**
+   * return markdown table that is merged all of markdown table in array
+   * (The merged markdown table options are used for the first markdown table.)
+   * @param {Array} array of markdown table
+   */
+  mergeMarkdownTable(mdtableList) {
+    if (mdtableList == null || !(mdtableList instanceof Array)) {
+      return undefined;
+    }
+
+    let newTable = [];
+    const options = mdtableList[0].options; // use option of first markdown-table
+    mdtableList.forEach((mdtable) => {
+      newTable = newTable.concat(mdtable.table);
+    });
+    return (new MarkdownTable(newTable, options));
+  }
+
+  /**
+   * replace focused markdown table with editor
+   * (A replaced table is reformed by markdown-table.)
+   * @param {MarkdownTable} table
+   */
+  replaceFocusedMarkdownTableWithEditor(editor, table) {
+    const curPos = editor.getCursor();
+    editor.getDoc().replaceRange(table.toString(), this.getBot(editor), this.getEot(editor));
+    editor.getDoc().setCursor(curPos.line + 1, 2);
+  }
+
+}
+
+// singleton pattern
+const instance = new MarkdownTableUtil();
+Object.freeze(instance);
+export default instance;

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

@@ -1,7 +1,7 @@
 import { useCallback } from 'react';
 
 import { useCodeMirrorEditorIsolated } from '../../../stores';
-import { useHandsontableModal } from '../../../stores/use-table-modal';
+import { useHandsontableModal } from '../../../stores/use-hands-on-table';
 
 type Props = {
   editorKey: string,
@@ -15,7 +15,6 @@ export const TableButton = (props: Props): JSX.Element => {
 
   const openTableModalHandler = useCallback(() => {
     const editor = codeMirrorEditor?.view;
-    console.log(editor);
     openTableModal(editor);
   }, [codeMirrorEditor]);
 

+ 3 - 6
packages/editor/src/stores/use-table-modal.ts → packages/editor/src/stores/use-hands-on-table.ts

@@ -7,14 +7,12 @@ type HandsonTableModalSaveHandler = () => void;
 
 type HandsontableModalStatus = {
   isOpened: boolean,
-  editor: any,
   // onSave is passed only when editing table directly from the page.
   onSave?: HandsonTableModalSaveHandler
 }
 
 type HandsontableModalStatusUtils = {
   open(
-    editor: any,
     onSave?: HandsonTableModalSaveHandler
   ): void
   close(): void
@@ -23,21 +21,20 @@ type HandsontableModalStatusUtils = {
 export const useHandsontableModal = (status?: HandsontableModalStatus): SWRResponse<HandsontableModalStatus, Error> & HandsontableModalStatusUtils => {
   const initialData: HandsontableModalStatus = {
     isOpened: false,
-    editor: undefined,
   };
 
   const swrResponse = useSWRStatic<HandsontableModalStatus, Error>('handsontableModalStatus', status, { fallbackData: initialData });
 
   const { mutate } = swrResponse;
 
-  const open = useCallback((editor: any, onSave?: HandsonTableModalSaveHandler): void => {
+  const open = useCallback((onSave?: HandsonTableModalSaveHandler): void => {
     mutate({
-      isOpened: true, editor, onSave,
+      isOpened: true, onSave,
     });
   }, [mutate]);
   const close = useCallback((): void => {
     mutate({
-      isOpened: false, editor: undefined, onSave: undefined,
+      isOpened: false, onSave: undefined,
     });
   }, [mutate]);