LeetCode Turbo

Replace monaco with vanilla textarea.

  1. // ==UserScript==
  2. // @name LeetCode Turbo
  3. // @description Replace monaco with vanilla textarea.
  4. // @namespace https://greatest.deepsurf.us/users/197529
  5. // @version 0.1.4
  6. // @author kkocdko
  7. // @license Unlicense
  8. // @match *://leetcode.com/problems/*
  9. // @match *://leetcode.cn/problems/*
  10. // @run-at document-start
  11. // ==/UserScript==
  12. "use strict";
  13.  
  14. const globalThis = this.unsafeWindow || this;
  15. const originFetch = globalThis.fetch;
  16. const textarea = document.createElement("textarea");
  17. textarea.style =
  18. "font-family: monospace; height: 100%; width: 100%; padding: 6px 10px; white-space: pre; outline: none;";
  19. const replaceEditorTimer = setInterval(() => {
  20. const el = document.querySelector("#editor");
  21. if (el) el.replaceWith(textarea), clearInterval(replaceEditorTimer);
  22. }, 500);
  23. globalThis.fetch = (input, init) => {
  24. if (input?.includes("/lc-monaco/") || input?.includes("/monaco-tm/"))
  25. throw Error("Monaco editor blocked.");
  26. if (input?.endsWith("/submit") || input?.endsWith("/submit/"))
  27. init.body = JSON.stringify({
  28. ...JSON.parse(init.body),
  29. typed_code: textarea.value,
  30. });
  31. return originFetch(input, init);
  32. };
  33. fetch("https://leetcode.cn/graphql/", {
  34. method: "POST",
  35. headers: { "content-type": "application/json" },
  36. body: JSON.stringify({
  37. operationName: "questionEditorData",
  38. variables: { titleSlug: location.pathname.split("/")[2] },
  39. query:
  40. "query questionEditorData($titleSlug: String!) { question(titleSlug: $titleSlug) { codeSnippets { langSlug code } } }",
  41. }),
  42. }).then(async (v) => {
  43. const snippets = (await v.json()).data.question.codeSnippets;
  44. textarea.value = snippets.find((v) => v.langSlug == "cpp").code;
  45. });
  46. globalThis.requestAnimationFrame = () => {}; // just ignore the requestAnimationFrame is ok?
  47.  
  48. // make a LRU cache for getComputedStyle
  49. /*
  50. const cache4gcs = new Map();
  51. const originGetComputedStyle = globalThis.getComputedStyle;
  52. globalThis.getComputedStyle = (elt, pseudoElt) => {
  53. if (pseudoElt !== undefined) return originGetComputedStyle(elt, pseudoElt);
  54. let pair = cache4gcs.get(elt);
  55. const now = Date.now();
  56. if (pair === undefined || pair[0] + 900 < now) {
  57. pair = [now, originGetComputedStyle(elt, pseudoElt)];
  58. console.log("miss " + now);
  59. }
  60. cache4gcs.delete(elt);
  61. cache4gcs.set(elt, pair);
  62. if (cache4gcs.size > 32) {
  63. const keys = cache4gcs.keys();
  64. for (let i = 0; i < 8; i++) cache4gcs.delete(keys.next().value);
  65. }
  66. return pair[1];
  67. };
  68. */
  69. // more conservative requestAnimationFrame
  70. /*
  71. const cache4raf = new Map();
  72. const originRequestAnimationFrame = globalThis.requestAnimationFrame;
  73. globalThis.requestAnimationFrame = (callback) => {
  74. const k = callback.toString();
  75. const now = Date.now();
  76. const interval = 200;
  77. if (cache4raf.get(k) > now - interval) {
  78. originRequestAnimationFrame(callback);
  79. } else {
  80. setTimeout(() => {
  81. originRequestAnimationFrame(callback);
  82. }, interval);
  83. }
  84. if (cache4raf.size > 32) {
  85. const keys = cache4raf.keys();
  86. for (let i = 0; i < 8; i++) cache4raf.delete(keys.next().value);
  87. }
  88. };
  89. */
  90.  
  91. // https://leetcode.cn/problems/intersection-of-two-arrays-ii/description/
  92.  
  93. // ublock append ||static.leetcode.cn/lc-monaco/
  94.  
  95. /*
  96. fetch("https://leetcode.cn/graphql/", {
  97. headers: { "content-type": "application/json" },
  98. method: "POST",
  99. body: JSON.stringify({
  100. query:
  101. "\n query questionTitle($titleSlug: String!) {\n question(titleSlug: $titleSlug) {\n questionId\n questionFrontendId\n title\n titleSlug\n isPaidOnly\n difficulty\n likes\n dislikes\n categoryTitle\n }\n}\n ",
  102. variables: { titleSlug: "intersection-of-two-arrays-ii" },
  103. operationName: "questionTitle",
  104. }),
  105. })
  106. .then((v) => v.text())
  107. .then((v) => console.log(v));
  108.  
  109. // 获取预设代码片段
  110. fetch("https://leetcode.cn/graphql/", {
  111. headers: { "content-type": "application/json" },
  112. method: "POST",
  113. body: JSON.stringify({
  114. query:
  115. "\n query questionEditorData($titleSlug: String!) {\n question(titleSlug: $titleSlug) {\n questionId\n questionFrontendId\n codeSnippets {\n lang\n langSlug\n code\n }\n envInfo\n enableRunCode\n hasFrontendPreview\n frontendPreviews\n }\n}\n ",
  116. variables: { titleSlug: "intersection-of-two-arrays-ii" },
  117. operationName: "questionEditorData",
  118. }),
  119. })
  120. .then((v) => v.json())
  121. .then((v) => console.log(v));
  122.  
  123. fetch("https://leetcode.cn/graphql/", {
  124. headers: { "content-type": "application/json" },
  125. method: "POST",
  126. body: JSON.stringify({
  127. query:
  128. "\n query questionContent($titleSlug: String!) {\n question(titleSlug: $titleSlug) {\n content\n editorType\n mysqlSchemas\n dataSchemas\n }\n}\n ",
  129. variables: { titleSlug: "intersection-of-two-arrays-ii" },
  130. operationName: "questionContent",
  131. }),
  132. });
  133.  
  134. fetch("https://leetcode.cn/graphql/", {
  135. headers: { "content-type": "application/json" },
  136. method: "POST",
  137. body: JSON.stringify({
  138. query:
  139. "\n query questionTranslations($titleSlug: String!) {\n question(titleSlug: $titleSlug) {\n translatedTitle\n translatedContent\n }\n}\n ",
  140. variables: { titleSlug: "intersection-of-two-arrays-ii" },
  141. operationName: "questionTranslations",
  142. }),
  143. });
  144. */