MarkdownListInterceptor.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import { BasicInterceptor } from 'crowi-pluginkit';
  2. import * as codemirror from 'codemirror';
  3. import mlu from './MarkdownListUtil';
  4. export default class MarkdownListInterceptor extends BasicInterceptor {
  5. constructor() {
  6. super();
  7. this.pasteText = this.pasteText.bind(this);
  8. }
  9. /**
  10. * @inheritdoc
  11. */
  12. isInterceptWhen(contextName) {
  13. return (
  14. contextName === 'preHandleEnter'
  15. );
  16. }
  17. /**
  18. * return boolean value whether processable parallel
  19. */
  20. isProcessableParallel() {
  21. return false;
  22. }
  23. /**
  24. * @inheritdoc
  25. */
  26. process(contextName, ...args) {
  27. const context = Object.assign(args[0]); // clone
  28. const editor = context.editor;
  29. // get strings from current position to EOL(end of line) before break the line
  30. const strToEol = mlu.getStrToEol(editor);
  31. if (mlu.indentAndMarkRE.test(strToEol)) {
  32. codemirror.commands.newlineAndIndent(editor);
  33. // replace the line with strToEol (abort auto indent)
  34. editor.getDoc().replaceRange(strToEol, mlu.getBol(editor), mlu.getEol(editor));
  35. // report to manager that handling was done
  36. context.handlers.push(this.className);
  37. }
  38. // resolve
  39. return Promise.resolve(context);
  40. }
  41. /**
  42. * paste text
  43. * @param {any} editor An editor instance of CodeMirror
  44. * @param {any} event
  45. * @param {string} text
  46. */
  47. pasteText(editor, event, text) {
  48. // get strings from BOL(beginning of line) to current position
  49. const strFromBol = mlu.getStrFromBol(editor);
  50. const matched = strFromBol.match(mlu.indentAndMarkRE);
  51. // when match indentAndMarkOnlyRE
  52. // (this means the current position is the beginning of the list item)
  53. if (mlu.indentAndMarkOnlyRE.test(strFromBol)) {
  54. const adjusted = this.adjustPastedData(strFromBol, text);
  55. // replace
  56. if (adjusted != null) {
  57. event.preventDefault();
  58. editor.getDoc().replaceRange(adjusted, mlu.getBol(editor), editor.getCursor());
  59. }
  60. }
  61. }
  62. /**
  63. * return adjusted pasted data by indentAndMark
  64. *
  65. * @param {string} indentAndMark
  66. * @param {string} text
  67. * @returns adjusted pasted data
  68. * returns null when adjustment is not necessary
  69. */
  70. adjustPastedData(indentAndMark, text) {
  71. let adjusted = null;
  72. // list data (starts with indent and mark)
  73. if (text.match(mlu.indentAndMarkRE)) {
  74. const indent = indentAndMark.match(mlu.indentAndMarkRE)[1];
  75. // splice to an array of line
  76. const lines = text.match(/[^\r\n]+/g);
  77. // indent
  78. const replacedLines = lines.map((line) => {
  79. return indent + line;
  80. })
  81. adjusted = replacedLines.join('\n');
  82. }
  83. // listful data
  84. else if (this.isListfulData(text)) {
  85. // do nothing (return null)
  86. }
  87. // not listful data
  88. else {
  89. // append `indentAndMark` at the beginning of all lines (except the first line)
  90. const replacedText = text.replace(/(\r\n|\r|\n)/g, "$1" + indentAndMark);
  91. // append `indentAndMark` to the first line
  92. adjusted = indentAndMark + replacedText;
  93. }
  94. return adjusted;
  95. }
  96. /**
  97. * evaluate whether `text` is list like data or not
  98. * @param {string} text
  99. */
  100. isListfulData(text) {
  101. // return false if includes at least one blank line
  102. // see https://stackoverflow.com/a/16369725
  103. if (text.match(/^\s*[\r\n]/m) != null) {
  104. return false;
  105. }
  106. const lines = text.match(/[^\r\n]+/g);
  107. // count lines that starts with indent and mark
  108. let isListful = false;
  109. let count = 0;
  110. lines.forEach((line) => {
  111. if (line.match(mlu.indentAndMarkRE)) {
  112. count++;
  113. }
  114. // ensure to be true if it is 50% or more
  115. if (count >= lines.length / 2) {
  116. isListful = true;
  117. return;
  118. }
  119. });
  120. return isListful;
  121. }
  122. }