markdown-table-util-for-editor.ts 4.3 KB

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