indentation.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { RangeJSON, Range as SlateRange, Editor as CoreEditor } from 'slate';
  2. import { Plugin } from '@grafana/slate-react';
  3. import { isKeyHotkey } from 'is-hotkey';
  4. const isIndentLeftHotkey = isKeyHotkey('mod+[');
  5. const isShiftTabHotkey = isKeyHotkey('shift+tab');
  6. const isIndentRightHotkey = isKeyHotkey('mod+]');
  7. const SLATE_TAB = ' ';
  8. const handleTabKey = (event: KeyboardEvent, editor: CoreEditor, next: Function): void => {
  9. const {
  10. startBlock,
  11. endBlock,
  12. selection: {
  13. start: { offset: startOffset, key: startKey },
  14. end: { offset: endOffset, key: endKey },
  15. },
  16. } = editor.value;
  17. const first = startBlock.getFirstText();
  18. const startBlockIsSelected =
  19. startOffset === 0 && startKey === first.key && endOffset === first.text.length && endKey === first.key;
  20. if (startBlockIsSelected || !startBlock.equals(endBlock)) {
  21. handleIndent(editor, 'right');
  22. } else {
  23. editor.insertText(SLATE_TAB);
  24. }
  25. };
  26. const handleIndent = (editor: CoreEditor, indentDirection: 'left' | 'right') => {
  27. const curSelection = editor.value.selection;
  28. const selectedBlocks = editor.value.document.getLeafBlocksAtRange(curSelection).toArray();
  29. if (indentDirection === 'left') {
  30. for (const block of selectedBlocks) {
  31. const blockWhitespace = block.text.length - block.text.trimLeft().length;
  32. const textKey = block.getFirstText().key;
  33. const rangeProperties: RangeJSON = {
  34. anchor: {
  35. key: textKey,
  36. offset: blockWhitespace,
  37. path: [],
  38. },
  39. focus: {
  40. key: textKey,
  41. offset: blockWhitespace,
  42. path: [],
  43. },
  44. };
  45. editor.deleteBackwardAtRange(SlateRange.create(rangeProperties), Math.min(SLATE_TAB.length, blockWhitespace));
  46. }
  47. } else {
  48. const { startText } = editor.value;
  49. const textBeforeCaret = startText.text.slice(0, curSelection.start.offset);
  50. const isWhiteSpace = /^\s*$/.test(textBeforeCaret);
  51. for (const block of selectedBlocks) {
  52. editor.insertTextByKey(block.getFirstText().key, 0, SLATE_TAB);
  53. }
  54. if (isWhiteSpace) {
  55. editor.moveStartBackward(SLATE_TAB.length);
  56. }
  57. }
  58. };
  59. // Clears the rest of the line after the caret
  60. export default function IndentationPlugin(): Plugin {
  61. return {
  62. onKeyDown(event: KeyboardEvent, editor: CoreEditor, next: Function) {
  63. if (isIndentLeftHotkey(event) || isShiftTabHotkey(event)) {
  64. event.preventDefault();
  65. handleIndent(editor, 'left');
  66. } else if (isIndentRightHotkey(event)) {
  67. event.preventDefault();
  68. handleIndent(editor, 'right');
  69. } else if (event.key === 'Tab') {
  70. event.preventDefault();
  71. handleTabKey(event, editor, next);
  72. } else {
  73. return next();
  74. }
  75. return true;
  76. },
  77. };
  78. }