MarkdownTableUtilForEditor.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { EditorView } from '@codemirror/view';
  2. import MarkdownTable from '~/client/models/MarkdownTable';
  3. /**
  4. * Utility for markdown table
  5. */
  6. // https://regex101.com/r/7BN2fR/10
  7. const linePartOfTableRE = /^([^\r\n|]*)\|(([^\r\n|]*\|)+)$/;
  8. const curPos = (editor: EditorView): number => {
  9. return editor.state.selection.main.head;
  10. };
  11. /**
  12. * return boolean value whether the cursor position is in a table
  13. */
  14. export const isInTable = (editor: EditorView): boolean => {
  15. const lineText = editor.state.doc.lineAt(curPos(editor)).text;
  16. return linePartOfTableRE.test(lineText);
  17. };
  18. /**
  19. * return the postion of the BOT(beginning of table)
  20. * (If the cursor is not in a table, return its position)
  21. */
  22. export const getBot = (editor: EditorView): number => {
  23. if (!isInTable(editor)) {
  24. return curPos(editor);
  25. }
  26. const doc = editor.state.doc;
  27. const firstLineNum = 1;
  28. let line = doc.lineAt(curPos(editor)).number - 1;
  29. for (; line >= firstLineNum; line--) {
  30. const strLine = doc.line(line).text;
  31. if (!linePartOfTableRE.test(strLine)) {
  32. break;
  33. }
  34. }
  35. const botLineNum = Math.max(firstLineNum, line + 1);
  36. return doc.line(botLineNum).from;
  37. };
  38. /**
  39. * return the postion of the EOT(end of table)
  40. * (If the cursor is not in a table, return its position)
  41. */
  42. export const getEot = (editor: EditorView): number => {
  43. if (!isInTable(editor)) {
  44. return curPos(editor);
  45. }
  46. const doc = editor.state.doc;
  47. const lastLineNum = doc.line(doc.lines).number;
  48. let line = doc.lineAt(curPos(editor)).number + 1;
  49. for (; line <= lastLineNum; line++) {
  50. const strLine = doc.line(line).text;
  51. if (!linePartOfTableRE.test(strLine)) {
  52. break;
  53. }
  54. }
  55. const eotLineNum = Math.min(line - 1, lastLineNum);
  56. return doc.line(eotLineNum).to;
  57. };
  58. /**
  59. * return strings from BOT(beginning of table) to the cursor position
  60. */
  61. export const getStrFromBot = (editor: EditorView): string => {
  62. return editor.state.sliceDoc(getBot(editor), curPos(editor));
  63. };
  64. /**
  65. * return strings from the cursor position to EOT(end of table)
  66. */
  67. export const getStrToEot = (editor: EditorView): string => {
  68. return editor.state.sliceDoc(curPos(editor), getEot(editor));
  69. };
  70. /**
  71. * return MarkdownTable instance of the table where the cursor is
  72. * (If the cursor is not in a table, return null)
  73. */
  74. export const getMarkdownTable = (editor: EditorView): MarkdownTable | undefined => {
  75. if (!isInTable(editor)) {
  76. return;
  77. }
  78. const strFromBotToEot = editor.state.sliceDoc(getBot(editor), getEot(editor));
  79. return MarkdownTable.fromMarkdownString(strFromBotToEot);
  80. };
  81. export const getMarkdownTableFromLine = (markdown: string, bol: number, eol: number): MarkdownTable => {
  82. const tableLines = markdown.split(/\r\n|\r|\n/).slice(bol - 1, eol).join('\n');
  83. return MarkdownTable.fromMarkdownString(tableLines);
  84. };
  85. /**
  86. * return boolean value whether the cursor position is end of line
  87. */
  88. export const isEndOfLine = (editor: EditorView): boolean => {
  89. return (curPos(editor) === editor.state.doc.lineAt(curPos(editor)).number);
  90. };
  91. /**
  92. * add a row at the end
  93. * (This function overwrite directory markdown table specified as argument.)
  94. * @param {MarkdownTable} markdown table
  95. */
  96. export const addRowToMarkdownTable = (mdtable: MarkdownTable): any => {
  97. const numCol = mdtable.table.length > 0 ? mdtable.table[0].length : 1;
  98. const newRow: string[] = [];
  99. (new Array(numCol)).forEach(() => { return newRow.push('') }); // create cols
  100. mdtable.table.push(newRow);
  101. };
  102. /**
  103. * return markdown table that is merged all of markdown table in array
  104. * (The merged markdown table options are used for the first markdown table.)
  105. * @param {Array} array of markdown table
  106. */
  107. export const mergeMarkdownTable = (mdtableList: MarkdownTable): MarkdownTable | undefined => {
  108. if (mdtableList == null || !(mdtableList instanceof Array)) {
  109. return undefined;
  110. }
  111. let newTable = [];
  112. const options = mdtableList[0].options; // use option of first markdown-table
  113. mdtableList.forEach((mdtable) => {
  114. newTable = newTable.concat(mdtable.table);
  115. });
  116. return (new MarkdownTable(newTable, options));
  117. };
  118. /**
  119. * replace focused markdown table with editor
  120. * (A replaced table is reformed by markdown-table.)
  121. * @param {MarkdownTable} table
  122. */
  123. export const replaceFocusedMarkdownTableWithEditor = (editor: EditorView, table: MarkdownTable): void => {
  124. const botPos = getBot(editor);
  125. const eotPos = getEot(editor);
  126. editor.dispatch({
  127. changes: {
  128. from: botPos,
  129. to: eotPos,
  130. insert: table.toString(),
  131. },
  132. });
  133. editor.dispatch({
  134. selection: { anchor: editor.state.doc.lineAt(curPos(editor)).to },
  135. });
  136. };
  137. /**
  138. * return markdown where the markdown table specified by line number params is replaced to the markdown table specified by table param
  139. * @param {string} markdown
  140. * @param {MarkdownTable} table
  141. * @param beginLineNumber
  142. * @param endLineNumber
  143. */
  144. export const replaceMarkdownTableInMarkdown = (table: MarkdownTable, markdown: string, beginLineNumber: number, endLineNumber: number): string => {
  145. const splitMarkdown = markdown.split(/\r\n|\r|\n/);
  146. const markdownBeforeTable = splitMarkdown.slice(0, beginLineNumber - 1);
  147. const markdownAfterTable = splitMarkdown.slice(endLineNumber);
  148. let newMarkdown = '';
  149. if (markdownBeforeTable.length > 0) {
  150. newMarkdown += `${markdownBeforeTable.join('\n')}\n`;
  151. }
  152. newMarkdown += table;
  153. if (markdownAfterTable.length > 0) {
  154. newMarkdown += `\n${markdownAfterTable.join('\n')}`;
  155. }
  156. return newMarkdown;
  157. };