PasteHelper.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. class PasteHelper {
  2. constructor() {
  3. // https://regex101.com/r/7BN2fR/3
  4. this.indentAndMarkPattern = /^([ \t]*)(?:>|\-|\+|\*|\d+\.) /;
  5. this.pasteHandler = this.pasteHandler.bind(this);
  6. this.pasteText = this.pasteText.bind(this);
  7. this.adjustPastedData = this.adjustPastedData.bind(this);
  8. }
  9. /**
  10. * CodeMirror paste event handler
  11. * see: https://codemirror.net/doc/manual.html#events
  12. * @param {any} editor An editor instance of CodeMirror
  13. * @param {any} event
  14. */
  15. pasteHandler(editor, event) {
  16. if (event.clipboardData.types.includes('text/plain') > -1) {
  17. this.pasteText(editor, event);
  18. }
  19. }
  20. /**
  21. * paste text
  22. * @param {any} editor An editor instance of CodeMirror
  23. * @param {any} event
  24. */
  25. pasteText(editor, event) {
  26. // get data in clipboard
  27. let text = event.clipboardData.getData('text/plain');
  28. if (text.length == 0) { return; }
  29. const curPos = editor.getCursor();
  30. const bol = { line: curPos.line, ch: 0 }; // beginning of line
  31. // get strings from BOL(beginning of line) to current position
  32. const strFromBol = editor.getDoc().getRange(bol, curPos);
  33. const matched = strFromBol.match(this.indentAndMarkPattern);
  34. // when match completely to pattern
  35. // (this means the current position is the beginning of the list item)
  36. if (matched && matched[0] == strFromBol) {
  37. const adjusted = this.adjustPastedData(strFromBol, text);
  38. // replace
  39. if (adjusted != null) {
  40. event.preventDefault();
  41. editor.getDoc().replaceRange(adjusted, bol, curPos);
  42. }
  43. }
  44. }
  45. /**
  46. * return adjusted pasted data by indentAndMark
  47. *
  48. * @param {string} indentAndMark
  49. * @param {string} text
  50. * @returns adjusted pasted data
  51. * returns null when adjustment is not necessary
  52. */
  53. adjustPastedData(indentAndMark, text) {
  54. let adjusted = null;
  55. // list data (starts with indent and mark)
  56. if (text.match(this.indentAndMarkPattern)) {
  57. const indent = indentAndMark.match(this.indentAndMarkPattern)[1];
  58. // splice to an array of line
  59. const lines = text.match(/[^\r\n]+/g);
  60. // indent
  61. const replacedLines = lines.map((line) => {
  62. return indent + line;
  63. })
  64. adjusted = replacedLines.join('\n');
  65. }
  66. // listful data
  67. else if (this.isListfulData(text)) {
  68. // do nothing (return null)
  69. }
  70. // not listful data
  71. else {
  72. // append `indentAndMark` at the beginning of all lines (except the first line)
  73. const replacedText = text.replace(/(\r\n|\r|\n)/g, "$1" + indentAndMark);
  74. // append `indentAndMark` to the first line
  75. adjusted = indentAndMark + replacedText;
  76. }
  77. return adjusted;
  78. }
  79. /**
  80. * evaluate whether `text` is list like data or not
  81. * @param {string} text
  82. */
  83. isListfulData(text) {
  84. // return false if includes at least one blank line
  85. // see https://stackoverflow.com/a/16369725
  86. if (text.match(/^\s*[\r\n]/m) != null) {
  87. return false;
  88. }
  89. const lines = text.match(/[^\r\n]+/g);
  90. // count lines that starts with indent and mark
  91. let isListful = false;
  92. let count = 0;
  93. lines.forEach((line) => {
  94. if (line.match(this.indentAndMarkPattern)) {
  95. count++;
  96. }
  97. // ensure to be true if it is 50% or more
  98. if (count >= lines.length / 2) {
  99. isListful = true;
  100. return;
  101. }
  102. });
  103. return isListful;
  104. }
  105. }
  106. // singleton pattern
  107. const instance = new PasteHelper();
  108. Object.freeze(instance);
  109. export default instance;