index.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import {uploadFiles} from "../upload/index";
  2. import {isCtrl, isFirefox} from "../util/compatibility";
  3. import {blurEvent, dropEvent, focusEvent, hotkeyEvent, selectEvent} from "../util/editorCommonEvent";
  4. import {getSelectText} from "../util/getSelectText";
  5. import {highlightToolbarSV} from "./highlightToolbarSV";
  6. import {html2md} from "./html2md";
  7. import {inputEvent} from "./inputEvent";
  8. import {insertText} from "./insertText";
  9. class Editor {
  10. public element: HTMLPreElement;
  11. public composingLock: boolean = false;
  12. public processTimeoutId: number;
  13. public hlToolbarTimeoutId: number;
  14. public preventInput: boolean;
  15. constructor(vditor: IVditor) {
  16. this.element = document.createElement("pre");
  17. this.element.className = "vditor-sv vditor-reset";
  18. this.element.setAttribute("placeholder", vditor.options.placeholder);
  19. this.element.setAttribute("contenteditable", "true");
  20. this.element.setAttribute("spellcheck", "false");
  21. this.bindEvent(vditor);
  22. focusEvent(vditor, this.element);
  23. blurEvent(vditor, this.element);
  24. hotkeyEvent(vditor, this.element);
  25. selectEvent(vditor, this.element);
  26. dropEvent(vditor, this.element);
  27. }
  28. private bindEvent(vditor: IVditor) {
  29. this.element.addEventListener("copy", (event: ClipboardEvent) => {
  30. event.stopPropagation();
  31. event.preventDefault();
  32. event.clipboardData.setData("text/plain", getSelectText(this.element));
  33. });
  34. this.element.addEventListener("paste", (event: ClipboardEvent) => {
  35. const textHTML = event.clipboardData.getData("text/html");
  36. const textPlain = event.clipboardData.getData("text/plain");
  37. event.stopPropagation();
  38. event.preventDefault();
  39. if (textHTML.trim() !== "") {
  40. if (textHTML.replace(/<(|\/)(html|body|meta)[^>]*?>/ig, "").trim() ===
  41. `<a href="${textPlain}">${textPlain}</a>` ||
  42. textHTML.replace(/<(|\/)(html|body|meta)[^>]*?>/ig, "").trim() ===
  43. `<!--StartFragment--><a href="${textPlain}">${textPlain}</a><!--EndFragment-->`) {
  44. // https://github.com/b3log/vditor/issues/37
  45. } else {
  46. const tempElement = document.createElement("div");
  47. tempElement.innerHTML = textHTML;
  48. tempElement.querySelectorAll("[style]").forEach((e) => {
  49. e.removeAttribute("style");
  50. });
  51. tempElement.querySelectorAll(".vditor-copy").forEach((e) => {
  52. e.remove();
  53. });
  54. tempElement.querySelectorAll(".vditor-anchor").forEach((e) => {
  55. e.remove();
  56. });
  57. const mdValue = html2md(vditor, tempElement.innerHTML, textPlain).trimRight();
  58. insertText(vditor, mdValue, "", true);
  59. return;
  60. }
  61. } else if (textPlain.trim() !== "" && event.clipboardData.files.length === 0) {
  62. // https://github.com/b3log/vditor/issues/67
  63. } else if (event.clipboardData.files.length > 0) {
  64. // upload file
  65. if (!(vditor.options.upload.url || vditor.options.upload.handler)) {
  66. return;
  67. }
  68. // NOTE: not work in Safari.
  69. // maybe the browser considered local filesystem as the same domain as the pasted data
  70. uploadFiles(vditor, event.clipboardData.files);
  71. return;
  72. }
  73. insertText(vditor, textPlain, "", true);
  74. });
  75. this.element.addEventListener("scroll", () => {
  76. if (vditor.preview.element.style.display !== "block") {
  77. return;
  78. }
  79. const textScrollTop = this.element.scrollTop;
  80. const textHeight = this.element.clientHeight;
  81. const textScrollHeight = this.element.scrollHeight - parseFloat(this.element.style.paddingBottom || "0");
  82. const preview = vditor.preview.element;
  83. if ((textScrollTop / textHeight > 0.5)) {
  84. preview.scrollTop = (textScrollTop + textHeight) *
  85. preview.scrollHeight / textScrollHeight - textHeight;
  86. } else {
  87. preview.scrollTop = textScrollTop *
  88. preview.scrollHeight / textScrollHeight;
  89. }
  90. });
  91. this.element.addEventListener("compositionstart", (event: InputEvent) => {
  92. this.composingLock = true;
  93. });
  94. this.element.addEventListener("compositionend", (event: InputEvent) => {
  95. if (!isFirefox()) {
  96. inputEvent(vditor, event);
  97. }
  98. this.composingLock = false;
  99. });
  100. this.element.addEventListener("input", (event: InputEvent) => {
  101. if (this.composingLock) {
  102. return;
  103. }
  104. if (this.preventInput) {
  105. this.preventInput = false;
  106. return;
  107. }
  108. inputEvent(vditor, event);
  109. });
  110. this.element.addEventListener("click", (event: InputEvent) => {
  111. highlightToolbarSV(vditor);
  112. });
  113. this.element.addEventListener("keyup", (event) => {
  114. if (event.isComposing || isCtrl(event)) {
  115. return;
  116. }
  117. highlightToolbarSV(vditor);
  118. if ((event.key === "Backspace" || event.key === "Delete") &&
  119. vditor.sv.element.innerHTML !== "" && vditor.sv.element.childNodes.length === 1 &&
  120. vditor.sv.element.firstElementChild && vditor.sv.element.firstElementChild.tagName === "DIV"
  121. && vditor.sv.element.firstElementChild.childElementCount === 2
  122. && (vditor.sv.element.firstElementChild.textContent === "" || vditor.sv.element.textContent === "\n")) {
  123. // 为空时显示 placeholder
  124. vditor.sv.element.innerHTML = "";
  125. return;
  126. }
  127. });
  128. }
  129. }
  130. export {Editor};